diff --git a/kioslave/nntp/nntp.cpp b/kioslave/nntp/nntp.cpp index 42e5570da..3269f7beb 100644 --- a/kioslave/nntp/nntp.cpp +++ b/kioslave/nntp/nntp.cpp @@ -1,964 +1,964 @@ /* This file is part of KDE Copyright (C) 2000 by Wolfram Diestel Copyright (C) 2005 by Tim Way - Copyright (C) 2005 by Volker Krause + Copyright (C) 2005 by Volker Krause 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 "nntp.h" #include #include #include #include #include #include #include #include #include #include #include #define DBG_AREA 7114 #define DBG kDebug(DBG_AREA) #define ERR kError(DBG_AREA) using namespace KIO; extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } int kdemain(int argc, char **argv) { KComponentData componentData("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 QByteArray & pool, const QByteArray & app, bool isSSL ) : TCPSlaveBase((isSSL ? "nntps" : "nntp"), pool, app, isSSL ), isAuthenticated( false ) { DBG << "=============> NNTPProtocol::NNTPProtocol"; readBufferLen = 0; m_defaultPort = isSSL ? DEFAULT_NNTPS_PORT : DEFAULT_NNTP_PORT; m_port = m_defaultPort; } NNTPProtocol::~NNTPProtocol() { DBG << "<============= NNTPProtocol::~NNTPProtocol"; // close connection nntp_close(); } void NNTPProtocol::setHost ( const QString & host, quint16 port, const QString & user, const QString & pass ) { DBG << ( ! user.isEmpty() ? (user+'@') : QString("")) << host << ":" << ( ( port == 0 ) ? m_defaultPort : port ); if ( isConnected() && (mHost != host || m_port != port || mUser != user || mPass != pass) ) nntp_close(); mHost = host; m_port = ( ( port == 0 ) ? m_defaultPort : port ); mUser = user; mPass = pass; } void NNTPProtocol::get( const KUrl& url ) { DBG << url.prettyUrl(); QString path = QDir::cleanPath(url.path()); // path should be like: /group/ or /group/ if ( path.startsWith( QDir::separator() ) ) path.remove( 0, 1 ); int pos = path.indexOf( QDir::separator() ); QString group; QString msg_id; if ( pos > 0 ) { group = path.left( pos ); msg_id = path.mid( pos + 1 ); } if ( group.isEmpty() || msg_id.isEmpty() ) { error(ERR_DOES_NOT_EXIST,path); return; } int res_code; DBG << "group:" << group << "msg:" << msg_id; if ( !nntp_open() ) return; // select group if necessary if ( mCurrentGroup != group && !group.isEmpty() ) { infoMessage( i18n("Selecting group %1...", group ) ); res_code = sendCommand( "GROUP " + group ); if ( res_code == 411 ){ error( ERR_DOES_NOT_EXIST, path ); mCurrentGroup.clear(); return; } else if ( res_code != 211 ) { unexpected_response( res_code, "GROUP" ); mCurrentGroup.clear(); return; } mCurrentGroup = group; } // get article infoMessage( i18n("Downloading article...") ); res_code = sendCommand( "ARTICLE " + msg_id ); if ( res_code == 423 || 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 char tmp[MAX_PACKET_LEN]; while ( true ) { if ( !waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } int len = readLine( tmp, MAX_PACKET_LEN ); const char* buffer = tmp; if ( len <= 0 ) break; if ( len == 3 && tmp[0] == '.' && tmp[1] == '\r' && tmp[2] == '\n') break; if ( len > 1 && tmp[0] == '.' && tmp[1] == '.' ) { ++buffer; --len; } data( QByteArray::fromRawData( buffer, len ) ); } // end of data data(QByteArray()); // finish finished(); } void NNTPProtocol::put( const KUrl &/*url*/, int /*permissions*/, KIO::JobFlags /*flags*/ ) { if ( !nntp_open() ) return; if ( post_article() ) finished(); } void NNTPProtocol::special(const QByteArray& data) { // 1 = post article int cmd; QDataStream stream(data); if ( !nntp_open() ) return; stream >> cmd; if (cmd == 1) { if (post_article()) finished(); } else { error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1", cmd)); } } bool NNTPProtocol::post_article() { DBG; // send post command infoMessage( i18n("Sending article...") ); 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; dataReq(); result = readData( buffer ); DBG << "receiving data:" << buffer; // treat the buffer data if ( result > 0 ) { // translate "\r\n." to "\r\n.." int pos = 0; if ( last_chunk_had_line_ending && buffer[0] == '.' ) { buffer.insert( 0, '.' ); pos += 2; } last_chunk_had_line_ending = ( buffer.endsWith( "\r\n" ) ); while ( (pos = buffer.indexOf( "\r\n.", pos )) > 0) { buffer.insert( pos + 2, '.' ); pos += 4; } // send data to socket, write() doesn't send the terminating 0 write( buffer, buffer.length() ); DBG << "writing:" << buffer; } } while ( result > 0 ); // error occurred? if (result<0) { ERR << "error while getting article data for posting"; 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 << url.prettyUrl(); UDSEntry entry; QString path = QDir::cleanPath(url.path()); QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",Qt::CaseInsensitive); QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", Qt::CaseInsensitive); int pos; QString group; QString msg_id; // / = group list if (path.isEmpty() || path == "/") { DBG << "root"; fillUDSEntry( entry, QString(), 0, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) ); // /group = message list } else if (regGroup.indexIn(path) == 0) { if ( path.startsWith( '/' ) ) path.remove(0,1); if ((pos = path.indexOf('/')) > 0) group = path.left(pos); else group = path; DBG << "group:" << group; // 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, false, ( S_IWUSR | S_IWGRP | S_IWOTH ) ); // /group/ = message } else if (regMsgId.indexIn(path) == 0) { pos = path.indexOf('<'); group = path.left(pos); msg_id = KUrl::fromPercentEncoding( path.right(path.length()-pos).toLatin1() ); if ( group.startsWith( '/' ) ) group.remove( 0, 1 ); if ((pos = group.indexOf('/')) > 0) group = group.left(pos); DBG << "group:" << group << "msg:" << msg_id; fillUDSEntry( entry, msg_id, 0, true ); // invalid url } else { error(ERR_DOES_NOT_EXIST,path); return; } statEntry(entry); finished(); } void NNTPProtocol::listDir( const KUrl& url ) { DBG << url.prettyUrl(); if ( !nntp_open() ) return; QString path = QDir::cleanPath(url.path()); if (path.isEmpty()) { KUrl newURL(url); newURL.setPath("/"); DBG << "redirecting to" << newURL.prettyUrl(); redirection(newURL); finished(); return; } else if ( path == "/" ) { fetchGroups( url.queryItem( "since" ), url.queryItem( "desc" ) == "true" ); finished(); } else { // if path = /group int pos; QString group; if ( path.startsWith( '/' ) ) path.remove( 0, 1 ); if ((pos = path.indexOf('/')) > 0) group = path.left(pos); else group = path; QString first = url.queryItem( "first" ); QString max = url.queryItem( "max" ); if ( fetchGroup( group, first.toULong(), max.toULong() ) ) finished(); } } void NNTPProtocol::fetchGroups( const QString &since, bool desc ) { int expected; int res; if ( since.isEmpty() ) { // full listing infoMessage( i18n("Downloading group list...") ); res = sendCommand( "LIST" ); expected = 215; } else { // incremental listing infoMessage( i18n("Looking for new groups...") ); res = sendCommand( "NEWGROUPS " + since ); expected = 231; } if ( res != expected ) { unexpected_response( res, "LIST" ); return; } // read newsgroups line by line QByteArray line; QString group; int pos, pos2; long msg_cnt; long access; UDSEntry entry; QHash entryMap; // read in data and process each group. one line at a time while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QByteArray( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; // group name if ((pos = line.indexOf(' ')) > 0) { group = line.left(pos); // number of messages line.remove(0,pos+1); long last = 0; access = 0; if (((pos = line.indexOf(' ')) > 0 || (pos = line.indexOf('\t')) > 0) && ((pos2 = line.indexOf(' ',pos+1)) > 0 || (pos2 = line.indexOf('\t',pos+1)) > 0)) { last = line.left(pos).toLongLong(); long first = line.mid(pos+1,pos2-pos-1).toLongLong(); msg_cnt = abs(last-first+1); // group access rights switch ( line[pos2 + 1] ) { case 'n': access = 0; break; case 'm': access = S_IWUSR | S_IWGRP; break; case 'y': access = S_IWUSR | S_IWGRP | S_IWOTH; break; } } else { msg_cnt = 0; } entry.clear(); fillUDSEntry( entry, group, msg_cnt, false, access ); if ( !desc ) listEntry( entry, false ); else entryMap.insert( group, entry ); } } // handle group descriptions QHash::Iterator it = entryMap.begin(); if ( desc ) { infoMessage( i18n("Downloading group descriptions...") ); totalSize( entryMap.size() ); } while ( desc ) { // request all group descriptions if ( since.isEmpty() ) res = sendCommand( "LIST NEWSGROUPS" ); else { // request only descriptions for new groups if ( it == entryMap.end() ) break; res = sendCommand( "LIST NEWSGROUPS " + it.key() ); ++it; if( res == 503 ) { // Information not available (RFC 2980 §2.1.6), try next group continue; } } if ( res != 215 ) { // No group description available or not implemented break; } // download group descriptions while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QByteArray( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; //DBG << " fetching group description: " << QString( line ).trimmed(); int pos = line.indexOf( ' ' ); pos = pos < 0 ? line.indexOf( '\t' ) : qMin( pos, line.indexOf( '\t' ) ); group = line.left( pos ); QString groupDesc = line.right( line.length() - pos ).trimmed(); if ( entryMap.contains( group ) ) { entry = entryMap.take( group ); entry.insert( KIO::UDSEntry::UDS_EXTRA, groupDesc ); listEntry( entry, false ); } } if ( since.isEmpty() ) break; } // take care of groups without descriptions for ( QHash::Iterator it = entryMap.begin(); it != entryMap.end(); ++it ) listEntry( it.value(), false ); entry.clear(); listEntry( entry, true ); } bool NNTPProtocol::fetchGroup( QString &group, unsigned long first, unsigned long max ) { int res_code; QString resp_line; // select group infoMessage( i18n("Selecting group %1...", group ) ); res_code = sendCommand( "GROUP " + group ); if ( res_code == 411 ) { error( ERR_DOES_NOT_EXIST, group ); mCurrentGroup.clear(); return false; } else if ( res_code != 211 ) { unexpected_response( res_code, "GROUP" ); mCurrentGroup.clear(); return false; } mCurrentGroup = group; // repsonse to "GROUP " command is 211 then find the message count (cnt) // and the first and last message followed by the group name unsigned long firstSerNum, lastSerNum; resp_line = QString::fromLatin1( readBuffer ); QRegExp re ( "211\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); if ( re.indexIn( resp_line ) != -1 ) { firstSerNum = re.cap( 2 ).toLong(); lastSerNum = re.cap( 3 ).toLong(); } else { error( ERR_INTERNAL, i18n("Could not extract message serial numbers from server response:\n%1", resp_line ) ); return false; } if (firstSerNum == 0) return true; first = qMax( first, firstSerNum ); if ( max > 0 && lastSerNum - first > max ) first = lastSerNum - max + 1; DBG << "Starting from serial number: " << first << " of " << firstSerNum << " - " << lastSerNum; setMetaData( "FirstSerialNumber", QString::number( firstSerNum ) ); setMetaData( "LastSerialNumber", QString::number( lastSerNum ) ); infoMessage( i18n("Downloading new headers...") ); totalSize( lastSerNum - first ); 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; // 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 ... QString msg_id; int pos, pos2; if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) { msg_id = resp_line.mid(pos,pos2-pos+1); fillUDSEntry( entry, msg_id, 0, true ); listEntry( entry, false ); } else { error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1", resp_line)); return false; } // go through all articles while (true) { res_code = sendCommand("NEXT"); if (res_code == 421) { // last artice reached entry.clear(); listEntry( entry, true ); return true; } else if (res_code != 223) { unexpected_response(res_code,"NEXT"); return false; } //res_line: 223 nnn ... resp_line = readBuffer; if ((pos = resp_line.indexOf('<')) > 0 && (pos2 = resp_line.indexOf('>',pos+1))) { msg_id = resp_line.mid(pos,pos2-pos+1); entry.clear(); fillUDSEntry( entry, msg_id, 0, true ); listEntry( entry, false ); } else { error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1", resp_line)); return false; } } return true; // Not reached } bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool ¬Supported ) { 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 ); nntp_close(); return false; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QString::fromLatin1( readBuffer, readBufferLen ); if ( line == ".\r\n" ) break; headers << line.trimmed(); DBG << "OVERVIEW.FMT:" << line.trimmed(); } } 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 name; UDSEntry entry; int udsType; QStringList fields; while ( true ) { if ( ! waitForResponse( readTimeout() ) ) { error( ERR_SERVER_TIMEOUT, mHost ); nntp_close(); return false; } readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); line = QString::fromLatin1( readBuffer, readBufferLen ); if ( line == ".\r\n" ) { entry.clear(); listEntry( entry, true ); return true; } fields = line.split( '\t', QString::KeepEmptyParts); msgSize = 0; entry.clear(); udsType = KIO::UDSEntry::UDS_EXTRA; QStringList::ConstIterator it = headers.constBegin(); QStringList::ConstIterator it2 = fields.constBegin(); // first entry is the serial number name = (*it2); ++it2; for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) { if ( (*it) == "Bytes:" ) { msgSize = (*it2).toLong(); continue; } QString atomStr; if ( (*it).endsWith( "full" ) ) if ( (*it2).trimmed().isEmpty() ) atomStr = (*it).left( (*it).indexOf( ':' ) + 1 ); // strip of the 'full' suffix else atomStr = (*it2).trimmed(); else atomStr = (*it) + ' ' + (*it2).trimmed(); entry.insert( udsType++, atomStr ); if ( udsType >= KIO::UDSEntry::UDS_EXTRA_END ) break; } fillUDSEntry( entry, name, msgSize, true ); listEntry( entry, false ); } return true; // not reached } void NNTPProtocol::fillUDSEntry( UDSEntry& entry, const QString& name, long size, bool is_article, long access ) { long posting=0; // entry name entry.insert(KIO::UDSEntry::UDS_NAME, name); // entry size entry.insert(KIO::UDSEntry::UDS_SIZE, size); // file type entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, is_article? S_IFREG : S_IFDIR); // access permissions posting = postingAllowed? access : 0; long long accessVal = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting); entry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal); entry.insert(KIO::UDSEntry::UDS_USER, mUser.isEmpty() ? QString::fromLatin1("root") : mUser); /* entry->insert(UDS_GROUP, QString::fromLatin1("root")); */ // MIME type if (is_article) { entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("message/news") ); } } void NNTPProtocol::nntp_close () { if ( isConnected() ) { write( "QUIT\r\n", 6 ); disconnectFromHost(); isAuthenticated = false; } mCurrentGroup.clear(); } bool NNTPProtocol::nntp_open() { // if still connected reuse connection if ( isConnected() ) { DBG << "reusing old connection"; return true; } DBG << " nntp_open -- creating a new connection to" << mHost << ":" << m_port; // create a new connection (connectToHost() includes error handling) infoMessage( i18n("Connecting to server...") ); if ( connectToHost( (isAutoSsl() ? "nntps" : "nntp"), mHost.toLatin1(), m_port ) ) { DBG << " nntp_open -- connection is open"; // 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; 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; } if ( !startSsl() ) { error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") ); return false; } } // *try* to authenticate now (see bug#167718) authenticate(); return true; } return false; } int NNTPProtocol::sendCommand( const QString &cmd ) { int res_code = 0; if ( !nntp_open() ) { ERR << "NOT CONNECTED, cannot send cmd" << cmd; return 0; } DBG << "cmd:" << cmd; write( cmd.toLatin1(), 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"; if ( mUser.isEmpty() || mPass.isEmpty() ) { KIO::AuthInfo authInfo; authInfo.username = mUser; authInfo.password = mPass; if ( openPasswordDialog( authInfo ) ) { mUser = authInfo.username; mPass = authInfo.password; } } if ( mUser.isEmpty() || mPass.isEmpty() ) return res_code; res_code = authenticate(); if (res_code != 281) { // error should be handled by invoking function return res_code; } // ok now, resend command write( cmd.toLatin1(), cmd.length() ); if ( !cmd.endsWith( "\r\n" ) ) write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); } return res_code; } int NNTPProtocol::authenticate() { int res_code = 0; if( isAuthenticated ) { // already authenticated return 281; } if( mUser.isEmpty() || mPass.isEmpty() ) { return 281; // failsafe : maybe add a "relax" mode to optionally ask user/pwd. } // send username to server and confirm response write( "AUTHINFO USER ", 14 ); write( mUser.toLatin1(), mUser.length() ); write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); if( res_code == 281 ) { // no password needed (RFC 2980 3.1.1 does not required one) return res_code; } if (res_code != 381) { // error should be handled by invoking function return res_code; } // send password write( "AUTHINFO PASS ", 14 ); write( mPass.toLatin1(), mPass.length() ); write( "\r\n", 2 ); res_code = evalResponse( readBuffer, readBufferLen ); if( res_code == 281 ) { isAuthenticated = true; } return res_code; } void NNTPProtocol::unexpected_response( int res_code, const QString &command ) { ERR << "Unexpected response to" << command << "command: (" << res_code << ")" << readBuffer; KIO::Error errCode; switch ( res_code ) { case 480: errCode = ERR_COULD_NOT_LOGIN; break; default: errCode = ERR_INTERNAL; } error( errCode, i18n("Unexpected server response to %1 command:\n%2", command, readBuffer ) ); nntp_close(); } int NNTPProtocol::evalResponse ( char *data, ssize_t &len ) { if ( !waitForResponse( responseTimeout() ) ) { error( ERR_SERVER_TIMEOUT , mHost ); nntp_close(); return -1; } 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 << "got:" << respCode; 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 index 9abcdcd2c..9fa28d174 100644 --- a/kioslave/nntp/nntp.h +++ b/kioslave/nntp/nntp.h @@ -1,141 +1,141 @@ /* This file is part of KDE Copyright (C) 2000 by Wolfram Diestel Copyright (C) 2005 by Tim Way - Copyright (C) 2005 by Volker Krause + Copyright (C) 2005 by Volker Krause 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 #include #define MAX_PACKET_LEN 8192 /* 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 QByteArray & pool, const QByteArray & app, bool isSSL ); virtual ~NNTPProtocol(); virtual void get(const KUrl& url ); virtual void put( const KUrl& url, int permissions, KIO::JobFlags flags ); virtual void stat(const KUrl& url ); virtual void listDir(const KUrl& url ); virtual void setHost(const QString& host, quint16 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; quint16 m_port, m_defaultPort; bool postingAllowed, isAuthenticated; char readBuffer[MAX_PACKET_LEN]; ssize_t readBufferLen; /// Current selected newsgroup QString mCurrentGroup; /** * 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 * @param desc Fetch group description (needs more memory) */ void fetchGroups( const QString &since, bool desc ); /** * 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. * @param max Maximal number of returned messages, 0 means unlimited. * @return true on success, false otherwise. */ bool fetchGroup ( QString &group, unsigned long first = 0, unsigned long max = 0 ); /** * Fetch message listing from the current group using RFC977 STAT/NEXT * commands. * @param first message number of the first article * @return true on success, 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 success, false otherwise */ bool fetchGroupXOVER( unsigned long first, bool ¬Supported ); /// creates an UDSEntry with file information used in stat and listDir void fillUDSEntry ( KIO::UDSEntry & entry, const QString & name, long size, bool is_article, long access = 0 ); /// 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 ); /** * Try to authenticate to the server. * @return the response code from the server if the mUser/mPassword * are available; 281 (successfull authentication) otherwise. */ int authenticate(); }; #endif diff --git a/kldap/tests/testkldap.h b/kldap/tests/testkldap.h index 700279384..c680ecc27 100644 --- a/kldap/tests/testkldap.h +++ b/kldap/tests/testkldap.h @@ -1,57 +1,57 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTKLDAP_H #define TESTKLDAP_H #include #include #include #include using namespace KLDAP; class KLdapTest : public QObject { Q_OBJECT private Q_SLOTS: //void testKLdap(); void initTestCase(); void cleanupTestCase(); void testLdapUrl(); void testBer(); void testLdapConnection(); void testLdapSearch(); void testLdapDN(); void testLdapModel(); public Q_SLOTS: void searchResult( KLDAP::LdapSearch *search ); void searchData( KLDAP::LdapSearch *search, const KLDAP::LdapObject &obj ); private: QString m_url; LdapSearch *m_search; LdapObjects m_objects; LdapModel *m_model; }; #endif diff --git a/kmime/tests/contentindextest.cpp b/kmime/tests/contentindextest.cpp index c1dfcff4f..ac6ff7f18 100644 --- a/kmime/tests/contentindextest.cpp +++ b/kmime/tests/contentindextest.cpp @@ -1,91 +1,91 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contentindextest.h" #include #include using namespace KMime; #include QTEST_KDEMAIN( ContentIndexTest, NoGUI ) void ContentIndexTest::testToString() { KMime::ContentIndex ci; QCOMPARE( ci.toString(), QString() ); ci.push( 1 ); QCOMPARE( ci.toString(), QString( "1" ) ); ci.push( 2 ); QCOMPARE( ci.toString(), QString( "2.1" ) ); } void ContentIndexTest::testFromString( ) { ContentIndex ci1; QVERIFY( !ci1.isValid() ); ContentIndex ci2( "1.2.bla" ); QVERIFY( !ci2.isValid() ); ContentIndex ci3( "1" ); QVERIFY( ci3.isValid() ); QCOMPARE( ci3.pop(), 1u ); QVERIFY( !ci3.isValid() ); ContentIndex ci4( "3.2" ); QVERIFY( ci4.isValid() ); QCOMPARE( ci4.pop(), 3u ); QCOMPARE( ci4.pop(), 2u ); QVERIFY( !ci4.isValid() ); } void ContentIndexTest::testContent( ) { Content *c1 = new Content(); QCOMPARE( c1->content( ContentIndex() ), c1 ); QCOMPARE( c1->content( ContentIndex( "1" ) ), (Content*)0 ); QCOMPARE( c1->indexForContent( c1 ), ContentIndex() ); Content *c11 = new Content(); // this makes c1 multipart/mixed, ie. c11 will be the second child! c1->addContent( c11 ); QCOMPARE( c1->content( ContentIndex( "2" ) ), c11 ); QCOMPARE( c1->content( ContentIndex( "3" ) ), (Content*)0 ); QCOMPARE( c1->content( ContentIndex( "2.1" ) ), (Content*)0 ); QCOMPARE( c1->indexForContent( c1 ), ContentIndex() ); QCOMPARE( c1->indexForContent( c11 ), ContentIndex( "2" ) ); QCOMPARE( c1->indexForContent( c1->contents().first() ), ContentIndex( "1" ) ); Content *c12 = new Content(); c1->addContent( c12 ); // c12 becomes multipart/mixed, ie. c12 will be the second child! Content *c121 = new Content(); c12->addContent( c121 ); QCOMPARE( c1->content( ContentIndex( "3" ) ), c12 ); QCOMPARE( c1->content( ContentIndex( "3.2" ) ), c121 ); QCOMPARE( c1->indexForContent( c121 ), ContentIndex( "3.2" ) ); QCOMPARE( c121->index(), ContentIndex( "3.2" ) ); QCOMPARE( c1->indexForContent( (Content*)0 ), ContentIndex() ); } #include "contentindextest.moc" diff --git a/kmime/tests/contentindextest.h b/kmime/tests/contentindextest.h index 2f5ea7b92..0a22b16c2 100644 --- a/kmime/tests/contentindextest.h +++ b/kmime/tests/contentindextest.h @@ -1,34 +1,34 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_CONTENTINDEXTEXT_H #define KMIME_CONTENTINDEXTEXT_H #include class ContentIndexTest : public QObject { Q_OBJECT private Q_SLOTS: void testToString(); void testFromString(); void testContent(); }; #endif diff --git a/kmime/tests/kmime_content_test.cpp b/kmime/tests/kmime_content_test.cpp index a531ef845..f4799acbd 100644 --- a/kmime/tests/kmime_content_test.cpp +++ b/kmime/tests/kmime_content_test.cpp @@ -1,446 +1,446 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmime_content_test.h" #include #include #include #include using namespace KMime; QTEST_KDEMAIN( KMimeContentTest, NoGUI ) void KMimeContentTest::testGetHeaderInstance( ) { // stuff that looks trivial but breaks if you mess with virtual method signatures (see r534381) Headers::From *myfrom = new Headers::From(); QCOMPARE( myfrom->type(), "From" ); Headers::Base *mybase = myfrom; QCOMPARE( mybase->type(), "From" ); // getHeaderInstance() is protected, so we need to test it via KMime::Message Message *c = new Message(); Headers::From *f1 = c->from( true ); Headers::From *f2 = c->from( true ); QCOMPARE( f1, f2 ); delete c; } void KMimeContentTest::testSetContent() { Content *c = new Content(); QVERIFY( !c->hasContent() ); // head and body present c->setContent( "head1\nhead2\n\nbody1\n\nbody2\n" ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); QList list; list << "head1" << "head2" << "" << "body1" << "" << "body2"; c->setContent( list ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); // ### the final \n is questionable // empty content c->setContent( QByteArray() ); QVERIFY( !c->hasContent() ); QVERIFY( c->head().isEmpty() ); QVERIFY( c->body().isEmpty() ); // empty head c->setContent( "\nbody1\n\nbody2\n" ); QVERIFY( c->hasContent() ); QVERIFY( c->head().isEmpty() ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); list.clear(); list << "" << "body1" << "" << "body2"; c->setContent( list ); QVERIFY( c->hasContent() ); QVERIFY( c->head().isEmpty() ); QCOMPARE( c->body(), QByteArray( "body1\n\nbody2\n" ) ); // empty body c->setContent( "head1\nhead2\n\n" ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QVERIFY( c->body().isEmpty() ); list.clear(); list << "head1" << "head2" << ""; c->setContent( list ); QVERIFY( c->hasContent() ); QCOMPARE( c->head(), QByteArray( "head1\nhead2\n" ) ); QVERIFY( c->body().isEmpty() ); } void KMimeContentTest::testMultipleHeaderExtraction() { QByteArray data = "From: Nathaniel Borenstein \n" "To: Ned Freed \n" "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" "Subject: Sample message\n" "Received: from ktown.kde.org ([192.168.100.1])\n" "Received: from dev1.kde.org ([192.168.100.2])\n" "\t by ktown.kde.org ([192.168.100.1])\n" "Received: from dev2.kde.org ([192.168.100.3])\n" " by ktown.kde.org ([192.168.100.1])\n"; Message *msg = new Message(); msg->setContent( data ); // FAILS identically to KMimeContentTest::testMultipartMixed // QCOMPARE( msg->encodedContent(), data ); msg->parse(); QList result = msg->headersByType( "Received" ); QCOMPARE( result.count(), 3 ); QCOMPARE( result[0]->asUnicodeString(), QString("from ktown.kde.org ([192.168.100.1])") ); QCOMPARE( result[1]->asUnicodeString(), QString("from dev1.kde.org ([192.168.100.2]) by ktown.kde.org ([192.168.100.1])") ); QCOMPARE( result[2]->asUnicodeString(), QString("from dev2.kde.org ([192.168.100.3]) by ktown.kde.org ([192.168.100.1])") ); } void KMimeContentTest::testMultipartMixed() { // example taken from RFC 2046, section 5.1.1. QByteArray data = "From: Nathaniel Borenstein \n" "To: Ned Freed \n" "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" "Subject: Sample message\n" "MIME-Version: 1.0\n" "Content-type: multipart/mixed; boundary=\"simple boundary\"\n" "\n" "This is the preamble. It is to be ignored, though it\n" "is a handy place for composition agents to include an\n" "explanatory note to non-MIME conformant readers.\n" "\n" "--simple boundary\n" "\n" "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak.\n" "--simple boundary\n" "Content-type: text/plain; charset=us-ascii\n" "\n" "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n" "\n" "--simple boundary--\n" "\n" "This is the epilogue. It is also to be ignored.\n"; QByteArray part1 = "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak."; QByteArray part2 = "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n"; // slightly diffrent from original data QByteArray assembled = "From: Nathaniel Borenstein \n" "Subject: Sample message\n" "To: Ned Freed \n" "Date: Sun, 21 Mar 1993 23:56:48 -0800\n" "MIME-Version: 1.0\n" "Content-Type: multipart/mixed; boundary=\"simple boundary\"\n" "\n" "\n" "--simple boundary\n" "\n" "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak.\n" "--simple boundary\n" "Content-Type: text/plain; charset=\"us-ascii\"\n" "\n" "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n" "\n" "--simple boundary--\n"; // test parsing Message *msg = new Message(); msg->setContent( data ); QCOMPARE( msg->encodedContent(), data ); msg->parse(); QVERIFY( msg->contentType()->isMultipart() ); Content::List list = msg->contents(); QCOMPARE( list.count(), 2 ); Content *c = list.takeFirst(); QCOMPARE( c->body(), part1 ); c = list.takeFirst(); QCOMPARE( c->body(), part2 ); // assemble again msg->assemble(); QCOMPARE( msg->encodedContent(), assembled ); delete msg; // assembling from scratch msg = new Message(); msg->from()->from7BitString( "Nathaniel Borenstein " ); msg->to()->from7BitString( "Ned Freed " ); msg->subject()->from7BitString( "Sample message" ); msg->date()->from7BitString( "Sun, 21 Mar 1993 23:56:48 -0800 (PST)" ); msg->setBody( part1 ); c = new Content(); c->setBody( part2 ); c->contentType()->setMimeType( "text/plain" ); c->contentType()->setCharset( "us-ascii" ); msg->addContent( c ); msg->contentType()->setBoundary( "simple boundary" ); list = msg->contents(); QCOMPARE( list.count(), 2 ); c = list.takeFirst(); QCOMPARE( c->body(), part1 ); c = list.takeFirst(); QCOMPARE( c->body(), part2 ); msg->assemble(); QCOMPARE( msg->encodedContent(), assembled ); } void KMimeContentTest::testImplicitMultipartGeneration() { Content *c1 = new Content(); c1->contentType()->from7BitString( "text/plain" ); c1->setBody( "textpart" ); Content *c2 = new Content(); c2->contentType()->from7BitString( "text/html" ); c2->setBody( "htmlpart" ); c1->addContent( c2 ); // c1 implicitly converted into a multipart/mixed node QCOMPARE( c1->contentType()->mimeType(), QByteArray("multipart/mixed") ); QVERIFY( c1->body().isEmpty() ); Content *c = c1->contents().at( 0 ); // former c1 QCOMPARE( c->contentType()->mimeType(), QByteArray("text/plain") ); QCOMPARE( c->body(), QByteArray("textpart") ); QCOMPARE( c1->contents().at( 1 ), c2 ); } void KMimeContentTest::testExplicitMultipartGeneration() { Content *c1 = new Content(); c1->contentType()->from7BitString( "multipart/mixed" ); Content *c2 = new Content(); c2->contentType()->from7BitString( "text/plain" ); c2->setBody( "textpart" ); Content *c3 = new Content(); c3->contentType()->from7BitString( "text/html" ); c3->setBody( "htmlpart" ); c1->addContent( c2 ); c1->addContent( c3 ); // c1 should not be changed QCOMPARE( c1->contentType()->mimeType(), QByteArray("multipart/mixed") ); QVERIFY( c1->body().isEmpty() ); QCOMPARE( c1->contents().at( 0 ), c2 ); QCOMPARE( c1->contents().at( 1 ), c3 ); } void KMimeContentTest::testParsingUuencoded() { const QByteArray body = "This is a test message that should appears as a text/plain part\n" "once this message is parsed and convert to a MIME tree.\n" "\n" "\n"; const QString imageName = "Name of the encoded file (oxygen 22x22 kde.png)"; const QByteArray uuencodedMsg = "Path: news.example.net!not-for-mail\n" "From: Coin coin \n" "Newsgroups: test.kmime.uuencoded\n" "Subject: Kmime test\n" "Date: Thu, 14 Apr 2005 20:12:47 -0700\n" "Message-ID: \n" "X-Newsreader: Forte Agent 2.0/32.640\n" "Lines: 1283\n" "Organization: Ament\n" "Xref: news.example.net test.kmime.uuencoded:4584\n" "\n" "This is a test message that should appears as a text/plain part\n" "once this message is parsed and convert to a MIME tree.\n" "\n" "begin 644 Name of the encoded file (oxygen 22x22 kde.png)\n" "MB5!.1PT*&@H````-24A$4@```!8````6\"`8```#$M&P[````!'-\"250(\"`@(\n" "M?`ADB`````EP2%ES```#=@```W8!?=6\"S````!ET15AT4V]F='=A^U1FZM2*X2V035`Q5.9UM3S:>&Z__]?36N1],G.QRQ[_QS:6C!YX\\R)\n" "M;C]FS/&)EU&=IH<.PEQ\"\",LUJMK*2K9VT9UVZ.V9D62)H.TK#RE4:;\n" "M1$1..L\"H&WSY\\)$#K*X-(%0,!ST6!EV&W91>&B-B^=>;.Z@*\"WL'#`8[NIG@$<=8(RXI2@5)MF,8;^'F!B5&$]$HX[9=L')\n" "MCW>)G.'I,W/Z>Q;()QF??K_B;,*Y\\S-^,XGPE&\"3/F`,N\\08*:L\"%8=U\"6H2\n" "M@D3X8&F\"H6@@31+B.*'T4%3*`\\O;1,[AG$.S\"5_\\\n" "MQ\"K.6OYVJ>*5&QT\"!I4(%W>_VZ$,@::%5H6Z5L1:&766K%GJ6R/625TKK4+E\n" "M`[,XY==_N$'9P.E?O,&6ZR`BB`AK*PD@&($B+\\CKFKRNJ6I/6;6(M83E-6O:\n" "MN&>-L\\Q+3]6V%%7#=&/,P;TISYZ]PL4JIJ@;0`#AF>?'/F]QRWWL7N;M7LCW-:)J*-C2`T*>/$5LJ*$$]=963.!\"4M@UL9H:FAK8%\n" "M5064IA5^=/8FEZ[EW/+MS]])K\\SQ=8GW-2)*1H8QA09V^:9\"M>6A9R[RR=.O\n" "M<>'-.7>N6-1;0F,((:\"J&&)<.N+TLY?)2\\^@8WC\\4X?0VA/J$@T!J0G\"L4<&\n" "M@[7C-Q975U,;)0QZ(U96]O/@>X0'/KC,X[_3YC%FVPW2Z1613\n" "M]HSV<77]E3\\\"K:@JLO]#*]W%X\\_W]]YS?[>_)XU=A#,6:P0DH-KB38;7G,CV\n" "MB;1+)%WBN$MD$U2$?+[57+OTTJLW7S_SI6I\\X8*H*K>(B`-&P'Y``.'_H[QE\n" ";$[@.>-WU'U(#IV=EWM`H`````$E%3D2N0F\"\"\n" "`\n" "end\n" "\n"; Message *msg = new Message(); msg->setContent( uuencodedMsg ); msg->parse(); Content::List contents = msg->contents(); // text + image QCOMPARE( contents.size(), 2 ); Content *c = 0; // Check the first text part c = contents.at( 0 ); QVERIFY( c->contentType()->isPlainText() ); QCOMPARE( c->body(), body ); // Check the image part c = contents.at( 1 ); QVERIFY( !c->contentType()->isText() ); QCOMPARE( c->contentType()->name(), imageName ); delete msg; } void KMimeContentTest::testParent() { Content *c1 = new Content(); c1->contentType()->from7BitString( "multipart/mixed" ); Content *c2 = new Content(); c2->contentType()->from7BitString( "text/plain" ); c2->setBody( "textpart" ); Content *c3 = new Content(); c3->contentType()->from7BitString( "text/html" ); c3->setBody( "htmlpart" ); Content *c4 = new Content(); c4->contentType()->from7BitString( "text/html" ); c4->setBody( "htmlpart2" ); Content *c5 = new Content(); c5->contentType()->from7BitString( "multipart/mixed" ); //c2 doesn't have a parent yet QCOMPARE(c2->parent(), (Content*)(0L)); c1->addContent( c2 ); c1->addContent( c3 ); c1->addContent( c4 ); // c1 is the parent of those QCOMPARE( c2->parent(), c1 ); QCOMPARE( c3->parent(), c1 ); //test removal c1->removeContent(c2, false); QCOMPARE(c2->parent(), (Content*)(0L)); QCOMPARE(c1->contents().at( 0 ), c3 ); //check if the content is moved correctly to another parent c5->addContent( c4 ); QCOMPARE(c4->parent(), c5); QCOMPARE(c1->contents().count(), 0); //yes, it should be 0 QCOMPARE(c5->contents().at( 0 ), c4); // example taken from RFC 2046, section 5.1.1. QByteArray data = "From: Nathaniel Borenstein \n" "To: Ned Freed \n" "Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\n" "Subject: Sample message\n" "MIME-Version: 1.0\n" "Content-type: multipart/mixed; boundary=\"simple boundary\"\n" "\n" "This is the preamble. It is to be ignored, though it\n" "is a handy place for composition agents to include an\n" "explanatory note to non-MIME conformant readers.\n" "\n" "--simple boundary\n" "\n" "This is implicitly typed plain US-ASCII text.\n" "It does NOT end with a linebreak.\n" "--simple boundary\n" "Content-type: text/plain; charset=us-ascii\n" "\n" "This is explicitly typed plain US-ASCII text.\n" "It DOES end with a linebreak.\n" "\n" "--simple boundary--\n" "\n" "This is the epilogue. It is also to be ignored.\n"; // test parsing Message *msg = new Message(); msg->setContent( data ); msg->parse(); QCOMPARE( msg->parent(), (Content*)(0L)); QCOMPARE( msg->contents().at( 0 )->parent(), msg); QCOMPARE( msg->contents().at( 1 )->parent(), msg); delete msg; } #include "kmime_content_test.moc" diff --git a/kmime/tests/kmime_content_test.h b/kmime/tests/kmime_content_test.h index 0085cda2a..148a4e499 100644 --- a/kmime/tests/kmime_content_test.h +++ b/kmime/tests/kmime_content_test.h @@ -1,43 +1,43 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_CONTENT_TEST_H #define KMIME_CONTENT_TEST_H #include class KMimeContentTest : public QObject { Q_OBJECT private Q_SLOTS: void testGetHeaderInstance(); void testSetContent(); void testMultipartMixed(); void testImplicitMultipartGeneration(); void testExplicitMultipartGeneration(); void testMultipleHeaderExtraction(); /** Tests that a message with uuencoded content is parsed correctly and if a corresponding MIME structure is created. */ void testParsingUuencoded(); void testParent(); }; #endif diff --git a/kmime/tests/kmime_util_test.cpp b/kmime/tests/kmime_util_test.cpp index ab6d0beab..0170cd706 100644 --- a/kmime/tests/kmime_util_test.cpp +++ b/kmime/tests/kmime_util_test.cpp @@ -1,63 +1,63 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library 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. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kmime_util_test.h" #include "kmime_util_test.moc" #include using namespace KMime; QTEST_KDEMAIN( KMimeUtilTest, NoGUI ) void KMimeUtilTest::testUnfoldHeader() { // empty header QCOMPARE( KMime::unfoldHeader( "" ), QByteArray() ); // identity QCOMPARE( KMime::unfoldHeader( "bla" ), QByteArray( "bla" ) ); // single folding QCOMPARE( KMime::unfoldHeader( "bla\nblub" ), QByteArray( "bla blub" ) ); QCOMPARE( KMime::unfoldHeader( "bla\n \t blub" ), QByteArray( "bla blub" ) ); QCOMPARE( KMime::unfoldHeader( "bla \r\nblub" ), QByteArray( "bla blub" ) ); // multiple folding QCOMPARE( KMime::unfoldHeader( "bla\nbla\nblub" ), QByteArray( "bla bla blub" ) ); QCOMPARE( KMime::unfoldHeader( "bla \r\n bla \r\n blub" ), QByteArray( "bla bla blub" ) ); QCOMPARE( KMime::unfoldHeader( "bla\n" ), QByteArray( "bla" ) ); } void KMimeUtilTest::testExtractHeader() { QByteArray header( "To: \nSubject: =?UTF-8?Q?_Notification_for_appointment:?=\n =?UTF-8?Q?_Test?=\nMIME-Version: 1.0" ); // basic tests QVERIFY( extractHeader( header, "Foo" ).isEmpty() ); QCOMPARE( extractHeader( header, "To" ), QByteArray("") ); // case insensitive matching QCOMPARE( extractHeader( header, "mime-version" ), QByteArray( "1.0" ) ); // extraction of multi-line headers QCOMPARE( extractHeader( header, "Subject" ), QByteArray("=?UTF-8?Q?_Notification_for_appointment:?= =?UTF-8?Q?_Test?=") ); // missing space after ':' QCOMPARE( extractHeader( "From:", "From" ), QByteArray( "" ) ); } diff --git a/kmime/tests/kmime_util_test.h b/kmime/tests/kmime_util_test.h index 4f9cd9bb6..04c5ceabe 100644 --- a/kmime/tests/kmime_util_test.h +++ b/kmime/tests/kmime_util_test.h @@ -1,33 +1,33 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library 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. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMIME_UTIL_TEST_H #define KMIME_UTIL_TEST_H #include class KMimeUtilTest : public QObject { Q_OBJECT private Q_SLOTS: void testUnfoldHeader(); void testExtractHeader(); }; #endif diff --git a/kmime/tests/rfc2047test.cpp b/kmime/tests/rfc2047test.cpp index 3bf968740..27ff7e16f 100644 --- a/kmime/tests/rfc2047test.cpp +++ b/kmime/tests/rfc2047test.cpp @@ -1,108 +1,108 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library 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. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "rfc2047test.h" #include "rfc2047test.moc" #include using namespace KMime; QTEST_KDEMAIN( RFC2047Test, NoGUI ) void RFC2047Test::testRFC2047decode() { QByteArray encCharset; // empty QCOMPARE( KMime::decodeRFC2047String( QByteArray(), encCharset, "utf-8", false ), QString() ); // identity QCOMPARE( KMime::decodeRFC2047String( "bla", encCharset, "utf-8", false ), QString( "bla" ) ); // utf-8 QCOMPARE( KMime::decodeRFC2047String( "=?utf-8?q?Ingo=20Kl=C3=B6cker?= ", encCharset, "utf-8", false ), QString::fromUtf8( "Ingo Klöcker " ) ); QCOMPARE( KMime::decodeRFC2047String( "=?utf-8?q?Ingo=20Kl=C3=B6cker?= ", encCharset, "iso8859-1", false ), QString::fromUtf8( "Ingo Klöcker " ) ); QCOMPARE( KMime::decodeRFC2047String( "=?utf-8?q?Ingo=20Kl=C3=B6cker?=", encCharset, "utf-8", false ), QString::fromUtf8( "Ingo Klöcker" ) ); QCOMPARE( encCharset, QByteArray( "UTF-8" ) ); // whitespaces between two encoded words QCOMPARE( KMime::decodeRFC2047String( "=?utf-8?q?Ingo=20Kl=C3=B6cker?= =?utf-8?q?Ingo=20Kl=C3=B6cker?=", encCharset, "utf-8", false ), QString::fromUtf8( "Ingo KlöckerIngo Klöcker" ) ); QCOMPARE( decodeRFC2047String( "=?utf-8?q?Ingo=20Kl=C3=B6cker?= foo =?utf-8?q?Ingo=20Kl=C3=B6cker?=", encCharset ), QString::fromUtf8( "Ingo Klöcker foo Ingo Klöcker" ) ); // iso-8859-x QCOMPARE( KMime::decodeRFC2047String( "=?ISO-8859-1?Q?Andr=E9s_Ot=F3n?=", encCharset, "utf-8", false ), QString::fromUtf8( "Andrés Otón" ) ); QCOMPARE( encCharset, QByteArray( "ISO-8859-1" ) ); QCOMPARE( KMime::decodeRFC2047String( "=?iso-8859-2?q?Rafa=B3_Rzepecki?=", encCharset, "utf-8", false ), QString::fromUtf8( "Rafał Rzepecki" ) ); QCOMPARE( encCharset, QByteArray( "ISO-8859-2" ) ); QCOMPARE( KMime::decodeRFC2047String( "=?iso-8859-9?Q?S=2E=C7a=F0lar?= Onur", encCharset, "utf-8", false ), QString::fromUtf8( "S.Çağlar Onur" ) ); QCOMPARE( encCharset, QByteArray( "ISO-8859-9" ) ); QCOMPARE( KMime::decodeRFC2047String( "Rafael =?iso-8859-15?q?Rodr=EDguez?=", encCharset, "utf-8", false ), QString::fromUtf8( "Rafael Rodríguez" ) ); QCOMPARE( encCharset, QByteArray( "ISO-8859-15" ) ); // wrong charset + charset overwrite QCOMPARE( KMime::decodeRFC2047String( "=?iso-8859-1?q?Ingo=20Kl=C3=B6cker?=", encCharset, "utf-8", true ), QString::fromUtf8( "Ingo Klöcker" ) ); // language parameter according to RFC 2231, section 5 QCOMPARE( decodeRFC2047String( "From: =?US-ASCII*EN?Q?Keith_Moore?= ", encCharset ), QString::fromUtf8( "From: Keith Moore " ) ); QCOMPARE( encCharset, QByteArray( "US-ASCII" ) ); // broken qp endoding (using lowercase) QCOMPARE( decodeRFC2047String( "Subject: =?iso-8859-1?Q?Belangrijk=3a=20Verhuizing=20FTP=20server?=", encCharset ), QString::fromUtf8( "Subject: Belangrijk: Verhuizing FTP server" ) ); QCOMPARE( encCharset, QByteArray( "ISO-8859-1" ) ); // mixed charsets, based on bug 125542 but pasted from above instead since I'm unable to enter those asian symbols QCOMPARE( decodeRFC2047String( "Subject: =?utf-8?q?Ingo=20Kl=C3=B6cker?= unencoded words =?iso-8859-9?Q?S=2E=C7a=F0lar?=", encCharset ), QString::fromUtf8( "Subject: Ingo Klöcker unencoded words S.Çağlar" ) ); QCOMPARE( encCharset, QByteArray( "ISO-8859-9" ) ); } void RFC2047Test::testInvalidDecode() { QByteArray encCharset; // invalid / incomplete encoded data QCOMPARE( decodeRFC2047String( "=", encCharset ), QString::fromUtf8("=") ); QCOMPARE( decodeRFC2047String( "=?", encCharset ), QString::fromUtf8("=?") ); QCOMPARE( decodeRFC2047String( "=?a?b?=", encCharset ), QString::fromUtf8("=?a?b?=") ); QCOMPARE( decodeRFC2047String( "=?a?b?c?", encCharset ), QString::fromUtf8("=?a?b?c?") ); QCOMPARE( decodeRFC2047String( "=?a??c?=", encCharset ), QString::fromUtf8("=?a??c?=") ); } void RFC2047Test::testRFC2047encode() { // empty QCOMPARE( KMime::encodeRFC2047String( QString(), "utf-8" ), QByteArray() ); // identity QCOMPARE( KMime::encodeRFC2047String( "bla", "utf-8" ), QByteArray( "bla" ) ); // utf-8 // expected value is probably wrong, libkmime will chose 'B' instead of 'Q' encoding QEXPECT_FAIL( "", "libkmime will chose 'B' instead of 'Q' encoding", Continue ); QCOMPARE( KMime::encodeRFC2047String( QString::fromUtf8( "Ingo Klöcker " ), "utf-8" ).constData(), "=?utf-8?q?Ingo=20Kl=C3=B6cker?= " ); } diff --git a/kmime/tests/rfc2047test.h b/kmime/tests/rfc2047test.h index 1a017761b..415075fe7 100644 --- a/kmime/tests/rfc2047test.h +++ b/kmime/tests/rfc2047test.h @@ -1,34 +1,34 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library 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. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef RFC2047TEST_H #define RFC2047TEST_H #include class RFC2047Test : public QObject { Q_OBJECT private Q_SLOTS: void testRFC2047decode(); void testInvalidDecode(); void testRFC2047encode(); }; #endif diff --git a/kresources/tests/idmappertest.h b/kresources/tests/idmappertest.h index eaafa883c..35a757a12 100644 --- a/kresources/tests/idmappertest.h +++ b/kresources/tests/idmappertest.h @@ -1,32 +1,32 @@ /* - Copyright (c) 2006 Volker Krause + Copyright (c) 2006 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef IDMAPPERTEST_H #define IDMAPPERTEST_H #include class IdMapperTest : public QObject { Q_OBJECT private Q_SLOTS: void testIdMapper(); }; #endif