From 84498d97e27f79e7e919b42bbe54208a02856aaf Mon Sep 17 00:00:00 2001 From: Michele Calgaro Date: Sun, 6 Dec 2020 21:23:48 +0900 Subject: Renaming of files in preparation for code style tools. Signed-off-by: Michele Calgaro (cherry picked from commit 4f99f868f09bbffa2e15733b8b7c78eba07a199e) --- tdeioslave/file/CMakeLists.txt | 2 +- tdeioslave/file/Makefile.am | 2 +- tdeioslave/file/file.cc | 1889 ---------- tdeioslave/file/file.cpp | 1889 ++++++++++ tdeioslave/ftp/CMakeLists.txt | 2 +- tdeioslave/ftp/Makefile.am | 2 +- tdeioslave/ftp/ftp.cc | 2674 -------------- tdeioslave/ftp/ftp.cpp | 2674 ++++++++++++++ tdeioslave/http/CMakeLists.txt | 2 +- tdeioslave/http/Makefile.am | 2 +- tdeioslave/http/README.webdav | 2 +- tdeioslave/http/http.cc | 6131 -------------------------------- tdeioslave/http/http.cpp | 6131 ++++++++++++++++++++++++++++++++ tdeioslave/http/http_cache_cleaner.cpp | 4 +- tdeioslave/iso/Makefile.am | 2 +- tdeioslave/iso/iso.cpp | 2 +- 16 files changed, 10705 insertions(+), 10705 deletions(-) delete mode 100644 tdeioslave/file/file.cc create mode 100644 tdeioslave/file/file.cpp delete mode 100644 tdeioslave/ftp/ftp.cc create mode 100644 tdeioslave/ftp/ftp.cpp delete mode 100644 tdeioslave/http/http.cc create mode 100644 tdeioslave/http/http.cpp (limited to 'tdeioslave') diff --git a/tdeioslave/file/CMakeLists.txt b/tdeioslave/file/CMakeLists.txt index 6456e962b..0215e1522 100644 --- a/tdeioslave/file/CMakeLists.txt +++ b/tdeioslave/file/CMakeLists.txt @@ -44,7 +44,7 @@ tde_create_translated_desktop( set( target tdeio_file ) set( ${target}_SRCS - file.cc + file.cpp ) tde_add_kpart( ${target} AUTOMOC diff --git a/tdeioslave/file/Makefile.am b/tdeioslave/file/Makefile.am index ac3a2094d..6c3f929a6 100644 --- a/tdeioslave/file/Makefile.am +++ b/tdeioslave/file/Makefile.am @@ -8,7 +8,7 @@ INCLUDES = $(all_includes) kde_module_LTLIBRARIES = tdeio_file.la -tdeio_file_la_SOURCES = file.cc +tdeio_file_la_SOURCES = file.cpp tdeio_file_la_LIBADD = $(LIB_TDEIO) $(LIB_QT) $(LIB_TDECORE) $(ACL_LIBS) tdeio_file_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) $(top_builddir)/dcop/libDCOP.la noinst_HEADERS = file.h diff --git a/tdeioslave/file/file.cc b/tdeioslave/file/file.cc deleted file mode 100644 index ad1cc5574..000000000 --- a/tdeioslave/file/file.cc +++ /dev/null @@ -1,1889 +0,0 @@ -/* - Copyright (C) 2000-2002 Stephan Kulow - Copyright (C) 2000-2002 David Faure - Copyright (C) 2000-2002 Waldo Bastian - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License (LGPL) 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. -*/ - -// $Id$ - -#include - -#include //for Q_OS_XXX -#include -#include -#include -#ifdef HAVE_SYS_TIME_H -#include -#endif - -//sendfile has different semantics in different platforms -#if defined HAVE_SENDFILE && defined Q_OS_LINUX -#define USE_SENDFILE 1 -#endif - -#ifdef USE_SENDFILE -#include -#endif - -#ifdef USE_POSIX_ACL -#include -#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS -#include -#else -#include -#endif -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_STRING_H -#include -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "file.h" -#include -#include -#include -#include - -#ifdef HAVE_VOLMGT -#include -#include -#endif - -#include -#include -#include -#include -#include - -using namespace TDEIO; - -#define MAX_IPC_SIZE (1024*32) - -static TQString testLogFile( const char *_filename ); -#ifdef USE_POSIX_ACL -static TQString aclAsString( acl_t p_acl ); -static bool isExtendedACL( acl_t p_acl ); -static void appendACLAtoms( const TQCString & path, UDSEntry& entry, - mode_t type, bool withACL ); -#endif - -extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } - -int kdemain( int argc, char **argv ) -{ - TDELocale::setMainCatalogue("tdelibs"); - TDEInstance instance( "tdeio_file" ); - ( void ) TDEGlobal::locale(); - - kdDebug(7101) << "Starting " << getpid() << endl; - - if (argc != 4) - { - fprintf(stderr, "Usage: tdeio_file protocol domain-socket1 domain-socket2\n"); - exit(-1); - } - - FileProtocol slave(argv[2], argv[3]); - slave.dispatchLoop(); - - kdDebug(7101) << "Done" << endl; - return 0; -} - - -FileProtocol::FileProtocol( const TQCString &pool, const TQCString &app ) : SlaveBase( "file", pool, app ) -{ - usercache.setAutoDelete( true ); - groupcache.setAutoDelete( true ); -} - - -int FileProtocol::setACL( const char *path, mode_t perm, bool directoryDefault ) -{ - int ret = 0; -#ifdef USE_POSIX_ACL - - const TQString ACLString = metaData( "ACL_STRING" ); - const TQString defaultACLString = metaData( "DEFAULT_ACL_STRING" ); - // Empty strings mean leave as is - if ( !ACLString.isEmpty() ) { - acl_t acl = 0; - if ( ACLString == "ACL_DELETE" ) { - // user told us to delete the extended ACL, so let's write only - // the minimal (UNIX permission bits) part - acl = acl_from_mode( perm ); - } - acl = acl_from_text( ACLString.latin1() ); - if ( acl_valid( acl ) == 0 ) { // let's be safe - ret = acl_set_file( path, ACL_TYPE_ACCESS, acl ); - kdDebug(7101) << "Set ACL on: " << path << " to: " << aclAsString( acl ) << endl; - } - acl_free( acl ); - if ( ret != 0 ) return ret; // better stop trying right away - } - - if ( directoryDefault && !defaultACLString.isEmpty() ) { - if ( defaultACLString == "ACL_DELETE" ) { - // user told us to delete the default ACL, do so - ret += acl_delete_def_file( path ); - } else { - acl_t acl = acl_from_text( defaultACLString.latin1() ); - if ( acl_valid( acl ) == 0 ) { // let's be safe - ret += acl_set_file( path, ACL_TYPE_DEFAULT, acl ); - kdDebug(7101) << "Set Default ACL on: " << path << " to: " << aclAsString( acl ) << endl; - } - acl_free( acl ); - } - } -#endif - return ret; -} - -void FileProtocol::chmod( const KURL& url, int permissions ) -{ - TQCString _path( TQFile::encodeName(url.path()) ); - /* FIXME: Should be atomic */ - if ( ::chmod( _path.data(), permissions ) == -1 || - ( setACL( _path.data(), permissions, false ) == -1 ) || - /* if not a directory, cannot set default ACLs */ - ( setACL( _path.data(), permissions, true ) == -1 && errno != ENOTDIR ) ) { - - switch (errno) { - case EPERM: - case EACCES: - error( TDEIO::ERR_ACCESS_DENIED, url.path() ); - break; - case ENOTSUP: - error( TDEIO::ERR_UNSUPPORTED_ACTION, url.path() ); - break; - case ENOSPC: - error( TDEIO::ERR_DISK_FULL, url.path() ); - break; - default: - error( TDEIO::ERR_CANNOT_CHMOD, url.path() ); - } - } else - finished(); -} - -void FileProtocol::mkdir( const KURL& url, int permissions ) -{ - TQCString _path( TQFile::encodeName(url.path())); - - kdDebug(7101) << "mkdir(): " << _path << ", permission = " << permissions << endl; - - KDE_struct_stat buff; - if ( KDE_stat( _path.data(), &buff ) == -1 ) { - if ( ::mkdir( _path.data(), 0777 /*umask will be applied*/ ) != 0 ) { - if ( errno == EACCES ) { - error( TDEIO::ERR_ACCESS_DENIED, url.path() ); - return; - } else if ( errno == ENOSPC ) { - error( TDEIO::ERR_DISK_FULL, url.path() ); - return; - } else { - error( TDEIO::ERR_COULD_NOT_MKDIR, url.path() ); - return; - } - } else { - if ( permissions != -1 ) - chmod( url, permissions ); - else - finished(); - return; - } - } - - if ( S_ISDIR( buff.st_mode ) ) { - kdDebug(7101) << "ERR_DIR_ALREADY_EXIST" << endl; - error( TDEIO::ERR_DIR_ALREADY_EXIST, url.path() ); - return; - } - error( TDEIO::ERR_FILE_ALREADY_EXIST, url.path() ); - return; -} - -void FileProtocol::get( const KURL& url ) -{ - if (!url.isLocalFile()) { - KURL redir(url); - redir.setProtocol(config()->readEntry("DefaultRemoteProtocol", "smb")); - redirection(redir); - finished(); - return; - } - - TQCString _path( TQFile::encodeName(url.path())); - KDE_struct_stat buff; - if ( KDE_stat( _path.data(), &buff ) == -1 ) { - if ( errno == EACCES ) - error( TDEIO::ERR_ACCESS_DENIED, url.path() ); - else - error( TDEIO::ERR_DOES_NOT_EXIST, url.path() ); - return; - } - - if ( S_ISDIR( buff.st_mode ) ) { - error( TDEIO::ERR_IS_DIRECTORY, url.path() ); - return; - } - if ( !S_ISREG( buff.st_mode ) ) { - error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.path() ); - return; - } - - int fd = KDE_open( _path.data(), O_RDONLY); - if ( fd < 0 ) { - error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.path() ); - return; - } - -#ifdef HAVE_FADVISE - posix_fadvise( fd, 0, 0, POSIX_FADV_SEQUENTIAL); -#endif - - // Determine the mimetype of the file to be retrieved, and emit it. - // This is mandatory in all slaves (for KRun/BrowserRun to work). - KMimeType::Ptr mt = KMimeType::findByURL( url, buff.st_mode, true /* local URL */ ); - emit mimeType( mt->name() ); - - TDEIO::filesize_t processed_size = 0; - - TQString resumeOffset = metaData("resume"); - if ( !resumeOffset.isEmpty() ) - { - bool ok; - TDEIO::fileoffset_t offset = resumeOffset.toLongLong(&ok); - if (ok && (offset > 0) && (offset < buff.st_size)) - { - if (KDE_lseek(fd, offset, SEEK_SET) == offset) - { - canResume (); - processed_size = offset; - kdDebug( 7101 ) << "Resume offset: " << TDEIO::number(offset) << endl; - } - } - } - - totalSize( buff.st_size ); - - char buffer[ MAX_IPC_SIZE ]; - TQByteArray array; - - while( 1 ) - { - int n = ::read( fd, buffer, MAX_IPC_SIZE ); - if (n == -1) - { - if (errno == EINTR) - continue; - error( TDEIO::ERR_COULD_NOT_READ, url.path()); - close(fd); - return; - } - if (n == 0) - break; // Finished - - array.setRawData(buffer, n); - data( array ); - array.resetRawData(buffer, n); - - processed_size += n; - processedSize( processed_size ); - - //kdDebug( 7101 ) << "Processed: " << TDEIO::number (processed_size) << endl; - } - - data( TQByteArray() ); - - close( fd ); - - processedSize( buff.st_size ); - finished(); -} - -static int -write_all(int fd, const char *buf, size_t len) -{ - while (len > 0) - { - ssize_t written = write(fd, buf, len); - if (written < 0) - { - if (errno == EINTR) - continue; - return -1; - } - buf += written; - len -= written; - } - return 0; -} - -static bool -same_inode(const KDE_struct_stat &src, const KDE_struct_stat &dest) -{ - if (src.st_ino == dest.st_ino && - src.st_dev == dest.st_dev) - return true; - - return false; -} - -void FileProtocol::put( const KURL& url, int _mode, bool _overwrite, bool _resume ) -{ - TQString dest_orig = url.path(); - TQCString _dest_orig( TQFile::encodeName(dest_orig)); - - kdDebug(7101) << "put(): " << dest_orig << ", mode=" << _mode << endl; - - TQString dest_part( dest_orig ); - dest_part += TQString::fromLatin1(".part"); - TQCString _dest_part( TQFile::encodeName(dest_part)); - - KDE_struct_stat buff_orig; - bool bOrigExists = (KDE_lstat( _dest_orig.data(), &buff_orig ) != -1); - bool bPartExists = false; - bool bMarkPartial = config()->readBoolEntry("MarkPartial", true); - - if (bMarkPartial) - { - KDE_struct_stat buff_part; - bPartExists = (KDE_stat( _dest_part.data(), &buff_part ) != -1); - - if (bPartExists && !_resume && !_overwrite && buff_part.st_size > 0 && S_ISREG(buff_part.st_mode)) - { - kdDebug(7101) << "FileProtocol::put : calling canResume with " - << TDEIO::number(buff_part.st_size) << endl; - - // Maybe we can use this partial file for resuming - // Tell about the size we have, and the app will tell us - // if it's ok to resume or not. - _resume = canResume( buff_part.st_size ); - - kdDebug(7101) << "FileProtocol::put got answer " << _resume << endl; - } - } - - if ( bOrigExists && !_overwrite && !_resume) - { - if (S_ISDIR(buff_orig.st_mode)) - error( TDEIO::ERR_DIR_ALREADY_EXIST, dest_orig ); - else - error( TDEIO::ERR_FILE_ALREADY_EXIST, dest_orig ); - return; - } - - int result; - TQString dest; - TQCString _dest; - - int fd = -1; - - // Loop until we got 0 (end of data) - do - { - TQByteArray buffer; - dataReq(); // Request for data - result = readData( buffer ); - - if (result >= 0) - { - if (dest.isEmpty()) - { - if (bMarkPartial) - { - kdDebug(7101) << "Appending .part extension to " << dest_orig << endl; - dest = dest_part; - if ( bPartExists && !_resume ) - { - kdDebug(7101) << "Deleting partial file " << dest_part << endl; - remove( _dest_part.data() ); - // Catch errors when we try to open the file. - } - } - else - { - dest = dest_orig; - if ( bOrigExists && !_resume ) - { - kdDebug(7101) << "Deleting destination file " << dest_orig << endl; - remove( _dest_orig.data() ); - // Catch errors when we try to open the file. - } - } - - _dest = TQFile::encodeName(dest); - - if ( _resume ) - { - fd = KDE_open( _dest.data(), O_RDWR ); // append if resuming - KDE_lseek(fd, 0, SEEK_END); // Seek to end - } - else - { - // WABA: Make sure that we keep writing permissions ourselves, - // otherwise we can be in for a surprise on NFS. - mode_t initialMode; - if (_mode != -1) - initialMode = _mode | S_IWUSR | S_IRUSR; - else - initialMode = 0666; - - fd = KDE_open(_dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); - } - - if ( fd < 0 ) - { - kdDebug(7101) << "####################### COULD NOT WRITE " << dest << " _mode=" << _mode << endl; - kdDebug(7101) << "errno==" << errno << "(" << strerror(errno) << ")" << endl; - if ( errno == EACCES ) - error( TDEIO::ERR_WRITE_ACCESS_DENIED, dest ); - else - error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, dest ); - return; - } - } - - if (write_all( fd, buffer.data(), buffer.size())) - { - if ( errno == ENOSPC ) // disk full - { - error( TDEIO::ERR_DISK_FULL, dest_orig); - result = -2; // means: remove dest file - } - else - { - kdWarning(7101) << "Couldn't write. Error:" << strerror(errno) << endl; - error( TDEIO::ERR_COULD_NOT_WRITE, dest_orig); - result = -1; - } - } - } - } - while ( result > 0 ); - - // An error occurred deal with it. - if (result < 0) - { - kdDebug(7101) << "Error during 'put'. Aborting." << endl; - - if (fd != -1) - { - close(fd); - - KDE_struct_stat buff; - if (bMarkPartial && KDE_stat( _dest.data(), &buff ) == 0) - { - int size = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); - if (buff.st_size < size) - remove(_dest.data()); - } - } - - ::exit(255); - } - - if ( fd == -1 ) // we got nothing to write out, so we never opened the file - { - finished(); - return; - } - - if ( close(fd) ) - { - kdWarning(7101) << "Error when closing file descriptor:" << strerror(errno) << endl; - error( TDEIO::ERR_COULD_NOT_WRITE, dest_orig); - return; - } - - // after full download rename the file back to original name - if ( bMarkPartial ) - { - // If the original URL is a symlink and we were asked to overwrite it, - // remove the symlink first. This ensures that we do not overwrite the - // current source if the symlink points to it. - if( _overwrite && S_ISLNK( buff_orig.st_mode ) ) - remove( _dest_orig.data() ); - - if ( ::rename( _dest.data(), _dest_orig.data() ) ) - { - kdWarning(7101) << " Couldn't rename " << _dest << " to " << _dest_orig << endl; - error( TDEIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig ); - return; - } - } - - // set final permissions - if ( _mode != -1 && !_resume ) - { - if (::chmod(_dest_orig.data(), _mode) != 0) - { - // couldn't chmod. Eat the error if the filesystem apparently doesn't support it. - if ( TDEIO::testFileSystemFlag( _dest_orig, TDEIO::SupportsChmod ) ) - warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) ); - } - } - - // set modification time - const TQString mtimeStr = metaData( "modified" ); - if ( !mtimeStr.isEmpty() ) { - TQDateTime dt = TQT_TQDATETIME_OBJECT(TQDateTime::fromString( mtimeStr, Qt::ISODate )); - if ( dt.isValid() ) { - KDE_struct_stat dest_statbuf; - if (KDE_stat( _dest_orig.data(), &dest_statbuf ) == 0) { - struct utimbuf utbuf; - utbuf.actime = dest_statbuf.st_atime; // access time, unchanged - utbuf.modtime = dt.toTime_t(); // modification time - kdDebug() << k_funcinfo << "setting modtime to " << utbuf.modtime << endl; - utime( _dest_orig.data(), &utbuf ); - } - } - - } - - // We have done our job => finish - finished(); -} - - -void FileProtocol::copy( const KURL &src, const KURL &dest, - int _mode, bool _overwrite ) -{ - kdDebug(7101) << "copy(): " << src << " -> " << dest << ", mode=" << _mode << endl; - - TQCString _src( TQFile::encodeName(src.path())); - TQCString _dest( TQFile::encodeName(dest.path())); - KDE_struct_stat buff_src; -#ifdef USE_POSIX_ACL - acl_t acl; -#endif - - if ( KDE_stat( _src.data(), &buff_src ) == -1 ) { - if ( errno == EACCES ) - error( TDEIO::ERR_ACCESS_DENIED, src.path() ); - else - error( TDEIO::ERR_DOES_NOT_EXIST, src.path() ); - return; - } - - if ( S_ISDIR( buff_src.st_mode ) ) { - error( TDEIO::ERR_IS_DIRECTORY, src.path() ); - return; - } - if ( S_ISFIFO( buff_src.st_mode ) || S_ISSOCK ( buff_src.st_mode ) ) { - error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.path() ); - return; - } - - KDE_struct_stat buff_dest; - bool dest_exists = ( KDE_lstat( _dest.data(), &buff_dest ) != -1 ); - if ( dest_exists ) - { - if (S_ISDIR(buff_dest.st_mode)) - { - error( TDEIO::ERR_DIR_ALREADY_EXIST, dest.path() ); - return; - } - - if ( same_inode( buff_dest, buff_src) ) - { - error( TDEIO::ERR_IDENTICAL_FILES, dest.path() ); - return; - } - - if (!_overwrite) - { - error( TDEIO::ERR_FILE_ALREADY_EXIST, dest.path() ); - return; - } - - // If the destination is a symlink and overwrite is TRUE, - // remove the symlink first to prevent the scenario where - // the symlink actually points to current source! - if (_overwrite && S_ISLNK(buff_dest.st_mode)) - { - kdDebug(7101) << "copy(): LINK DESTINATION" << endl; - remove( _dest.data() ); - } - } - - int src_fd = KDE_open( _src.data(), O_RDONLY); - if ( src_fd < 0 ) { - error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.path() ); - return; - } - -#ifdef HAVE_FADVISE - posix_fadvise(src_fd,0,0,POSIX_FADV_SEQUENTIAL); -#endif - // WABA: Make sure that we keep writing permissions ourselves, - // otherwise we can be in for a surprise on NFS. - mode_t initialMode; - if (_mode != -1) - initialMode = _mode | S_IWUSR; - else - initialMode = 0666; - - int dest_fd = KDE_open(_dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); - if ( dest_fd < 0 ) { - kdDebug(7101) << "###### COULD NOT WRITE " << dest.url() << endl; - if ( errno == EACCES ) { - error( TDEIO::ERR_WRITE_ACCESS_DENIED, dest.path() ); - } else { - error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, dest.path() ); - } - close(src_fd); - return; - } - -#ifdef HAVE_FADVISE - posix_fadvise(dest_fd,0,0,POSIX_FADV_SEQUENTIAL); -#endif - -#ifdef USE_POSIX_ACL - acl = acl_get_fd(src_fd); - if ( acl && !isExtendedACL( acl ) ) { - kdDebug(7101) << _dest.data() << " doesn't have extended ACL" << endl; - acl_free( acl ); - acl = NULL; - } -#endif - totalSize( buff_src.st_size ); - - TDEIO::filesize_t processed_size = 0; - char buffer[ MAX_IPC_SIZE ]; - int n; -#ifdef USE_SENDFILE - bool use_sendfile=buff_src.st_size < 0x7FFFFFFF; -#endif - while( 1 ) - { -#ifdef USE_SENDFILE - if (use_sendfile) { - off_t sf = processed_size; - n = ::sendfile( dest_fd, src_fd, &sf, MAX_IPC_SIZE ); - processed_size = sf; - if ( n == -1 && errno == EINVAL ) { //not all filesystems support sendfile() - kdDebug(7101) << "sendfile() not supported, falling back " << endl; - use_sendfile = false; - } - } - if (!use_sendfile) -#endif - n = ::read( src_fd, buffer, MAX_IPC_SIZE ); - - if (n == -1) - { - if (errno == EINTR) - continue; -#ifdef USE_SENDFILE - if ( use_sendfile ) { - kdDebug(7101) << "sendfile() error:" << strerror(errno) << endl; - if ( errno == ENOSPC ) // disk full - { - error( TDEIO::ERR_DISK_FULL, dest.path()); - remove( _dest.data() ); - } - else { - error( TDEIO::ERR_SLAVE_DEFINED, - i18n("Cannot copy file from %1 to %2. (Errno: %3)") - .arg( src.path() ).arg( dest.path() ).arg( errno ) ); - } - } else -#endif - error( TDEIO::ERR_COULD_NOT_READ, src.path()); - close(src_fd); - close(dest_fd); -#ifdef USE_POSIX_ACL - if (acl) acl_free(acl); -#endif - return; - } - if (n == 0) - break; // Finished -#ifdef USE_SENDFILE - if ( !use_sendfile ) { -#endif - if (write_all( dest_fd, buffer, n)) - { - close(src_fd); - close(dest_fd); - - if ( errno == ENOSPC ) // disk full - { - error( TDEIO::ERR_DISK_FULL, dest.path()); - remove( _dest.data() ); - } - else - { - kdWarning(7101) << "Couldn't write[2]. Error:" << strerror(errno) << endl; - error( TDEIO::ERR_COULD_NOT_WRITE, dest.path()); - } -#ifdef USE_POSIX_ACL - if (acl) acl_free(acl); -#endif - return; - } - processed_size += n; -#ifdef USE_SENDFILE - } -#endif - processedSize( processed_size ); - } - - close( src_fd ); - - if (close( dest_fd)) - { - kdWarning(7101) << "Error when closing file descriptor[2]:" << strerror(errno) << endl; - error( TDEIO::ERR_COULD_NOT_WRITE, dest.path()); -#ifdef USE_POSIX_ACL - if (acl) acl_free(acl); -#endif - return; - } - - // set final permissions - if ( _mode != -1 ) - { - if ( (::chmod(_dest.data(), _mode) != 0) -#ifdef USE_POSIX_ACL - || (acl && acl_set_file(_dest.data(), ACL_TYPE_ACCESS, acl) != 0) -#endif - ) - { - // Eat the error if the filesystem apparently doesn't support chmod. - if ( TDEIO::testFileSystemFlag( _dest, TDEIO::SupportsChmod ) ) - warning( i18n( "Could not change permissions for\n%1" ).arg( dest.path() ) ); - } - } -#ifdef USE_POSIX_ACL - if (acl) acl_free(acl); -#endif - - // copy access and modification time - struct utimbuf ut; - ut.actime = buff_src.st_atime; - ut.modtime = buff_src.st_mtime; - if ( ::utime( _dest.data(), &ut ) != 0 ) - { - kdWarning() << TQString(TQString::fromLatin1("Couldn't preserve access and modification time for\n%1").arg( dest.path() )) << endl; - } - - processedSize( buff_src.st_size ); - finished(); -} - -void FileProtocol::rename( const KURL &src, const KURL &dest, - bool _overwrite ) -{ - TQCString _src( TQFile::encodeName(src.path())); - TQCString _dest( TQFile::encodeName(dest.path())); - KDE_struct_stat buff_src; - if ( KDE_lstat( _src.data(), &buff_src ) == -1 ) { - if ( errno == EACCES ) - error( TDEIO::ERR_ACCESS_DENIED, src.path() ); - else - error( TDEIO::ERR_DOES_NOT_EXIST, src.path() ); - return; - } - - KDE_struct_stat buff_dest; - bool dest_exists = ( KDE_stat( _dest.data(), &buff_dest ) != -1 ); - if ( dest_exists ) - { - if (S_ISDIR(buff_dest.st_mode)) - { - error( TDEIO::ERR_DIR_ALREADY_EXIST, dest.path() ); - return; - } - - if ( same_inode( buff_dest, buff_src) ) - { - error( TDEIO::ERR_IDENTICAL_FILES, dest.path() ); - return; - } - - if (!_overwrite) - { - error( TDEIO::ERR_FILE_ALREADY_EXIST, dest.path() ); - return; - } - } - - if ( ::rename( _src.data(), _dest.data())) - { - if (( errno == EACCES ) || (errno == EPERM)) { - error( TDEIO::ERR_ACCESS_DENIED, dest.path() ); - } - else if (errno == EXDEV) { - error( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("rename")); - } - else if (errno == EROFS) { // The file is on a read-only filesystem - error( TDEIO::ERR_CANNOT_DELETE, src.path() ); - } - else { - error( TDEIO::ERR_CANNOT_RENAME, src.path() ); - } - return; - } - - finished(); -} - -void FileProtocol::symlink( const TQString &target, const KURL &dest, bool overwrite ) -{ - // Assume dest is local too (wouldn't be here otherwise) - if ( ::symlink( TQFile::encodeName( target ), TQFile::encodeName( dest.path() ) ) == -1 ) - { - // Does the destination already exist ? - if ( errno == EEXIST ) - { - if ( overwrite ) - { - // Try to delete the destination - if ( unlink( TQFile::encodeName( dest.path() ) ) != 0 ) - { - error( TDEIO::ERR_CANNOT_DELETE, dest.path() ); - return; - } - // Try again - this won't loop forever since unlink succeeded - symlink( target, dest, overwrite ); - } - else - { - KDE_struct_stat buff_dest; - KDE_lstat( TQFile::encodeName( dest.path() ), &buff_dest ); - if (S_ISDIR(buff_dest.st_mode)) - error( TDEIO::ERR_DIR_ALREADY_EXIST, dest.path() ); - else - error( TDEIO::ERR_FILE_ALREADY_EXIST, dest.path() ); - return; - } - } - else - { - // Some error occurred while we tried to symlink - error( TDEIO::ERR_CANNOT_SYMLINK, dest.path() ); - return; - } - } - finished(); -} - -void FileProtocol::del( const KURL& url, bool isfile) -{ - TQCString _path( TQFile::encodeName(url.path())); - /***** - * Delete files - *****/ - - if (isfile) { - kdDebug( 7101 ) << "Deleting file "<< url.url() << endl; - - // TODO deletingFile( source ); - - if ( unlink( _path.data() ) == -1 ) { - if ((errno == EACCES) || (errno == EPERM)) - error( TDEIO::ERR_ACCESS_DENIED, url.path()); - else if (errno == EISDIR) - error( TDEIO::ERR_IS_DIRECTORY, url.path()); - else - error( TDEIO::ERR_CANNOT_DELETE, url.path() ); - return; - } - } else { - - /***** - * Delete empty directory - *****/ - - kdDebug( 7101 ) << "Deleting directory " << url.url() << endl; - - if ( ::rmdir( _path.data() ) == -1 ) { - if ((errno == EACCES) || (errno == EPERM)) - error( TDEIO::ERR_ACCESS_DENIED, url.path()); - else { - kdDebug( 7101 ) << "could not rmdir " << perror << endl; - error( TDEIO::ERR_COULD_NOT_RMDIR, url.path() ); - return; - } - } - } - - finished(); -} - - -TQString FileProtocol::getUserName( uid_t uid ) -{ - TQString *temp; - temp = usercache.find( uid ); - if ( !temp ) { - struct passwd *user = getpwuid( uid ); - if ( user ) { - usercache.insert( uid, new TQString(TQString::fromLatin1(user->pw_name)) ); - return TQString::fromLatin1( user->pw_name ); - } - else - return TQString::number( uid ); - } - else - return *temp; -} - -TQString FileProtocol::getGroupName( gid_t gid ) -{ - TQString *temp; - temp = groupcache.find( gid ); - if ( !temp ) { - struct group *grp = getgrgid( gid ); - if ( grp ) { - groupcache.insert( gid, new TQString(TQString::fromLatin1(grp->gr_name)) ); - return TQString::fromLatin1( grp->gr_name ); - } - else - return TQString::number( gid ); - } - else - return *temp; -} - - - -bool FileProtocol::createUDSEntry( const TQString & filename, const TQCString & path, UDSEntry & entry, - short int details, bool withACL ) -{ - assert(entry.count() == 0); // by contract :-) - // Note: details = 0 (only "file or directory or symlink or doesn't exist") isn't implemented - // because there's no real performance penalty in tdeio_file for returning the complete - // details. Please consider doing it in your tdeioslave if you're using this one as a model :) - UDSAtom atom; - atom.m_uds = TDEIO::UDS_NAME; - atom.m_str = filename; - entry.append( atom ); - - mode_t type; - mode_t access; - KDE_struct_stat buff; - - if ( KDE_lstat( path.data(), &buff ) == 0 ) { - - if (S_ISLNK(buff.st_mode)) { - - char buffer2[ 1000 ]; - int n = readlink( path.data(), buffer2, 1000 ); - if ( n != -1 ) { - buffer2[ n ] = 0; - } - - atom.m_uds = TDEIO::UDS_LINK_DEST; - atom.m_str = TQFile::decodeName( buffer2 ); - entry.append( atom ); - - // A symlink -> follow it only if details>1 - if ( details > 1 && KDE_stat( path.data(), &buff ) == -1 ) { - // It is a link pointing to nowhere - type = S_IFMT - 1; - access = S_IRWXU | S_IRWXG | S_IRWXO; - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = type; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = access; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_SIZE; - atom.m_long = 0L; - entry.append( atom ); - - goto notype; - - } - } - } else { - // kdWarning() << "lstat didn't work on " << path.data() << endl; - return false; - } - - type = buff.st_mode & S_IFMT; // extract file type - access = buff.st_mode & 07777; // extract permissions - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = type; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = access; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_SIZE; - atom.m_long = buff.st_size; - entry.append( atom ); - -#ifdef USE_POSIX_ACL - /* Append an atom indicating whether the file has extended acl information - * and if withACL is specified also one with the acl itself. If it's a directory - * and it has a default ACL, also append that. */ - appendACLAtoms( path, entry, type, withACL ); -#endif - - notype: - atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; - atom.m_long = buff.st_mtime; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_USER; - atom.m_str = getUserName( buff.st_uid ); - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_GROUP; - atom.m_str = getGroupName( buff.st_gid ); - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_ACCESS_TIME; - atom.m_long = buff.st_atime; - entry.append( atom ); - - // Note: buff.st_ctime isn't the creation time ! - // We made that mistake for KDE 2.0, but it's in fact the - // "file status" change time, which we don't care about. - - return true; -} - -void FileProtocol::stat( const KURL & url ) -{ - if (!url.isLocalFile()) { - KURL redir(url); - redir.setProtocol(config()->readEntry("DefaultRemoteProtocol", "smb")); - redirection(redir); - kdDebug(7101) << "redirecting to " << redir.url() << endl; - finished(); - return; - } - - /* directories may not have a slash at the end if - * we want to stat() them; it requires that we - * change into it .. which may not be allowed - * stat("/is/unaccessible") -> rwx------ - * stat("/is/unaccessible/") -> EPERM H.Z. - * This is the reason for the -1 - */ - TQCString _path( TQFile::encodeName(url.path(-1))); - - TQString sDetails = metaData(TQString::fromLatin1("details")); - int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); - kdDebug(7101) << "FileProtocol::stat details=" << details << endl; - - UDSEntry entry; - if ( !createUDSEntry( url.fileName(), _path, entry, details, true /*with acls*/ ) ) - { - error( TDEIO::ERR_DOES_NOT_EXIST, url.path(-1) ); - return; - } -#if 0 -///////// debug code - TDEIO::UDSEntry::ConstIterator it = entry.begin(); - for( ; it != entry.end(); it++ ) { - switch ((*it).m_uds) { - case TDEIO::UDS_FILE_TYPE: - kdDebug(7101) << "File Type : " << (mode_t)((*it).m_long) << endl; - break; - case TDEIO::UDS_ACCESS: - kdDebug(7101) << "Access permissions : " << (mode_t)((*it).m_long) << endl; - break; - case TDEIO::UDS_USER: - kdDebug(7101) << "User : " << ((*it).m_str.ascii() ) << endl; - break; - case TDEIO::UDS_GROUP: - kdDebug(7101) << "Group : " << ((*it).m_str.ascii() ) << endl; - break; - case TDEIO::UDS_NAME: - kdDebug(7101) << "Name : " << ((*it).m_str.ascii() ) << endl; - //m_strText = decodeFileName( (*it).m_str ); - break; - case TDEIO::UDS_URL: - kdDebug(7101) << "URL : " << ((*it).m_str.ascii() ) << endl; - break; - case TDEIO::UDS_MIME_TYPE: - kdDebug(7101) << "MimeType : " << ((*it).m_str.ascii() ) << endl; - break; - case TDEIO::UDS_LINK_DEST: - kdDebug(7101) << "LinkDest : " << ((*it).m_str.ascii() ) << endl; - break; - case TDEIO::UDS_EXTENDED_ACL: - kdDebug(7101) << "Contains extended ACL " << endl; - break; - } - } - MetaData::iterator it1 = mOutgoingMetaData.begin(); - for ( ; it1 != mOutgoingMetaData.end(); it1++ ) { - kdDebug(7101) << it1.key() << " = " << it1.data() << endl; - } -///////// -#endif - statEntry( entry ); - - finished(); -} - -void FileProtocol::listDir( const KURL& url) -{ - kdDebug(7101) << "========= LIST " << url.url() << " =========" << endl; - if (!url.isLocalFile()) { - KURL redir(url); - redir.setProtocol(config()->readEntry("DefaultRemoteProtocol", "smb")); - redirection(redir); - kdDebug(7101) << "redirecting to " << redir.url() << endl; - finished(); - return; - } - - TQCString _path( TQFile::encodeName(url.path())); - - KDE_struct_stat buff; - if ( KDE_stat( _path.data(), &buff ) == -1 ) { - error( TDEIO::ERR_DOES_NOT_EXIST, url.path() ); - return; - } - - if ( !S_ISDIR( buff.st_mode ) ) { - error( TDEIO::ERR_IS_FILE, url.path() ); - return; - } - - DIR *dp = 0L; - KDE_struct_dirent *ep; - - dp = opendir( _path.data() ); - if ( dp == 0 ) { - switch (errno) - { -#ifdef ENOMEDIUM - case ENOMEDIUM: - error( ERR_SLAVE_DEFINED, - i18n( "No media in device for %1" ).arg( url.path() ) ); - break; -#endif - default: - error( TDEIO::ERR_CANNOT_ENTER_DIRECTORY, url.path() ); - break; - } - return; - } - - // Don't make this a TQStringList. The locale file name we get here - // should be passed intact to createUDSEntry to avoid problems with - // files where TQFile::encodeName(TQFile::decodeName(a)) != a. - TQStrList entryNames; - - while ( ( ep = KDE_readdir( dp ) ) != 0L ) { - entryNames.append( ep->d_name ); - } - - closedir( dp ); - totalSize( entryNames.count() ); - - /* set the current dir to the path to speed up - in not having to pass an absolute path. - We restore the path later to get out of the - path - the kernel wouldn't unmount or delete - directories we keep as active directory. And - as the slave runs in the background, it's hard - to see for the user what the problem would be */ -#if !defined(PATH_MAX) && defined(__GLIBC__) - char *path_buffer; - path_buffer = getcwd(NULL, 0); -#else - char path_buffer[PATH_MAX]; - (void) getcwd(path_buffer, PATH_MAX - 1); -#endif - if ( chdir( _path.data() ) ) { - if (errno == EACCES) - error(ERR_ACCESS_DENIED, _path); - else - error(ERR_CANNOT_ENTER_DIRECTORY, _path); - finished(); - } - - UDSEntry entry; - TQStrListIterator it(entryNames); - for (; it.current(); ++it) { - entry.clear(); - if ( createUDSEntry( TQFile::decodeName(*it), - *it /* we can use the filename as relative path*/, - entry, 2, true ) ) - listEntry( entry, false); - //else - // ;//Well, this should never happen... but with wrong encoding names - } - - listEntry( entry, true ); // ready - - kdDebug(7101) << "============= COMPLETED LIST ============" << endl; - - chdir(path_buffer); -#if !defined(PATH_MAX) && defined(__GLIBC__) - free(path_buffer); -#endif - - finished(); -} - -/* -void FileProtocol::testDir( const TQString& path ) -{ - TQCString _path( TQFile::encodeName(path)); - KDE_struct_stat buff; - if ( KDE_stat( _path.data(), &buff ) == -1 ) { - error( TDEIO::ERR_DOES_NOT_EXIST, path ); - return; - } - - if ( S_ISDIR( buff.st_mode ) ) - isDirectory(); - else - isFile(); - - finished(); -} -*/ - -void FileProtocol::special( const TQByteArray &data) -{ - int tmp; - TQDataStream stream(data, IO_ReadOnly); - - stream >> tmp; - switch (tmp) { - case 1: - { - TQString fstype, dev, point; - TQ_INT8 iRo; - - stream >> iRo >> fstype >> dev >> point; - - bool ro = ( iRo != 0 ); - - kdDebug(7101) << "MOUNTING fstype=" << fstype << " dev=" << dev << " point=" << point << " ro=" << ro << endl; - bool ok = pmount( dev ); - if (ok) - finished(); - else - mount( ro, fstype.ascii(), dev, point ); - - } - break; - case 2: - { - TQString point; - stream >> point; - bool ok = pumount( point ); - if (ok) - finished(); - else - unmount( point ); - } - break; - - case 3: - { - TQString filename; - stream >> filename; - KShred shred( filename ); - connect( &shred, TQT_SIGNAL( processedSize( TDEIO::filesize_t ) ), - this, TQT_SLOT( slotProcessedSize( TDEIO::filesize_t ) ) ); - connect( &shred, TQT_SIGNAL( infoMessage( const TQString & ) ), - this, TQT_SLOT( slotInfoMessage( const TQString & ) ) ); - if (!shred.shred()) - error( TDEIO::ERR_CANNOT_DELETE, filename ); - else - finished(); - break; - } - default: - break; - } -} - -// Connected to KShred -void FileProtocol::slotProcessedSize( TDEIO::filesize_t bytes ) -{ - kdDebug(7101) << "FileProtocol::slotProcessedSize (" << (unsigned int) bytes << ")" << endl; - processedSize( bytes ); -} - -// Connected to KShred -void FileProtocol::slotInfoMessage( const TQString & msg ) -{ - kdDebug(7101) << "FileProtocol::slotInfoMessage (" << msg << ")" << endl; - infoMessage( msg ); -} - -void FileProtocol::mount( bool _ro, const char *_fstype, const TQString& _dev, const TQString& _point ) -{ - kdDebug(7101) << "FileProtocol::mount _fstype=" << _fstype << endl; - TQCString buffer; - -#ifdef HAVE_VOLMGT - /* - * support for Solaris volume management - */ - TQString err; - TQCString devname = TQFile::encodeName( _dev ); - - if( volmgt_running() ) { -// kdDebug(7101) << "VOLMGT: vold ok." << endl; - if( volmgt_check( devname.data() ) == 0 ) { - kdDebug(7101) << "VOLMGT: no media in " - << devname.data() << endl; - err = i18n("No Media inserted or Media not recognized."); - error( TDEIO::ERR_COULD_NOT_MOUNT, err ); - return; - } else { - kdDebug(7101) << "VOLMGT: " << devname.data() - << ": media ok" << endl; - finished(); - return; - } - } else { - err = i18n("\"vold\" is not running."); - kdDebug(7101) << "VOLMGT: " << err << endl; - error( TDEIO::ERR_COULD_NOT_MOUNT, err ); - return; - } -#else - - - KTempFile tmpFile; - TQCString tmpFileC = TQFile::encodeName(tmpFile.name()); - const char *tmp = tmpFileC.data(); - TQCString dev; - if ( _dev.startsWith( "LABEL=" ) ) { // turn LABEL=foo into -L foo (#71430) - TQString labelName = _dev.mid( 6 ); - dev = "-L "; - dev += TQFile::encodeName( TDEProcess::quote( labelName ) ); // is it correct to assume same encoding as filesystem? - } else if ( _dev.startsWith( "UUID=" ) ) { // and UUID=bar into -U bar - TQString uuidName = _dev.mid( 5 ); - dev = "-U "; - dev += TQFile::encodeName( TDEProcess::quote( uuidName ) ); - } - else - dev = TQFile::encodeName( TDEProcess::quote(_dev) ); // get those ready to be given to a shell - - TQCString point = TQFile::encodeName( TDEProcess::quote(_point) ); - bool fstype_empty = !_fstype || !*_fstype; - TQCString fstype = TDEProcess::quote(_fstype).latin1(); // good guess - TQCString readonly = _ro ? "-r" : ""; - TQString epath = TQString::fromLatin1(getenv("PATH")); - TQString path = TQString::fromLatin1("/sbin:/bin"); - if(!epath.isEmpty()) - path += TQString::fromLatin1(":") + epath; - TQString mountProg = TDEGlobal::dirs()->findExe("mount", path); - if (mountProg.isEmpty()){ - error( TDEIO::ERR_COULD_NOT_MOUNT, i18n("Could not find program \"mount\"")); - return; - } - - // Two steps, in case mount doesn't like it when we pass all options - for ( int step = 0 ; step <= 1 ; step++ ) - { - // Mount using device only if no fstype nor mountpoint (KDE-1.x like) - if ( !_dev.isEmpty() && _point.isEmpty() && fstype_empty ) - buffer.sprintf( "%s %s 2>%s", mountProg.latin1(), dev.data(), tmp ); - else - // Mount using the mountpoint, if no fstype nor device (impossible in first step) - if ( !_point.isEmpty() && _dev.isEmpty() && fstype_empty ) - buffer.sprintf( "%s %s 2>%s", mountProg.latin1(), point.data(), tmp ); - else - // mount giving device + mountpoint but no fstype - if ( !_point.isEmpty() && !_dev.isEmpty() && fstype_empty ) - buffer.sprintf( "%s %s %s %s 2>%s", mountProg.latin1(), readonly.data(), dev.data(), point.data(), tmp ); - else - // mount giving device + mountpoint + fstype -#if defined(__svr4__) && defined(__sun__) // MARCO for Solaris 8 and I - // believe this is true for SVR4 in general - buffer.sprintf( "%s -F %s %s %s %s 2>%s" - mountProg.latin1() - fstype.data() - _ro ? "-oro" : "" - dev.data() - point.data() - tmp ); -#elif defined(__OpenBSD__) - buffer.sprintf( "%s %s %s -t %s %s %s 2>%s", "tdesu", mountProg.latin1(), readonly.data(), - fstype.data(), dev.data(), point.data(), tmp ); -#else - buffer.sprintf( "%s %s -t %s %s %s 2>%s", mountProg.latin1(), readonly.data(), - fstype.data(), dev.data(), point.data(), tmp ); -#endif - - kdDebug(7101) << buffer << endl; - - int mount_ret = system( buffer.data() ); - - TQString err = testLogFile( tmp ); - if ( err.isEmpty() && mount_ret == 0) - { - finished(); - return; - } - else - { - // Didn't work - or maybe we just got a warning - TQString mp = TDEIO::findDeviceMountPoint( _dev ); - // Is the device mounted ? - if ( !mp.isEmpty() && mount_ret == 0) - { - kdDebug(7101) << "mount got a warning: " << err << endl; - warning( err ); - finished(); - return; - } - else - { - if ( (step == 0) && !_point.isEmpty()) - { - kdDebug(7101) << err << endl; - kdDebug(7101) << "Mounting with those options didn't work, trying with only mountpoint" << endl; - fstype = ""; - fstype_empty = true; - dev = ""; - // The reason for trying with only mountpoint (instead of - // only device) is that some people (hi Malte!) have the - // same device associated with two mountpoints - // for different fstypes, like /dev/fd0 /mnt/e2floppy and - // /dev/fd0 /mnt/dosfloppy. - // If the user has the same mountpoint associated with two - // different devices, well they shouldn't specify the - // mountpoint but just the device. - } - else - { - error( TDEIO::ERR_COULD_NOT_MOUNT, err ); - return; - } - } - } - } -#endif /* ! HAVE_VOLMGT */ -} - - -void FileProtocol::unmount( const TQString& _point ) -{ - TQCString buffer; - - KTempFile tmpFile; - TQCString tmpFileC = TQFile::encodeName(tmpFile.name()); - TQString err; - const char *tmp = tmpFileC.data(); - -#ifdef HAVE_VOLMGT - /* - * support for Solaris volume management - */ - char *devname; - char *ptr; - FILE *mnttab; - struct mnttab mnt; - - if( volmgt_running() ) { - kdDebug(7101) << "VOLMGT: looking for " - << _point.local8Bit() << endl; - - if( (mnttab = KDE_fopen( MNTTAB, "r" )) == NULL ) { - err = "couldn't open mnttab"; - kdDebug(7101) << "VOLMGT: " << err << endl; - error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); - return; - } - - /* - * since there's no way to derive the device name from - * the mount point through the volmgt library (and - * media_findname() won't work in this case), we have to - * look ourselves... - */ - devname = NULL; - rewind( mnttab ); - while( getmntent( mnttab, &mnt ) == 0 ) { - if( strcmp( _point.local8Bit(), mnt.mnt_mountp ) == 0 ){ - devname = mnt.mnt_special; - break; - } - } - fclose( mnttab ); - - if( devname == NULL ) { - err = "not in mnttab"; - kdDebug(7101) << "VOLMGT: " - << TQFile::encodeName(_point).data() - << ": " << err << endl; - error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); - return; - } - - /* - * strip off the directory name (volume name) - * the eject(1) command will handle unmounting and - * physically eject the media (if possible) - */ - ptr = strrchr( devname, '/' ); - *ptr = '\0'; - TQCString qdevname(TQFile::encodeName(TDEProcess::quote(TQFile::decodeName(TQCString(devname)))).data()); - buffer.sprintf( "/usr/bin/eject %s 2>%s", qdevname.data(), tmp ); - kdDebug(7101) << "VOLMGT: eject " << qdevname << endl; - - /* - * from eject(1): exit status == 0 => need to manually eject - * exit status == 4 => media was ejected - */ -// if( WEXITSTATUS( system( buffer.local8Bit() )) == 4 ) { - if( WEXITSTATUS( system( buffer.data() )) == 4 ) { // Fix for TQString -> QCString? - /* - * this is not an error, so skip "testLogFile()" - * to avoid wrong/confusing error popup - */ - unlink( tmp ); - finished(); - return; - } - } else { - /* - * eject(1) should do its job without vold(1M) running, - * so we probably could call eject anyway, but since the - * media is mounted now, vold must've died for some reason - * during the user's session, so it should be restarted... - */ - err = i18n("\"vold\" is not running."); - kdDebug(7101) << "VOLMGT: " << err << endl; - error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); - return; - } -#else - TQString epath = getenv("PATH"); - TQString path = TQString::fromLatin1("/sbin:/bin"); - if (!epath.isEmpty()) - path += ":" + epath; - TQString umountProg = TDEGlobal::dirs()->findExe("umount", path); - - if (umountProg.isEmpty()) { - error( TDEIO::ERR_COULD_NOT_UNMOUNT, i18n("Could not find program \"umount\"")); - return; - } -#ifdef __OpenBSD__ - buffer.sprintf( "%s %s %s 2>%s", "tdesu", umountProg.latin1(), TQFile::encodeName(TDEProcess::quote(_point)).data(), tmp ); -#else - buffer.sprintf( "%s %s 2>%s", umountProg.latin1(), TQFile::encodeName(TDEProcess::quote(_point)).data(), tmp ); -#endif - system( buffer.data() ); -#endif /* HAVE_VOLMGT */ - - err = testLogFile( tmp ); - - if (err.contains("fstab") || err.contains("root")) { - TQString olderr; - err = TQString::null; - - DCOPRef d("kded", "mediamanager"); - d.setDCOPClient ( dcopClient() ); - DCOPReply reply = d.call("properties", _point); - TQString udi; - - if ( reply.isValid() ) { - TQStringList list = reply; - if (list.size()) - udi = list[0]; - } - - if (!udi.isEmpty()) - reply = d.call("unmount", udi); - - if (udi.isEmpty() || !reply.isValid()) - err = olderr; - else if (reply.isValid()) - reply.get(err); - } - - if ( err.isEmpty() ) - finished(); - else - error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); -} - -/************************************* - * - * pmount handling - * - *************************************/ - -bool FileProtocol::pmount(const TQString &dev) -{ - TQString mountProg; - TQCString buffer; - -#ifdef WITH_UDISKS2 - // Use 'udisksctl' (UDISKS2) if available - if (mountProg.isEmpty()) { - mountProg = TDEGlobal::dirs()->findExe("udisksctl"); - if (!mountProg.isEmpty()) { - buffer.sprintf( "%s mount -b %s", TQFile::encodeName(mountProg).data(), - TQFile::encodeName(TDEProcess::quote(dev)).data() ); - } - } -#endif // WITH_UDISKS2 - -#ifdef WITH_UDISKS - // Use 'udisks' (UDISKS1) if available - if (mountProg.isEmpty()) { - mountProg = TDEGlobal::dirs()->findExe("udisks"); - if (!mountProg.isEmpty()) { - buffer.sprintf( "%s --mount %s", TQFile::encodeName(mountProg).data(), - TQFile::encodeName(TDEProcess::quote(dev)).data() ); - } - } -#endif // WITH_UDISKS - - // Use 'pmount', if available - if (mountProg.isEmpty()) { - mountProg = TDEGlobal::dirs()->findExe("pmount"); - if (!mountProg.isEmpty()) { - buffer.sprintf( "%s %s", TQFile::encodeName(mountProg).data(), - TQFile::encodeName(TDEProcess::quote(dev)).data() ); - } - } - - if (mountProg.isEmpty()) { - return false; - } - - int res = system( buffer.data() ); - - return res==0; -} - -bool FileProtocol::pumount(const TQString &point) -{ - TQString real_point = TDEStandardDirs::realPath(point); - - KMountPoint::List mtab = KMountPoint::currentMountPoints(); - - KMountPoint::List::const_iterator it = mtab.begin(); - KMountPoint::List::const_iterator end = mtab.end(); - - TQString dev; - - for (; it!=end; ++it) - { - TQString tmp = (*it)->mountedFrom(); - TQString mp = (*it)->mountPoint(); - mp = TDEStandardDirs::realPath(mp); - - if (mp==real_point) - dev = TDEStandardDirs::realPath(tmp); - } - - if (dev.isEmpty()) return false; - if (dev.endsWith("/")) dev.truncate(dev.length()-1); - - TQString umountProg; - TQCString buffer; - -#ifdef WITH_UDISKS2 - // Use 'udisksctl' (UDISKS2), if available - if (umountProg.isEmpty()) { - umountProg = TDEGlobal::dirs()->findExe("udisksctl"); - if (!umountProg.isEmpty()) { - buffer.sprintf( "%s unmount -b %s", TQFile::encodeName(umountProg).data(), - TQFile::encodeName(TDEProcess::quote(dev)).data() ); - } - } -#endif // WITH_UDISKS2 - -#ifdef WITH_UDISKS - // Use 'udisks' (UDISKS1), if available - if (umountProg.isEmpty()) { - umountProg = TDEGlobal::dirs()->findExe("udisks"); - if (!umountProg.isEmpty()) { - buffer.sprintf( "%s --unmount %s", TQFile::encodeName(umountProg).data(), - TQFile::encodeName(TDEProcess::quote(dev)).data() ); - } - } -#endif // WITH_UDISKS - - // Use 'pumount', if available - if (umountProg.isEmpty()) { - umountProg = TDEGlobal::dirs()->findExe("pumount"); - if (!umountProg.isEmpty()) { - buffer.sprintf( "%s %s", TQFile::encodeName(umountProg).data(), - TQFile::encodeName(TDEProcess::quote(dev)).data() ); - } - } - - if (umountProg.isEmpty()) { - return false; - } - - int res = system( buffer.data() ); - - return res==0; -} - -/************************************* - * - * Utilities - * - *************************************/ - -static TQString testLogFile( const char *_filename ) -{ - char buffer[ 1024 ]; - KDE_struct_stat buff; - - TQString result; - - KDE_stat( _filename, &buff ); - int size = buff.st_size; - if ( size == 0 ) { - unlink( _filename ); - return result; - } - - FILE * f = KDE_fopen( _filename, "rb" ); - if ( f == 0L ) { - unlink( _filename ); - result = i18n("Could not read %1").arg(TQFile::decodeName(_filename)); - return result; - } - - result = ""; - const char *p = ""; - while ( p != 0L ) { - p = fgets( buffer, sizeof(buffer)-1, f ); - if ( p != 0L ) - result += TQString::fromLocal8Bit(buffer); - } - - fclose( f ); - - unlink( _filename ); - - return result; -} - -/************************************* - * - * ACL handling helpers - * - *************************************/ -#ifdef USE_POSIX_ACL - -static bool isExtendedACL( acl_t acl ) -{ - return ( acl_equiv_mode( acl, 0 ) != 0 ); -} - -static TQString aclAsString( acl_t acl ) -{ - char *aclString = acl_to_text( acl, 0 ); - TQString ret = TQString::fromLatin1( aclString ); - acl_free( (void*)aclString ); - return ret; -} - -static void appendACLAtoms( const TQCString & path, UDSEntry& entry, mode_t type, bool withACL ) -{ - // first check for a noop -#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS - if ( acl_extended_file( path.data() ) == 0 ) return; -#endif - - acl_t acl = 0; - acl_t defaultAcl = 0; - UDSAtom atom; - bool isDir = S_ISDIR( type ); - // do we have an acl for the file, and/or a default acl for the dir, if it is one? - if ( ( acl = acl_get_file( path.data(), ACL_TYPE_ACCESS ) ) ) { - if ( !isExtendedACL( acl ) ) { - acl_free( acl ); - acl = 0; - } - } - - /* Sadly libacl does not provided a means of checking for extended ACL and default - * ACL separately. Since a directory can have both, we need to check again. */ - if ( isDir ) - defaultAcl = acl_get_file( path.data(), ACL_TYPE_DEFAULT ); - - if ( acl || defaultAcl ) { - kdDebug(7101) << path.data() << " has extended ACL entries " << endl; - atom.m_uds = TDEIO::UDS_EXTENDED_ACL; - atom.m_long = 1; - entry.append( atom ); - } - if ( withACL ) { - if ( acl ) { - atom.m_uds = TDEIO::UDS_ACL_STRING; - atom.m_str = aclAsString( acl ); - entry.append( atom ); - kdDebug(7101) << path.data() << "ACL: " << atom.m_str << endl; - } - if ( defaultAcl ) { - atom.m_uds = TDEIO::UDS_DEFAULT_ACL_STRING; - atom.m_str = aclAsString( defaultAcl ); - entry.append( atom ); - kdDebug(7101) << path.data() << "DEFAULT ACL: " << atom.m_str << endl; - } - } - if ( acl ) acl_free( acl ); - if ( defaultAcl ) acl_free( defaultAcl ); -} -#endif - -#include "file.moc" diff --git a/tdeioslave/file/file.cpp b/tdeioslave/file/file.cpp new file mode 100644 index 000000000..ad1cc5574 --- /dev/null +++ b/tdeioslave/file/file.cpp @@ -0,0 +1,1889 @@ +/* + Copyright (C) 2000-2002 Stephan Kulow + Copyright (C) 2000-2002 David Faure + Copyright (C) 2000-2002 Waldo Bastian + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) 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. +*/ + +// $Id$ + +#include + +#include //for Q_OS_XXX +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif + +//sendfile has different semantics in different platforms +#if defined HAVE_SENDFILE && defined Q_OS_LINUX +#define USE_SENDFILE 1 +#endif + +#ifdef USE_SENDFILE +#include +#endif + +#ifdef USE_POSIX_ACL +#include +#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS +#include +#else +#include +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_STRING_H +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "file.h" +#include +#include +#include +#include + +#ifdef HAVE_VOLMGT +#include +#include +#endif + +#include +#include +#include +#include +#include + +using namespace TDEIO; + +#define MAX_IPC_SIZE (1024*32) + +static TQString testLogFile( const char *_filename ); +#ifdef USE_POSIX_ACL +static TQString aclAsString( acl_t p_acl ); +static bool isExtendedACL( acl_t p_acl ); +static void appendACLAtoms( const TQCString & path, UDSEntry& entry, + mode_t type, bool withACL ); +#endif + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + TDELocale::setMainCatalogue("tdelibs"); + TDEInstance instance( "tdeio_file" ); + ( void ) TDEGlobal::locale(); + + kdDebug(7101) << "Starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: tdeio_file protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + FileProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7101) << "Done" << endl; + return 0; +} + + +FileProtocol::FileProtocol( const TQCString &pool, const TQCString &app ) : SlaveBase( "file", pool, app ) +{ + usercache.setAutoDelete( true ); + groupcache.setAutoDelete( true ); +} + + +int FileProtocol::setACL( const char *path, mode_t perm, bool directoryDefault ) +{ + int ret = 0; +#ifdef USE_POSIX_ACL + + const TQString ACLString = metaData( "ACL_STRING" ); + const TQString defaultACLString = metaData( "DEFAULT_ACL_STRING" ); + // Empty strings mean leave as is + if ( !ACLString.isEmpty() ) { + acl_t acl = 0; + if ( ACLString == "ACL_DELETE" ) { + // user told us to delete the extended ACL, so let's write only + // the minimal (UNIX permission bits) part + acl = acl_from_mode( perm ); + } + acl = acl_from_text( ACLString.latin1() ); + if ( acl_valid( acl ) == 0 ) { // let's be safe + ret = acl_set_file( path, ACL_TYPE_ACCESS, acl ); + kdDebug(7101) << "Set ACL on: " << path << " to: " << aclAsString( acl ) << endl; + } + acl_free( acl ); + if ( ret != 0 ) return ret; // better stop trying right away + } + + if ( directoryDefault && !defaultACLString.isEmpty() ) { + if ( defaultACLString == "ACL_DELETE" ) { + // user told us to delete the default ACL, do so + ret += acl_delete_def_file( path ); + } else { + acl_t acl = acl_from_text( defaultACLString.latin1() ); + if ( acl_valid( acl ) == 0 ) { // let's be safe + ret += acl_set_file( path, ACL_TYPE_DEFAULT, acl ); + kdDebug(7101) << "Set Default ACL on: " << path << " to: " << aclAsString( acl ) << endl; + } + acl_free( acl ); + } + } +#endif + return ret; +} + +void FileProtocol::chmod( const KURL& url, int permissions ) +{ + TQCString _path( TQFile::encodeName(url.path()) ); + /* FIXME: Should be atomic */ + if ( ::chmod( _path.data(), permissions ) == -1 || + ( setACL( _path.data(), permissions, false ) == -1 ) || + /* if not a directory, cannot set default ACLs */ + ( setACL( _path.data(), permissions, true ) == -1 && errno != ENOTDIR ) ) { + + switch (errno) { + case EPERM: + case EACCES: + error( TDEIO::ERR_ACCESS_DENIED, url.path() ); + break; + case ENOTSUP: + error( TDEIO::ERR_UNSUPPORTED_ACTION, url.path() ); + break; + case ENOSPC: + error( TDEIO::ERR_DISK_FULL, url.path() ); + break; + default: + error( TDEIO::ERR_CANNOT_CHMOD, url.path() ); + } + } else + finished(); +} + +void FileProtocol::mkdir( const KURL& url, int permissions ) +{ + TQCString _path( TQFile::encodeName(url.path())); + + kdDebug(7101) << "mkdir(): " << _path << ", permission = " << permissions << endl; + + KDE_struct_stat buff; + if ( KDE_stat( _path.data(), &buff ) == -1 ) { + if ( ::mkdir( _path.data(), 0777 /*umask will be applied*/ ) != 0 ) { + if ( errno == EACCES ) { + error( TDEIO::ERR_ACCESS_DENIED, url.path() ); + return; + } else if ( errno == ENOSPC ) { + error( TDEIO::ERR_DISK_FULL, url.path() ); + return; + } else { + error( TDEIO::ERR_COULD_NOT_MKDIR, url.path() ); + return; + } + } else { + if ( permissions != -1 ) + chmod( url, permissions ); + else + finished(); + return; + } + } + + if ( S_ISDIR( buff.st_mode ) ) { + kdDebug(7101) << "ERR_DIR_ALREADY_EXIST" << endl; + error( TDEIO::ERR_DIR_ALREADY_EXIST, url.path() ); + return; + } + error( TDEIO::ERR_FILE_ALREADY_EXIST, url.path() ); + return; +} + +void FileProtocol::get( const KURL& url ) +{ + if (!url.isLocalFile()) { + KURL redir(url); + redir.setProtocol(config()->readEntry("DefaultRemoteProtocol", "smb")); + redirection(redir); + finished(); + return; + } + + TQCString _path( TQFile::encodeName(url.path())); + KDE_struct_stat buff; + if ( KDE_stat( _path.data(), &buff ) == -1 ) { + if ( errno == EACCES ) + error( TDEIO::ERR_ACCESS_DENIED, url.path() ); + else + error( TDEIO::ERR_DOES_NOT_EXIST, url.path() ); + return; + } + + if ( S_ISDIR( buff.st_mode ) ) { + error( TDEIO::ERR_IS_DIRECTORY, url.path() ); + return; + } + if ( !S_ISREG( buff.st_mode ) ) { + error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.path() ); + return; + } + + int fd = KDE_open( _path.data(), O_RDONLY); + if ( fd < 0 ) { + error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.path() ); + return; + } + +#ifdef HAVE_FADVISE + posix_fadvise( fd, 0, 0, POSIX_FADV_SEQUENTIAL); +#endif + + // Determine the mimetype of the file to be retrieved, and emit it. + // This is mandatory in all slaves (for KRun/BrowserRun to work). + KMimeType::Ptr mt = KMimeType::findByURL( url, buff.st_mode, true /* local URL */ ); + emit mimeType( mt->name() ); + + TDEIO::filesize_t processed_size = 0; + + TQString resumeOffset = metaData("resume"); + if ( !resumeOffset.isEmpty() ) + { + bool ok; + TDEIO::fileoffset_t offset = resumeOffset.toLongLong(&ok); + if (ok && (offset > 0) && (offset < buff.st_size)) + { + if (KDE_lseek(fd, offset, SEEK_SET) == offset) + { + canResume (); + processed_size = offset; + kdDebug( 7101 ) << "Resume offset: " << TDEIO::number(offset) << endl; + } + } + } + + totalSize( buff.st_size ); + + char buffer[ MAX_IPC_SIZE ]; + TQByteArray array; + + while( 1 ) + { + int n = ::read( fd, buffer, MAX_IPC_SIZE ); + if (n == -1) + { + if (errno == EINTR) + continue; + error( TDEIO::ERR_COULD_NOT_READ, url.path()); + close(fd); + return; + } + if (n == 0) + break; // Finished + + array.setRawData(buffer, n); + data( array ); + array.resetRawData(buffer, n); + + processed_size += n; + processedSize( processed_size ); + + //kdDebug( 7101 ) << "Processed: " << TDEIO::number (processed_size) << endl; + } + + data( TQByteArray() ); + + close( fd ); + + processedSize( buff.st_size ); + finished(); +} + +static int +write_all(int fd, const char *buf, size_t len) +{ + while (len > 0) + { + ssize_t written = write(fd, buf, len); + if (written < 0) + { + if (errno == EINTR) + continue; + return -1; + } + buf += written; + len -= written; + } + return 0; +} + +static bool +same_inode(const KDE_struct_stat &src, const KDE_struct_stat &dest) +{ + if (src.st_ino == dest.st_ino && + src.st_dev == dest.st_dev) + return true; + + return false; +} + +void FileProtocol::put( const KURL& url, int _mode, bool _overwrite, bool _resume ) +{ + TQString dest_orig = url.path(); + TQCString _dest_orig( TQFile::encodeName(dest_orig)); + + kdDebug(7101) << "put(): " << dest_orig << ", mode=" << _mode << endl; + + TQString dest_part( dest_orig ); + dest_part += TQString::fromLatin1(".part"); + TQCString _dest_part( TQFile::encodeName(dest_part)); + + KDE_struct_stat buff_orig; + bool bOrigExists = (KDE_lstat( _dest_orig.data(), &buff_orig ) != -1); + bool bPartExists = false; + bool bMarkPartial = config()->readBoolEntry("MarkPartial", true); + + if (bMarkPartial) + { + KDE_struct_stat buff_part; + bPartExists = (KDE_stat( _dest_part.data(), &buff_part ) != -1); + + if (bPartExists && !_resume && !_overwrite && buff_part.st_size > 0 && S_ISREG(buff_part.st_mode)) + { + kdDebug(7101) << "FileProtocol::put : calling canResume with " + << TDEIO::number(buff_part.st_size) << endl; + + // Maybe we can use this partial file for resuming + // Tell about the size we have, and the app will tell us + // if it's ok to resume or not. + _resume = canResume( buff_part.st_size ); + + kdDebug(7101) << "FileProtocol::put got answer " << _resume << endl; + } + } + + if ( bOrigExists && !_overwrite && !_resume) + { + if (S_ISDIR(buff_orig.st_mode)) + error( TDEIO::ERR_DIR_ALREADY_EXIST, dest_orig ); + else + error( TDEIO::ERR_FILE_ALREADY_EXIST, dest_orig ); + return; + } + + int result; + TQString dest; + TQCString _dest; + + int fd = -1; + + // Loop until we got 0 (end of data) + do + { + TQByteArray buffer; + dataReq(); // Request for data + result = readData( buffer ); + + if (result >= 0) + { + if (dest.isEmpty()) + { + if (bMarkPartial) + { + kdDebug(7101) << "Appending .part extension to " << dest_orig << endl; + dest = dest_part; + if ( bPartExists && !_resume ) + { + kdDebug(7101) << "Deleting partial file " << dest_part << endl; + remove( _dest_part.data() ); + // Catch errors when we try to open the file. + } + } + else + { + dest = dest_orig; + if ( bOrigExists && !_resume ) + { + kdDebug(7101) << "Deleting destination file " << dest_orig << endl; + remove( _dest_orig.data() ); + // Catch errors when we try to open the file. + } + } + + _dest = TQFile::encodeName(dest); + + if ( _resume ) + { + fd = KDE_open( _dest.data(), O_RDWR ); // append if resuming + KDE_lseek(fd, 0, SEEK_END); // Seek to end + } + else + { + // WABA: Make sure that we keep writing permissions ourselves, + // otherwise we can be in for a surprise on NFS. + mode_t initialMode; + if (_mode != -1) + initialMode = _mode | S_IWUSR | S_IRUSR; + else + initialMode = 0666; + + fd = KDE_open(_dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); + } + + if ( fd < 0 ) + { + kdDebug(7101) << "####################### COULD NOT WRITE " << dest << " _mode=" << _mode << endl; + kdDebug(7101) << "errno==" << errno << "(" << strerror(errno) << ")" << endl; + if ( errno == EACCES ) + error( TDEIO::ERR_WRITE_ACCESS_DENIED, dest ); + else + error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, dest ); + return; + } + } + + if (write_all( fd, buffer.data(), buffer.size())) + { + if ( errno == ENOSPC ) // disk full + { + error( TDEIO::ERR_DISK_FULL, dest_orig); + result = -2; // means: remove dest file + } + else + { + kdWarning(7101) << "Couldn't write. Error:" << strerror(errno) << endl; + error( TDEIO::ERR_COULD_NOT_WRITE, dest_orig); + result = -1; + } + } + } + } + while ( result > 0 ); + + // An error occurred deal with it. + if (result < 0) + { + kdDebug(7101) << "Error during 'put'. Aborting." << endl; + + if (fd != -1) + { + close(fd); + + KDE_struct_stat buff; + if (bMarkPartial && KDE_stat( _dest.data(), &buff ) == 0) + { + int size = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + if (buff.st_size < size) + remove(_dest.data()); + } + } + + ::exit(255); + } + + if ( fd == -1 ) // we got nothing to write out, so we never opened the file + { + finished(); + return; + } + + if ( close(fd) ) + { + kdWarning(7101) << "Error when closing file descriptor:" << strerror(errno) << endl; + error( TDEIO::ERR_COULD_NOT_WRITE, dest_orig); + return; + } + + // after full download rename the file back to original name + if ( bMarkPartial ) + { + // If the original URL is a symlink and we were asked to overwrite it, + // remove the symlink first. This ensures that we do not overwrite the + // current source if the symlink points to it. + if( _overwrite && S_ISLNK( buff_orig.st_mode ) ) + remove( _dest_orig.data() ); + + if ( ::rename( _dest.data(), _dest_orig.data() ) ) + { + kdWarning(7101) << " Couldn't rename " << _dest << " to " << _dest_orig << endl; + error( TDEIO::ERR_CANNOT_RENAME_PARTIAL, dest_orig ); + return; + } + } + + // set final permissions + if ( _mode != -1 && !_resume ) + { + if (::chmod(_dest_orig.data(), _mode) != 0) + { + // couldn't chmod. Eat the error if the filesystem apparently doesn't support it. + if ( TDEIO::testFileSystemFlag( _dest_orig, TDEIO::SupportsChmod ) ) + warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) ); + } + } + + // set modification time + const TQString mtimeStr = metaData( "modified" ); + if ( !mtimeStr.isEmpty() ) { + TQDateTime dt = TQT_TQDATETIME_OBJECT(TQDateTime::fromString( mtimeStr, Qt::ISODate )); + if ( dt.isValid() ) { + KDE_struct_stat dest_statbuf; + if (KDE_stat( _dest_orig.data(), &dest_statbuf ) == 0) { + struct utimbuf utbuf; + utbuf.actime = dest_statbuf.st_atime; // access time, unchanged + utbuf.modtime = dt.toTime_t(); // modification time + kdDebug() << k_funcinfo << "setting modtime to " << utbuf.modtime << endl; + utime( _dest_orig.data(), &utbuf ); + } + } + + } + + // We have done our job => finish + finished(); +} + + +void FileProtocol::copy( const KURL &src, const KURL &dest, + int _mode, bool _overwrite ) +{ + kdDebug(7101) << "copy(): " << src << " -> " << dest << ", mode=" << _mode << endl; + + TQCString _src( TQFile::encodeName(src.path())); + TQCString _dest( TQFile::encodeName(dest.path())); + KDE_struct_stat buff_src; +#ifdef USE_POSIX_ACL + acl_t acl; +#endif + + if ( KDE_stat( _src.data(), &buff_src ) == -1 ) { + if ( errno == EACCES ) + error( TDEIO::ERR_ACCESS_DENIED, src.path() ); + else + error( TDEIO::ERR_DOES_NOT_EXIST, src.path() ); + return; + } + + if ( S_ISDIR( buff_src.st_mode ) ) { + error( TDEIO::ERR_IS_DIRECTORY, src.path() ); + return; + } + if ( S_ISFIFO( buff_src.st_mode ) || S_ISSOCK ( buff_src.st_mode ) ) { + error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.path() ); + return; + } + + KDE_struct_stat buff_dest; + bool dest_exists = ( KDE_lstat( _dest.data(), &buff_dest ) != -1 ); + if ( dest_exists ) + { + if (S_ISDIR(buff_dest.st_mode)) + { + error( TDEIO::ERR_DIR_ALREADY_EXIST, dest.path() ); + return; + } + + if ( same_inode( buff_dest, buff_src) ) + { + error( TDEIO::ERR_IDENTICAL_FILES, dest.path() ); + return; + } + + if (!_overwrite) + { + error( TDEIO::ERR_FILE_ALREADY_EXIST, dest.path() ); + return; + } + + // If the destination is a symlink and overwrite is TRUE, + // remove the symlink first to prevent the scenario where + // the symlink actually points to current source! + if (_overwrite && S_ISLNK(buff_dest.st_mode)) + { + kdDebug(7101) << "copy(): LINK DESTINATION" << endl; + remove( _dest.data() ); + } + } + + int src_fd = KDE_open( _src.data(), O_RDONLY); + if ( src_fd < 0 ) { + error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, src.path() ); + return; + } + +#ifdef HAVE_FADVISE + posix_fadvise(src_fd,0,0,POSIX_FADV_SEQUENTIAL); +#endif + // WABA: Make sure that we keep writing permissions ourselves, + // otherwise we can be in for a surprise on NFS. + mode_t initialMode; + if (_mode != -1) + initialMode = _mode | S_IWUSR; + else + initialMode = 0666; + + int dest_fd = KDE_open(_dest.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); + if ( dest_fd < 0 ) { + kdDebug(7101) << "###### COULD NOT WRITE " << dest.url() << endl; + if ( errno == EACCES ) { + error( TDEIO::ERR_WRITE_ACCESS_DENIED, dest.path() ); + } else { + error( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, dest.path() ); + } + close(src_fd); + return; + } + +#ifdef HAVE_FADVISE + posix_fadvise(dest_fd,0,0,POSIX_FADV_SEQUENTIAL); +#endif + +#ifdef USE_POSIX_ACL + acl = acl_get_fd(src_fd); + if ( acl && !isExtendedACL( acl ) ) { + kdDebug(7101) << _dest.data() << " doesn't have extended ACL" << endl; + acl_free( acl ); + acl = NULL; + } +#endif + totalSize( buff_src.st_size ); + + TDEIO::filesize_t processed_size = 0; + char buffer[ MAX_IPC_SIZE ]; + int n; +#ifdef USE_SENDFILE + bool use_sendfile=buff_src.st_size < 0x7FFFFFFF; +#endif + while( 1 ) + { +#ifdef USE_SENDFILE + if (use_sendfile) { + off_t sf = processed_size; + n = ::sendfile( dest_fd, src_fd, &sf, MAX_IPC_SIZE ); + processed_size = sf; + if ( n == -1 && errno == EINVAL ) { //not all filesystems support sendfile() + kdDebug(7101) << "sendfile() not supported, falling back " << endl; + use_sendfile = false; + } + } + if (!use_sendfile) +#endif + n = ::read( src_fd, buffer, MAX_IPC_SIZE ); + + if (n == -1) + { + if (errno == EINTR) + continue; +#ifdef USE_SENDFILE + if ( use_sendfile ) { + kdDebug(7101) << "sendfile() error:" << strerror(errno) << endl; + if ( errno == ENOSPC ) // disk full + { + error( TDEIO::ERR_DISK_FULL, dest.path()); + remove( _dest.data() ); + } + else { + error( TDEIO::ERR_SLAVE_DEFINED, + i18n("Cannot copy file from %1 to %2. (Errno: %3)") + .arg( src.path() ).arg( dest.path() ).arg( errno ) ); + } + } else +#endif + error( TDEIO::ERR_COULD_NOT_READ, src.path()); + close(src_fd); + close(dest_fd); +#ifdef USE_POSIX_ACL + if (acl) acl_free(acl); +#endif + return; + } + if (n == 0) + break; // Finished +#ifdef USE_SENDFILE + if ( !use_sendfile ) { +#endif + if (write_all( dest_fd, buffer, n)) + { + close(src_fd); + close(dest_fd); + + if ( errno == ENOSPC ) // disk full + { + error( TDEIO::ERR_DISK_FULL, dest.path()); + remove( _dest.data() ); + } + else + { + kdWarning(7101) << "Couldn't write[2]. Error:" << strerror(errno) << endl; + error( TDEIO::ERR_COULD_NOT_WRITE, dest.path()); + } +#ifdef USE_POSIX_ACL + if (acl) acl_free(acl); +#endif + return; + } + processed_size += n; +#ifdef USE_SENDFILE + } +#endif + processedSize( processed_size ); + } + + close( src_fd ); + + if (close( dest_fd)) + { + kdWarning(7101) << "Error when closing file descriptor[2]:" << strerror(errno) << endl; + error( TDEIO::ERR_COULD_NOT_WRITE, dest.path()); +#ifdef USE_POSIX_ACL + if (acl) acl_free(acl); +#endif + return; + } + + // set final permissions + if ( _mode != -1 ) + { + if ( (::chmod(_dest.data(), _mode) != 0) +#ifdef USE_POSIX_ACL + || (acl && acl_set_file(_dest.data(), ACL_TYPE_ACCESS, acl) != 0) +#endif + ) + { + // Eat the error if the filesystem apparently doesn't support chmod. + if ( TDEIO::testFileSystemFlag( _dest, TDEIO::SupportsChmod ) ) + warning( i18n( "Could not change permissions for\n%1" ).arg( dest.path() ) ); + } + } +#ifdef USE_POSIX_ACL + if (acl) acl_free(acl); +#endif + + // copy access and modification time + struct utimbuf ut; + ut.actime = buff_src.st_atime; + ut.modtime = buff_src.st_mtime; + if ( ::utime( _dest.data(), &ut ) != 0 ) + { + kdWarning() << TQString(TQString::fromLatin1("Couldn't preserve access and modification time for\n%1").arg( dest.path() )) << endl; + } + + processedSize( buff_src.st_size ); + finished(); +} + +void FileProtocol::rename( const KURL &src, const KURL &dest, + bool _overwrite ) +{ + TQCString _src( TQFile::encodeName(src.path())); + TQCString _dest( TQFile::encodeName(dest.path())); + KDE_struct_stat buff_src; + if ( KDE_lstat( _src.data(), &buff_src ) == -1 ) { + if ( errno == EACCES ) + error( TDEIO::ERR_ACCESS_DENIED, src.path() ); + else + error( TDEIO::ERR_DOES_NOT_EXIST, src.path() ); + return; + } + + KDE_struct_stat buff_dest; + bool dest_exists = ( KDE_stat( _dest.data(), &buff_dest ) != -1 ); + if ( dest_exists ) + { + if (S_ISDIR(buff_dest.st_mode)) + { + error( TDEIO::ERR_DIR_ALREADY_EXIST, dest.path() ); + return; + } + + if ( same_inode( buff_dest, buff_src) ) + { + error( TDEIO::ERR_IDENTICAL_FILES, dest.path() ); + return; + } + + if (!_overwrite) + { + error( TDEIO::ERR_FILE_ALREADY_EXIST, dest.path() ); + return; + } + } + + if ( ::rename( _src.data(), _dest.data())) + { + if (( errno == EACCES ) || (errno == EPERM)) { + error( TDEIO::ERR_ACCESS_DENIED, dest.path() ); + } + else if (errno == EXDEV) { + error( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("rename")); + } + else if (errno == EROFS) { // The file is on a read-only filesystem + error( TDEIO::ERR_CANNOT_DELETE, src.path() ); + } + else { + error( TDEIO::ERR_CANNOT_RENAME, src.path() ); + } + return; + } + + finished(); +} + +void FileProtocol::symlink( const TQString &target, const KURL &dest, bool overwrite ) +{ + // Assume dest is local too (wouldn't be here otherwise) + if ( ::symlink( TQFile::encodeName( target ), TQFile::encodeName( dest.path() ) ) == -1 ) + { + // Does the destination already exist ? + if ( errno == EEXIST ) + { + if ( overwrite ) + { + // Try to delete the destination + if ( unlink( TQFile::encodeName( dest.path() ) ) != 0 ) + { + error( TDEIO::ERR_CANNOT_DELETE, dest.path() ); + return; + } + // Try again - this won't loop forever since unlink succeeded + symlink( target, dest, overwrite ); + } + else + { + KDE_struct_stat buff_dest; + KDE_lstat( TQFile::encodeName( dest.path() ), &buff_dest ); + if (S_ISDIR(buff_dest.st_mode)) + error( TDEIO::ERR_DIR_ALREADY_EXIST, dest.path() ); + else + error( TDEIO::ERR_FILE_ALREADY_EXIST, dest.path() ); + return; + } + } + else + { + // Some error occurred while we tried to symlink + error( TDEIO::ERR_CANNOT_SYMLINK, dest.path() ); + return; + } + } + finished(); +} + +void FileProtocol::del( const KURL& url, bool isfile) +{ + TQCString _path( TQFile::encodeName(url.path())); + /***** + * Delete files + *****/ + + if (isfile) { + kdDebug( 7101 ) << "Deleting file "<< url.url() << endl; + + // TODO deletingFile( source ); + + if ( unlink( _path.data() ) == -1 ) { + if ((errno == EACCES) || (errno == EPERM)) + error( TDEIO::ERR_ACCESS_DENIED, url.path()); + else if (errno == EISDIR) + error( TDEIO::ERR_IS_DIRECTORY, url.path()); + else + error( TDEIO::ERR_CANNOT_DELETE, url.path() ); + return; + } + } else { + + /***** + * Delete empty directory + *****/ + + kdDebug( 7101 ) << "Deleting directory " << url.url() << endl; + + if ( ::rmdir( _path.data() ) == -1 ) { + if ((errno == EACCES) || (errno == EPERM)) + error( TDEIO::ERR_ACCESS_DENIED, url.path()); + else { + kdDebug( 7101 ) << "could not rmdir " << perror << endl; + error( TDEIO::ERR_COULD_NOT_RMDIR, url.path() ); + return; + } + } + } + + finished(); +} + + +TQString FileProtocol::getUserName( uid_t uid ) +{ + TQString *temp; + temp = usercache.find( uid ); + if ( !temp ) { + struct passwd *user = getpwuid( uid ); + if ( user ) { + usercache.insert( uid, new TQString(TQString::fromLatin1(user->pw_name)) ); + return TQString::fromLatin1( user->pw_name ); + } + else + return TQString::number( uid ); + } + else + return *temp; +} + +TQString FileProtocol::getGroupName( gid_t gid ) +{ + TQString *temp; + temp = groupcache.find( gid ); + if ( !temp ) { + struct group *grp = getgrgid( gid ); + if ( grp ) { + groupcache.insert( gid, new TQString(TQString::fromLatin1(grp->gr_name)) ); + return TQString::fromLatin1( grp->gr_name ); + } + else + return TQString::number( gid ); + } + else + return *temp; +} + + + +bool FileProtocol::createUDSEntry( const TQString & filename, const TQCString & path, UDSEntry & entry, + short int details, bool withACL ) +{ + assert(entry.count() == 0); // by contract :-) + // Note: details = 0 (only "file or directory or symlink or doesn't exist") isn't implemented + // because there's no real performance penalty in tdeio_file for returning the complete + // details. Please consider doing it in your tdeioslave if you're using this one as a model :) + UDSAtom atom; + atom.m_uds = TDEIO::UDS_NAME; + atom.m_str = filename; + entry.append( atom ); + + mode_t type; + mode_t access; + KDE_struct_stat buff; + + if ( KDE_lstat( path.data(), &buff ) == 0 ) { + + if (S_ISLNK(buff.st_mode)) { + + char buffer2[ 1000 ]; + int n = readlink( path.data(), buffer2, 1000 ); + if ( n != -1 ) { + buffer2[ n ] = 0; + } + + atom.m_uds = TDEIO::UDS_LINK_DEST; + atom.m_str = TQFile::decodeName( buffer2 ); + entry.append( atom ); + + // A symlink -> follow it only if details>1 + if ( details > 1 && KDE_stat( path.data(), &buff ) == -1 ) { + // It is a link pointing to nowhere + type = S_IFMT - 1; + access = S_IRWXU | S_IRWXG | S_IRWXO; + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = type; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = access; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = 0L; + entry.append( atom ); + + goto notype; + + } + } + } else { + // kdWarning() << "lstat didn't work on " << path.data() << endl; + return false; + } + + type = buff.st_mode & S_IFMT; // extract file type + access = buff.st_mode & 07777; // extract permissions + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = type; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = access; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = buff.st_size; + entry.append( atom ); + +#ifdef USE_POSIX_ACL + /* Append an atom indicating whether the file has extended acl information + * and if withACL is specified also one with the acl itself. If it's a directory + * and it has a default ACL, also append that. */ + appendACLAtoms( path, entry, type, withACL ); +#endif + + notype: + atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; + atom.m_long = buff.st_mtime; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_USER; + atom.m_str = getUserName( buff.st_uid ); + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_GROUP; + atom.m_str = getGroupName( buff.st_gid ); + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS_TIME; + atom.m_long = buff.st_atime; + entry.append( atom ); + + // Note: buff.st_ctime isn't the creation time ! + // We made that mistake for KDE 2.0, but it's in fact the + // "file status" change time, which we don't care about. + + return true; +} + +void FileProtocol::stat( const KURL & url ) +{ + if (!url.isLocalFile()) { + KURL redir(url); + redir.setProtocol(config()->readEntry("DefaultRemoteProtocol", "smb")); + redirection(redir); + kdDebug(7101) << "redirecting to " << redir.url() << endl; + finished(); + return; + } + + /* directories may not have a slash at the end if + * we want to stat() them; it requires that we + * change into it .. which may not be allowed + * stat("/is/unaccessible") -> rwx------ + * stat("/is/unaccessible/") -> EPERM H.Z. + * This is the reason for the -1 + */ + TQCString _path( TQFile::encodeName(url.path(-1))); + + TQString sDetails = metaData(TQString::fromLatin1("details")); + int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); + kdDebug(7101) << "FileProtocol::stat details=" << details << endl; + + UDSEntry entry; + if ( !createUDSEntry( url.fileName(), _path, entry, details, true /*with acls*/ ) ) + { + error( TDEIO::ERR_DOES_NOT_EXIST, url.path(-1) ); + return; + } +#if 0 +///////// debug code + TDEIO::UDSEntry::ConstIterator it = entry.begin(); + for( ; it != entry.end(); it++ ) { + switch ((*it).m_uds) { + case TDEIO::UDS_FILE_TYPE: + kdDebug(7101) << "File Type : " << (mode_t)((*it).m_long) << endl; + break; + case TDEIO::UDS_ACCESS: + kdDebug(7101) << "Access permissions : " << (mode_t)((*it).m_long) << endl; + break; + case TDEIO::UDS_USER: + kdDebug(7101) << "User : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_GROUP: + kdDebug(7101) << "Group : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_NAME: + kdDebug(7101) << "Name : " << ((*it).m_str.ascii() ) << endl; + //m_strText = decodeFileName( (*it).m_str ); + break; + case TDEIO::UDS_URL: + kdDebug(7101) << "URL : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_MIME_TYPE: + kdDebug(7101) << "MimeType : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_LINK_DEST: + kdDebug(7101) << "LinkDest : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_EXTENDED_ACL: + kdDebug(7101) << "Contains extended ACL " << endl; + break; + } + } + MetaData::iterator it1 = mOutgoingMetaData.begin(); + for ( ; it1 != mOutgoingMetaData.end(); it1++ ) { + kdDebug(7101) << it1.key() << " = " << it1.data() << endl; + } +///////// +#endif + statEntry( entry ); + + finished(); +} + +void FileProtocol::listDir( const KURL& url) +{ + kdDebug(7101) << "========= LIST " << url.url() << " =========" << endl; + if (!url.isLocalFile()) { + KURL redir(url); + redir.setProtocol(config()->readEntry("DefaultRemoteProtocol", "smb")); + redirection(redir); + kdDebug(7101) << "redirecting to " << redir.url() << endl; + finished(); + return; + } + + TQCString _path( TQFile::encodeName(url.path())); + + KDE_struct_stat buff; + if ( KDE_stat( _path.data(), &buff ) == -1 ) { + error( TDEIO::ERR_DOES_NOT_EXIST, url.path() ); + return; + } + + if ( !S_ISDIR( buff.st_mode ) ) { + error( TDEIO::ERR_IS_FILE, url.path() ); + return; + } + + DIR *dp = 0L; + KDE_struct_dirent *ep; + + dp = opendir( _path.data() ); + if ( dp == 0 ) { + switch (errno) + { +#ifdef ENOMEDIUM + case ENOMEDIUM: + error( ERR_SLAVE_DEFINED, + i18n( "No media in device for %1" ).arg( url.path() ) ); + break; +#endif + default: + error( TDEIO::ERR_CANNOT_ENTER_DIRECTORY, url.path() ); + break; + } + return; + } + + // Don't make this a TQStringList. The locale file name we get here + // should be passed intact to createUDSEntry to avoid problems with + // files where TQFile::encodeName(TQFile::decodeName(a)) != a. + TQStrList entryNames; + + while ( ( ep = KDE_readdir( dp ) ) != 0L ) { + entryNames.append( ep->d_name ); + } + + closedir( dp ); + totalSize( entryNames.count() ); + + /* set the current dir to the path to speed up + in not having to pass an absolute path. + We restore the path later to get out of the + path - the kernel wouldn't unmount or delete + directories we keep as active directory. And + as the slave runs in the background, it's hard + to see for the user what the problem would be */ +#if !defined(PATH_MAX) && defined(__GLIBC__) + char *path_buffer; + path_buffer = getcwd(NULL, 0); +#else + char path_buffer[PATH_MAX]; + (void) getcwd(path_buffer, PATH_MAX - 1); +#endif + if ( chdir( _path.data() ) ) { + if (errno == EACCES) + error(ERR_ACCESS_DENIED, _path); + else + error(ERR_CANNOT_ENTER_DIRECTORY, _path); + finished(); + } + + UDSEntry entry; + TQStrListIterator it(entryNames); + for (; it.current(); ++it) { + entry.clear(); + if ( createUDSEntry( TQFile::decodeName(*it), + *it /* we can use the filename as relative path*/, + entry, 2, true ) ) + listEntry( entry, false); + //else + // ;//Well, this should never happen... but with wrong encoding names + } + + listEntry( entry, true ); // ready + + kdDebug(7101) << "============= COMPLETED LIST ============" << endl; + + chdir(path_buffer); +#if !defined(PATH_MAX) && defined(__GLIBC__) + free(path_buffer); +#endif + + finished(); +} + +/* +void FileProtocol::testDir( const TQString& path ) +{ + TQCString _path( TQFile::encodeName(path)); + KDE_struct_stat buff; + if ( KDE_stat( _path.data(), &buff ) == -1 ) { + error( TDEIO::ERR_DOES_NOT_EXIST, path ); + return; + } + + if ( S_ISDIR( buff.st_mode ) ) + isDirectory(); + else + isFile(); + + finished(); +} +*/ + +void FileProtocol::special( const TQByteArray &data) +{ + int tmp; + TQDataStream stream(data, IO_ReadOnly); + + stream >> tmp; + switch (tmp) { + case 1: + { + TQString fstype, dev, point; + TQ_INT8 iRo; + + stream >> iRo >> fstype >> dev >> point; + + bool ro = ( iRo != 0 ); + + kdDebug(7101) << "MOUNTING fstype=" << fstype << " dev=" << dev << " point=" << point << " ro=" << ro << endl; + bool ok = pmount( dev ); + if (ok) + finished(); + else + mount( ro, fstype.ascii(), dev, point ); + + } + break; + case 2: + { + TQString point; + stream >> point; + bool ok = pumount( point ); + if (ok) + finished(); + else + unmount( point ); + } + break; + + case 3: + { + TQString filename; + stream >> filename; + KShred shred( filename ); + connect( &shred, TQT_SIGNAL( processedSize( TDEIO::filesize_t ) ), + this, TQT_SLOT( slotProcessedSize( TDEIO::filesize_t ) ) ); + connect( &shred, TQT_SIGNAL( infoMessage( const TQString & ) ), + this, TQT_SLOT( slotInfoMessage( const TQString & ) ) ); + if (!shred.shred()) + error( TDEIO::ERR_CANNOT_DELETE, filename ); + else + finished(); + break; + } + default: + break; + } +} + +// Connected to KShred +void FileProtocol::slotProcessedSize( TDEIO::filesize_t bytes ) +{ + kdDebug(7101) << "FileProtocol::slotProcessedSize (" << (unsigned int) bytes << ")" << endl; + processedSize( bytes ); +} + +// Connected to KShred +void FileProtocol::slotInfoMessage( const TQString & msg ) +{ + kdDebug(7101) << "FileProtocol::slotInfoMessage (" << msg << ")" << endl; + infoMessage( msg ); +} + +void FileProtocol::mount( bool _ro, const char *_fstype, const TQString& _dev, const TQString& _point ) +{ + kdDebug(7101) << "FileProtocol::mount _fstype=" << _fstype << endl; + TQCString buffer; + +#ifdef HAVE_VOLMGT + /* + * support for Solaris volume management + */ + TQString err; + TQCString devname = TQFile::encodeName( _dev ); + + if( volmgt_running() ) { +// kdDebug(7101) << "VOLMGT: vold ok." << endl; + if( volmgt_check( devname.data() ) == 0 ) { + kdDebug(7101) << "VOLMGT: no media in " + << devname.data() << endl; + err = i18n("No Media inserted or Media not recognized."); + error( TDEIO::ERR_COULD_NOT_MOUNT, err ); + return; + } else { + kdDebug(7101) << "VOLMGT: " << devname.data() + << ": media ok" << endl; + finished(); + return; + } + } else { + err = i18n("\"vold\" is not running."); + kdDebug(7101) << "VOLMGT: " << err << endl; + error( TDEIO::ERR_COULD_NOT_MOUNT, err ); + return; + } +#else + + + KTempFile tmpFile; + TQCString tmpFileC = TQFile::encodeName(tmpFile.name()); + const char *tmp = tmpFileC.data(); + TQCString dev; + if ( _dev.startsWith( "LABEL=" ) ) { // turn LABEL=foo into -L foo (#71430) + TQString labelName = _dev.mid( 6 ); + dev = "-L "; + dev += TQFile::encodeName( TDEProcess::quote( labelName ) ); // is it correct to assume same encoding as filesystem? + } else if ( _dev.startsWith( "UUID=" ) ) { // and UUID=bar into -U bar + TQString uuidName = _dev.mid( 5 ); + dev = "-U "; + dev += TQFile::encodeName( TDEProcess::quote( uuidName ) ); + } + else + dev = TQFile::encodeName( TDEProcess::quote(_dev) ); // get those ready to be given to a shell + + TQCString point = TQFile::encodeName( TDEProcess::quote(_point) ); + bool fstype_empty = !_fstype || !*_fstype; + TQCString fstype = TDEProcess::quote(_fstype).latin1(); // good guess + TQCString readonly = _ro ? "-r" : ""; + TQString epath = TQString::fromLatin1(getenv("PATH")); + TQString path = TQString::fromLatin1("/sbin:/bin"); + if(!epath.isEmpty()) + path += TQString::fromLatin1(":") + epath; + TQString mountProg = TDEGlobal::dirs()->findExe("mount", path); + if (mountProg.isEmpty()){ + error( TDEIO::ERR_COULD_NOT_MOUNT, i18n("Could not find program \"mount\"")); + return; + } + + // Two steps, in case mount doesn't like it when we pass all options + for ( int step = 0 ; step <= 1 ; step++ ) + { + // Mount using device only if no fstype nor mountpoint (KDE-1.x like) + if ( !_dev.isEmpty() && _point.isEmpty() && fstype_empty ) + buffer.sprintf( "%s %s 2>%s", mountProg.latin1(), dev.data(), tmp ); + else + // Mount using the mountpoint, if no fstype nor device (impossible in first step) + if ( !_point.isEmpty() && _dev.isEmpty() && fstype_empty ) + buffer.sprintf( "%s %s 2>%s", mountProg.latin1(), point.data(), tmp ); + else + // mount giving device + mountpoint but no fstype + if ( !_point.isEmpty() && !_dev.isEmpty() && fstype_empty ) + buffer.sprintf( "%s %s %s %s 2>%s", mountProg.latin1(), readonly.data(), dev.data(), point.data(), tmp ); + else + // mount giving device + mountpoint + fstype +#if defined(__svr4__) && defined(__sun__) // MARCO for Solaris 8 and I + // believe this is true for SVR4 in general + buffer.sprintf( "%s -F %s %s %s %s 2>%s" + mountProg.latin1() + fstype.data() + _ro ? "-oro" : "" + dev.data() + point.data() + tmp ); +#elif defined(__OpenBSD__) + buffer.sprintf( "%s %s %s -t %s %s %s 2>%s", "tdesu", mountProg.latin1(), readonly.data(), + fstype.data(), dev.data(), point.data(), tmp ); +#else + buffer.sprintf( "%s %s -t %s %s %s 2>%s", mountProg.latin1(), readonly.data(), + fstype.data(), dev.data(), point.data(), tmp ); +#endif + + kdDebug(7101) << buffer << endl; + + int mount_ret = system( buffer.data() ); + + TQString err = testLogFile( tmp ); + if ( err.isEmpty() && mount_ret == 0) + { + finished(); + return; + } + else + { + // Didn't work - or maybe we just got a warning + TQString mp = TDEIO::findDeviceMountPoint( _dev ); + // Is the device mounted ? + if ( !mp.isEmpty() && mount_ret == 0) + { + kdDebug(7101) << "mount got a warning: " << err << endl; + warning( err ); + finished(); + return; + } + else + { + if ( (step == 0) && !_point.isEmpty()) + { + kdDebug(7101) << err << endl; + kdDebug(7101) << "Mounting with those options didn't work, trying with only mountpoint" << endl; + fstype = ""; + fstype_empty = true; + dev = ""; + // The reason for trying with only mountpoint (instead of + // only device) is that some people (hi Malte!) have the + // same device associated with two mountpoints + // for different fstypes, like /dev/fd0 /mnt/e2floppy and + // /dev/fd0 /mnt/dosfloppy. + // If the user has the same mountpoint associated with two + // different devices, well they shouldn't specify the + // mountpoint but just the device. + } + else + { + error( TDEIO::ERR_COULD_NOT_MOUNT, err ); + return; + } + } + } + } +#endif /* ! HAVE_VOLMGT */ +} + + +void FileProtocol::unmount( const TQString& _point ) +{ + TQCString buffer; + + KTempFile tmpFile; + TQCString tmpFileC = TQFile::encodeName(tmpFile.name()); + TQString err; + const char *tmp = tmpFileC.data(); + +#ifdef HAVE_VOLMGT + /* + * support for Solaris volume management + */ + char *devname; + char *ptr; + FILE *mnttab; + struct mnttab mnt; + + if( volmgt_running() ) { + kdDebug(7101) << "VOLMGT: looking for " + << _point.local8Bit() << endl; + + if( (mnttab = KDE_fopen( MNTTAB, "r" )) == NULL ) { + err = "couldn't open mnttab"; + kdDebug(7101) << "VOLMGT: " << err << endl; + error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); + return; + } + + /* + * since there's no way to derive the device name from + * the mount point through the volmgt library (and + * media_findname() won't work in this case), we have to + * look ourselves... + */ + devname = NULL; + rewind( mnttab ); + while( getmntent( mnttab, &mnt ) == 0 ) { + if( strcmp( _point.local8Bit(), mnt.mnt_mountp ) == 0 ){ + devname = mnt.mnt_special; + break; + } + } + fclose( mnttab ); + + if( devname == NULL ) { + err = "not in mnttab"; + kdDebug(7101) << "VOLMGT: " + << TQFile::encodeName(_point).data() + << ": " << err << endl; + error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); + return; + } + + /* + * strip off the directory name (volume name) + * the eject(1) command will handle unmounting and + * physically eject the media (if possible) + */ + ptr = strrchr( devname, '/' ); + *ptr = '\0'; + TQCString qdevname(TQFile::encodeName(TDEProcess::quote(TQFile::decodeName(TQCString(devname)))).data()); + buffer.sprintf( "/usr/bin/eject %s 2>%s", qdevname.data(), tmp ); + kdDebug(7101) << "VOLMGT: eject " << qdevname << endl; + + /* + * from eject(1): exit status == 0 => need to manually eject + * exit status == 4 => media was ejected + */ +// if( WEXITSTATUS( system( buffer.local8Bit() )) == 4 ) { + if( WEXITSTATUS( system( buffer.data() )) == 4 ) { // Fix for TQString -> QCString? + /* + * this is not an error, so skip "testLogFile()" + * to avoid wrong/confusing error popup + */ + unlink( tmp ); + finished(); + return; + } + } else { + /* + * eject(1) should do its job without vold(1M) running, + * so we probably could call eject anyway, but since the + * media is mounted now, vold must've died for some reason + * during the user's session, so it should be restarted... + */ + err = i18n("\"vold\" is not running."); + kdDebug(7101) << "VOLMGT: " << err << endl; + error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); + return; + } +#else + TQString epath = getenv("PATH"); + TQString path = TQString::fromLatin1("/sbin:/bin"); + if (!epath.isEmpty()) + path += ":" + epath; + TQString umountProg = TDEGlobal::dirs()->findExe("umount", path); + + if (umountProg.isEmpty()) { + error( TDEIO::ERR_COULD_NOT_UNMOUNT, i18n("Could not find program \"umount\"")); + return; + } +#ifdef __OpenBSD__ + buffer.sprintf( "%s %s %s 2>%s", "tdesu", umountProg.latin1(), TQFile::encodeName(TDEProcess::quote(_point)).data(), tmp ); +#else + buffer.sprintf( "%s %s 2>%s", umountProg.latin1(), TQFile::encodeName(TDEProcess::quote(_point)).data(), tmp ); +#endif + system( buffer.data() ); +#endif /* HAVE_VOLMGT */ + + err = testLogFile( tmp ); + + if (err.contains("fstab") || err.contains("root")) { + TQString olderr; + err = TQString::null; + + DCOPRef d("kded", "mediamanager"); + d.setDCOPClient ( dcopClient() ); + DCOPReply reply = d.call("properties", _point); + TQString udi; + + if ( reply.isValid() ) { + TQStringList list = reply; + if (list.size()) + udi = list[0]; + } + + if (!udi.isEmpty()) + reply = d.call("unmount", udi); + + if (udi.isEmpty() || !reply.isValid()) + err = olderr; + else if (reply.isValid()) + reply.get(err); + } + + if ( err.isEmpty() ) + finished(); + else + error( TDEIO::ERR_COULD_NOT_UNMOUNT, err ); +} + +/************************************* + * + * pmount handling + * + *************************************/ + +bool FileProtocol::pmount(const TQString &dev) +{ + TQString mountProg; + TQCString buffer; + +#ifdef WITH_UDISKS2 + // Use 'udisksctl' (UDISKS2) if available + if (mountProg.isEmpty()) { + mountProg = TDEGlobal::dirs()->findExe("udisksctl"); + if (!mountProg.isEmpty()) { + buffer.sprintf( "%s mount -b %s", TQFile::encodeName(mountProg).data(), + TQFile::encodeName(TDEProcess::quote(dev)).data() ); + } + } +#endif // WITH_UDISKS2 + +#ifdef WITH_UDISKS + // Use 'udisks' (UDISKS1) if available + if (mountProg.isEmpty()) { + mountProg = TDEGlobal::dirs()->findExe("udisks"); + if (!mountProg.isEmpty()) { + buffer.sprintf( "%s --mount %s", TQFile::encodeName(mountProg).data(), + TQFile::encodeName(TDEProcess::quote(dev)).data() ); + } + } +#endif // WITH_UDISKS + + // Use 'pmount', if available + if (mountProg.isEmpty()) { + mountProg = TDEGlobal::dirs()->findExe("pmount"); + if (!mountProg.isEmpty()) { + buffer.sprintf( "%s %s", TQFile::encodeName(mountProg).data(), + TQFile::encodeName(TDEProcess::quote(dev)).data() ); + } + } + + if (mountProg.isEmpty()) { + return false; + } + + int res = system( buffer.data() ); + + return res==0; +} + +bool FileProtocol::pumount(const TQString &point) +{ + TQString real_point = TDEStandardDirs::realPath(point); + + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + + KMountPoint::List::const_iterator it = mtab.begin(); + KMountPoint::List::const_iterator end = mtab.end(); + + TQString dev; + + for (; it!=end; ++it) + { + TQString tmp = (*it)->mountedFrom(); + TQString mp = (*it)->mountPoint(); + mp = TDEStandardDirs::realPath(mp); + + if (mp==real_point) + dev = TDEStandardDirs::realPath(tmp); + } + + if (dev.isEmpty()) return false; + if (dev.endsWith("/")) dev.truncate(dev.length()-1); + + TQString umountProg; + TQCString buffer; + +#ifdef WITH_UDISKS2 + // Use 'udisksctl' (UDISKS2), if available + if (umountProg.isEmpty()) { + umountProg = TDEGlobal::dirs()->findExe("udisksctl"); + if (!umountProg.isEmpty()) { + buffer.sprintf( "%s unmount -b %s", TQFile::encodeName(umountProg).data(), + TQFile::encodeName(TDEProcess::quote(dev)).data() ); + } + } +#endif // WITH_UDISKS2 + +#ifdef WITH_UDISKS + // Use 'udisks' (UDISKS1), if available + if (umountProg.isEmpty()) { + umountProg = TDEGlobal::dirs()->findExe("udisks"); + if (!umountProg.isEmpty()) { + buffer.sprintf( "%s --unmount %s", TQFile::encodeName(umountProg).data(), + TQFile::encodeName(TDEProcess::quote(dev)).data() ); + } + } +#endif // WITH_UDISKS + + // Use 'pumount', if available + if (umountProg.isEmpty()) { + umountProg = TDEGlobal::dirs()->findExe("pumount"); + if (!umountProg.isEmpty()) { + buffer.sprintf( "%s %s", TQFile::encodeName(umountProg).data(), + TQFile::encodeName(TDEProcess::quote(dev)).data() ); + } + } + + if (umountProg.isEmpty()) { + return false; + } + + int res = system( buffer.data() ); + + return res==0; +} + +/************************************* + * + * Utilities + * + *************************************/ + +static TQString testLogFile( const char *_filename ) +{ + char buffer[ 1024 ]; + KDE_struct_stat buff; + + TQString result; + + KDE_stat( _filename, &buff ); + int size = buff.st_size; + if ( size == 0 ) { + unlink( _filename ); + return result; + } + + FILE * f = KDE_fopen( _filename, "rb" ); + if ( f == 0L ) { + unlink( _filename ); + result = i18n("Could not read %1").arg(TQFile::decodeName(_filename)); + return result; + } + + result = ""; + const char *p = ""; + while ( p != 0L ) { + p = fgets( buffer, sizeof(buffer)-1, f ); + if ( p != 0L ) + result += TQString::fromLocal8Bit(buffer); + } + + fclose( f ); + + unlink( _filename ); + + return result; +} + +/************************************* + * + * ACL handling helpers + * + *************************************/ +#ifdef USE_POSIX_ACL + +static bool isExtendedACL( acl_t acl ) +{ + return ( acl_equiv_mode( acl, 0 ) != 0 ); +} + +static TQString aclAsString( acl_t acl ) +{ + char *aclString = acl_to_text( acl, 0 ); + TQString ret = TQString::fromLatin1( aclString ); + acl_free( (void*)aclString ); + return ret; +} + +static void appendACLAtoms( const TQCString & path, UDSEntry& entry, mode_t type, bool withACL ) +{ + // first check for a noop +#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS + if ( acl_extended_file( path.data() ) == 0 ) return; +#endif + + acl_t acl = 0; + acl_t defaultAcl = 0; + UDSAtom atom; + bool isDir = S_ISDIR( type ); + // do we have an acl for the file, and/or a default acl for the dir, if it is one? + if ( ( acl = acl_get_file( path.data(), ACL_TYPE_ACCESS ) ) ) { + if ( !isExtendedACL( acl ) ) { + acl_free( acl ); + acl = 0; + } + } + + /* Sadly libacl does not provided a means of checking for extended ACL and default + * ACL separately. Since a directory can have both, we need to check again. */ + if ( isDir ) + defaultAcl = acl_get_file( path.data(), ACL_TYPE_DEFAULT ); + + if ( acl || defaultAcl ) { + kdDebug(7101) << path.data() << " has extended ACL entries " << endl; + atom.m_uds = TDEIO::UDS_EXTENDED_ACL; + atom.m_long = 1; + entry.append( atom ); + } + if ( withACL ) { + if ( acl ) { + atom.m_uds = TDEIO::UDS_ACL_STRING; + atom.m_str = aclAsString( acl ); + entry.append( atom ); + kdDebug(7101) << path.data() << "ACL: " << atom.m_str << endl; + } + if ( defaultAcl ) { + atom.m_uds = TDEIO::UDS_DEFAULT_ACL_STRING; + atom.m_str = aclAsString( defaultAcl ); + entry.append( atom ); + kdDebug(7101) << path.data() << "DEFAULT ACL: " << atom.m_str << endl; + } + } + if ( acl ) acl_free( acl ); + if ( defaultAcl ) acl_free( defaultAcl ); +} +#endif + +#include "file.moc" diff --git a/tdeioslave/ftp/CMakeLists.txt b/tdeioslave/ftp/CMakeLists.txt index 7ef877e9d..797c5ae9c 100644 --- a/tdeioslave/ftp/CMakeLists.txt +++ b/tdeioslave/ftp/CMakeLists.txt @@ -38,7 +38,7 @@ tde_create_translated_desktop( set( target tdeio_ftp ) set( ${target}_SRCS - ftp.cc + ftp.cpp ) tde_add_kpart( ${target} AUTOMOC diff --git a/tdeioslave/ftp/Makefile.am b/tdeioslave/ftp/Makefile.am index 1421b8667..9d4984808 100644 --- a/tdeioslave/ftp/Makefile.am +++ b/tdeioslave/ftp/Makefile.am @@ -4,7 +4,7 @@ INCLUDES= $(all_includes) kde_module_LTLIBRARIES = tdeio_ftp.la -tdeio_ftp_la_SOURCES = ftp.cc +tdeio_ftp_la_SOURCES = ftp.cpp tdeio_ftp_la_LIBADD = $(LIB_TDEIO) $(LIB_QT) $(LIB_TDECORE) tdeio_ftp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) diff --git a/tdeioslave/ftp/ftp.cc b/tdeioslave/ftp/ftp.cc deleted file mode 100644 index 65f3eedc5..000000000 --- a/tdeioslave/ftp/ftp.cc +++ /dev/null @@ -1,2674 +0,0 @@ -/* This file is part of the KDE libraries - Copyright (C) 2000 David Faure - - 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. - - Recommended reading explaining FTP details and quirks: - http://cr.yp.to/ftp.html (by D.J. Bernstein) -*/ - - -#define TDEIO_FTP_PRIVATE_INCLUDE -#include "ftp.h" - -#include -#ifdef HAVE_SYS_TIME_H -#include -#endif -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if TIME_WITH_SYS_TIME -#include -#endif - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_STRTOLL - #define charToLongLong(a) strtoll(a, 0, 10) -#else - #define charToLongLong(a) strtol(a, 0, 10) -#endif - -// JPF: a remark on coding style (2004-03-06): -// Some calls to TQString::fromLatin1() were removed from the code. In most places -// the KDE code relies on implicit creation of QStrings. Also Qt has a lot of -// const char* overloads, so that using TQString::fromLatin1() can be ineffectient! - -#define FTP_LOGIN "anonymous" -#define FTP_PASSWD "anonymous@" - -//#undef kdDebug -#define ENABLE_CAN_RESUME - -// JPF: somebody should find a better solution for this or move this to TDEIO -// JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions! -namespace TDEIO { - enum buffersizes - { /** - * largest buffer size that should be used to transfer data between - * TDEIO slaves using the data() function - */ - maximumIpcSize = 32 * 1024, - /** - * this is a reasonable value for an initial read() that a TDEIO slave - * can do to obtain data via a slow network connection. - */ - initialIpcSize = 2 * 1024, - /** - * recommended size of a data block passed to findBufferFileType() - */ - mimimumMimeSize = 1024 - }; - - // JPF: this helper was derived from write_all in file.cc (FileProtocol). - static // JPF: in ftp.cc we make it static - /** - * This helper handles some special issues (blocking and interrupted - * system call) when writing to a file handle. - * - * @return 0 on success or an error code on failure (ERR_COULD_NOT_WRITE, - * ERR_DISK_FULL, ERR_CONNECTION_BROKEN). - */ - int WriteToFile(int fd, const char *buf, size_t len) - { - while (len > 0) - { // JPF: shouldn't there be a KDE_write? - ssize_t written = write(fd, buf, len); - if (written >= 0) - { buf += written; - len -= written; - continue; - } - switch(errno) - { case EINTR: continue; - case EPIPE: return ERR_CONNECTION_BROKEN; - case ENOSPC: return ERR_DISK_FULL; - default: return ERR_COULD_NOT_WRITE; - } - } - return 0; - } -} - -TDEIO::filesize_t Ftp::UnknownSize = (TDEIO::filesize_t)-1; - -using namespace TDEIO; - -extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } - -int kdemain( int argc, char **argv ) -{ - TDELocale::setMainCatalogue("tdelibs"); - TDEInstance instance( "tdeio_ftp" ); - ( void ) TDEGlobal::locale(); - - kdDebug(7102) << "Starting " << getpid() << endl; - - if (argc != 4) - { - fprintf(stderr, "Usage: tdeio_ftp protocol domain-socket1 domain-socket2\n"); - exit(-1); - } - - Ftp slave(argv[2], argv[3]); - slave.dispatchLoop(); - - kdDebug(7102) << "Done" << endl; - return 0; -} - -//=============================================================================== -// FtpTextReader Read Text lines from a file (or socket) -//=============================================================================== - -void FtpTextReader::textClear() -{ m_iTextLine = m_iTextBuff = 0; - m_szText[0] = 0; - m_bTextEOF = m_bTextTruncated = false; -} - -int FtpTextReader::textRead(FtpSocket *pSock) -{ - // if we have still buffered data then move it to the left - char* pEOL; - if(m_iTextLine < m_iTextBuff) - { m_iTextBuff -= m_iTextLine; - memmove(m_szText, m_szText+m_iTextLine, m_iTextBuff); - pEOL = (char*)memchr(m_szText, '\n', m_iTextBuff); // have a complete line? - } - else - { m_iTextBuff = 0; - pEOL = NULL; - } - m_bTextEOF = m_bTextTruncated = false; - - // read data from the control socket until a complete line is read - int nBytes; - while(pEOL == NULL) - { - if(m_iTextBuff > textReadLimit) - { m_bTextTruncated = true; - m_iTextBuff = textReadLimit; - } - nBytes = pSock->read(m_szText+m_iTextBuff, sizeof(m_szText)-m_iTextBuff); - if(nBytes <= 0) - { - // This error can occur after the server closed the connection (after a timeout) - if(nBytes < 0) - pSock->debugMessage("textRead failed"); - m_bTextEOF = true; - pEOL = m_szText + m_iTextBuff; - } - else - { - m_iTextBuff += nBytes; - pEOL = (char*)memchr(m_szText, '\n', m_iTextBuff); - } - } - - nBytes = pEOL - m_szText; - m_iTextLine = nBytes + 1; - - if(nBytes > textReadLimit) - { m_bTextTruncated = true; - nBytes = textReadLimit; - } - if(nBytes && m_szText[nBytes-1] == '\r') - nBytes--; - m_szText[nBytes] = 0; - return nBytes; -} - -//=============================================================================== -// FtpSocket Helper Class for Data or Control Connections -//=============================================================================== -void FtpSocket::debugMessage(const char* pszMsg) const -{ - kdDebug(7102) << m_pszName << ": " << pszMsg << endl; -} - -int FtpSocket::errorMessage(int iErrorCode, const char* pszMsg) const -{ - kdError(7102) << m_pszName << ": " << pszMsg << endl; - return iErrorCode; -} - -int FtpSocket::connectSocket(int iTimeOutSec, bool bControl) -{ - closeSocket(); - - int iOpt = bControl ? KExtendedSocket::inetSocket - : KExtendedSocket::noResolve; - setSocketFlags(iOpt | socketFlags()); - setTimeout(iTimeOutSec); - - int iCon = KExtendedSocket::connect(); - if(iCon < 0) - { int iErrorCode = (status() == IO_LookupError) - ? ERR_UNKNOWN_HOST : ERR_COULD_NOT_CONNECT; - TQString strMsg = KExtendedSocket::strError(status(), systemError()); - strMsg.prepend("connect failed (code %1): "); - return errorMessage(iErrorCode, TQString(strMsg.arg(iCon)).latin1()); - } - if( !setAddressReusable(true) ) - return errorMessage(ERR_COULD_NOT_CREATE_SOCKET, "setAddressReusable failed"); - - if(!bControl) - { int on=1; - if( !setSocketOption(SO_KEEPALIVE, (char *)&on, sizeof(on)) ) - errorMessage(0, "Keepalive not allowed"); - - struct linger lng = { 1, 120 }; - if( !setSocketOption(SO_LINGER, (char *)&lng, sizeof (lng)) ) - errorMessage(0, "Linger mode was not allowed."); - } - - debugMessage("connected"); - return 0; -} - -void FtpSocket::closeSocket() -{ - if(m_server != -1 || fd() != -1) - debugMessage("disconnected"); - - if(m_server != -1) - { - ::shutdown(m_server, SHUT_RDWR); - ::close(m_server); - m_server = -1; - } - if(socketStatus() > nothing) - reset(); - textClear(); -} - -bool FtpSocket::setSocketOption(int opt, char*arg, socklen_t len) const -{ - return (setsockopt(sock(), SOL_SOCKET, opt, arg, len) != -1); -} - -//=============================================================================== -// Ftp -//=============================================================================== - -Ftp::Ftp( const TQCString &pool, const TQCString &app ) - : SlaveBase( "ftp", pool, app ) -{ - // init the socket data - m_data = m_control = NULL; - ftpCloseControlConnection(); - - // init other members - m_port = 0; - kdDebug(7102) << "Ftp::Ftp()" << endl; -} - - -Ftp::~Ftp() -{ - kdDebug(7102) << "Ftp::~Ftp()" << endl; - closeConnection(); -} - -/** - * This closes a data connection opened by ftpOpenDataConnection(). - */ -void Ftp::ftpCloseDataConnection() -{ - if(m_data != NULL) - { delete m_data; - m_data = NULL; - } -} - -/** - * This closes a control connection opened by ftpOpenControlConnection() and reinits the - * related states. This method gets called from the constructor with m_control = NULL. - */ -void Ftp::ftpCloseControlConnection() -{ - m_extControl = 0; - if(m_control) - delete m_control; - m_control = NULL; - m_cDataMode = 0; - m_bLoggedOn = false; // logon needs control connction - m_bTextMode = false; - m_bBusy = false; -} - -/** - * Returns the last response from the server (iOffset >= 0) -or- reads a new response - * (iOffset < 0). The result is returned (with iOffset chars skipped for iOffset > 0). - */ -const char* Ftp::ftpResponse(int iOffset) -{ - assert(m_control != NULL); // must have control connection socket - const char *pTxt = m_control->textLine(); - - // read the next line ... - if(iOffset < 0) - { - int iMore = 0; - m_iRespCode = 0; - - // If the server sends multiline responses "nnn-text" we loop here until - // a final "nnn text" line is reached. Only data from the final line will - // be stored. Some servers (OpenBSD) send a single "nnn-" followed by - // optional lines that start with a space and a final "nnn text" line. - do { - int nBytes = m_control->textRead(); - int iCode = atoi(pTxt); - if(iCode > 0) m_iRespCode = iCode; - - // ignore lines starting with a space in multiline response - if(iMore != 0 && pTxt[0] == 32) - ; - // otherwise the line should start with "nnn-" or "nnn " - else if(nBytes < 4 || iCode < 100) - iMore = 0; - // we got a valid line, now check for multiline responses ... - else if(iMore == 0 && pTxt[3] == '-') - iMore = iCode; - // "nnn " ends multiline mode ... - else if(iMore != 0 && (iMore != iCode || pTxt[3] != '-')) - iMore = 0; - - if(iMore != 0) - kdDebug(7102) << " > " << pTxt << endl; - } while(iMore != 0); - kdDebug(7102) << "resp> " << pTxt << endl; - - m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0; - } - - // return text with offset ... - while(iOffset-- > 0 && pTxt[0]) - pTxt++; - return pTxt; -} - - -void Ftp::closeConnection() -{ - if(m_control != NULL || m_data != NULL) - kdDebug(7102) << "Ftp::closeConnection m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy << endl; - - if(m_bBusy) // ftpCloseCommand not called - { - kdWarning(7102) << "Ftp::closeConnection Abandoned data stream" << endl; - ftpCloseDataConnection(); - } - - if(m_bLoggedOn) // send quit - { - if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) ) - kdWarning(7102) << "Ftp::closeConnection QUIT returned error: " << m_iRespCode << endl; - } - - // close the data and control connections ... - ftpCloseDataConnection(); - ftpCloseControlConnection(); -} - -void Ftp::setHost( const TQString& _host, int _port, const TQString& _user, - const TQString& _pass ) -{ - kdDebug(7102) << "Ftp::setHost (" << getpid() << "): " << _host << endl; - - m_proxyURL = metaData("UseProxy"); - m_bUseProxy = (m_proxyURL.isValid() && m_proxyURL.protocol() == "ftp"); - - if ( m_host != _host || m_port != _port || - m_user != _user || m_pass != _pass ) - closeConnection(); - - m_host = _host; - m_port = _port; - m_user = _user; - m_pass = _pass; -} - -void Ftp::openConnection() -{ - ftpOpenConnection(loginExplicit); -} - -bool Ftp::ftpOpenConnection (LoginMode loginMode) -{ - // check for implicit login if we are already logged on ... - if(loginMode == loginImplicit && m_bLoggedOn) - { - assert(m_control != NULL); // must have control connection socket - return true; - } - - kdDebug(7102) << "ftpOpenConnection " << m_host << ":" << m_port << " " - << m_user << " [password hidden]" << endl; - - infoMessage( i18n("Opening connection to host %1").arg(m_host) ); - - if ( m_host.isEmpty() ) - { - error( ERR_UNKNOWN_HOST, TQString::null ); - return false; - } - - assert( !m_bLoggedOn ); - - m_initialPath = TQString::null; - m_currentPath = TQString::null; - - TQString host = m_bUseProxy ? m_proxyURL.host() : m_host; - unsigned short int port = m_bUseProxy ? m_proxyURL.port() : m_port; - - if (!ftpOpenControlConnection(host, port) ) - return false; // error emitted by ftpOpenControlConnection - infoMessage( i18n("Connected to host %1").arg(m_host) ); - - if(loginMode != loginDefered) - { - m_bLoggedOn = ftpLogin(); - if( !m_bLoggedOn ) - return false; // error emitted by ftpLogin - } - - m_bTextMode = config()->readBoolEntry("textmode", false); - connected(); - return true; -} - - -/** - * Called by @ref openConnection. It opens the control connection to the ftp server. - * - * @return true on success. - */ -bool Ftp::ftpOpenControlConnection( const TQString &host, unsigned short int port ) -{ - if ( port == 0 ) { - struct servent *pse; - if ( ( pse = getservbyname( "ftp", "tcp" ) ) == NULL ) - port = 21; - else - port = ntohs(pse->s_port); - } - - // implicitly close, then try to open a new connection ... - closeConnection(); - int iErrorCode = ERR_OUT_OF_MEMORY; - TQString sErrorMsg; - m_control = new FtpSocket("CNTL"); - if(m_control != NULL) - { - // now connect to the server and read the login message ... - m_control->setAddress(host, port); - iErrorCode = m_control->connectSocket(connectTimeout(), true); - sErrorMsg = host; - - // on connect success try to read the server message... - if(iErrorCode == 0) - { - const char* psz = ftpResponse(-1); - if(m_iRespType != 2) - { // login not successful, do we have an message text? - if(psz[0]) - sErrorMsg = i18n("%1.\n\nReason: %2").arg(host).arg(psz); - iErrorCode = ERR_COULD_NOT_CONNECT; - } - } - } - - // if there was a problem - report it ... - if(iErrorCode == 0) // OK, return success - return true; - closeConnection(); // clean-up on error - error(iErrorCode, sErrorMsg); - return false; -} - -/** - * Called by @ref openConnection. It logs us in. - * @ref m_initialPath is set to the current working directory - * if logging on was successful. - * - * @return true on success. - */ -bool Ftp::ftpLogin() -{ - infoMessage( i18n("Sending login information") ); - - assert( !m_bLoggedOn ); - - TQString user = m_user; - TQString pass = m_pass; - - if ( config()->readBoolEntry("EnableAutoLogin") ) - { - TQString au = config()->readEntry("autoLoginUser"); - if ( !au.isEmpty() ) - { - user = au; - pass = config()->readEntry("autoLoginPass"); - } - } - - // Try anonymous login if both username/password - // information is blank. - if (user.isEmpty() && pass.isEmpty()) - { - user = FTP_LOGIN; - pass = FTP_PASSWD; - } - - AuthInfo info; - info.url.setProtocol( "ftp" ); - info.url.setHost( m_host ); - info.url.setPort( m_port ); - info.url.setUser( user ); - - TQCString tempbuf; - int failedAuth = 0; - - do - { - // Check the cache and/or prompt user for password if 1st - // login attempt failed OR the user supplied a login name, - // but no password. - if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) ) - { - TQString errorMsg; - kdDebug(7102) << "Prompting user for login info..." << endl; - - // Ask user if we should retry after when login fails! - if( failedAuth > 0 ) - { - errorMsg = i18n("Message sent:\nLogin using username=%1 and " - "password=[hidden]\n\nServer replied:\n%2\n\n" - ).arg(user).arg(ftpResponse(0)); - } - - if ( user != FTP_LOGIN ) - info.username = user; - - info.prompt = i18n("You need to supply a username and a password " - "to access this site."); - info.commentLabel = i18n( "Site:" ); - info.comment = i18n("%1").arg( m_host ); - info.keepPassword = true; // Prompt the user for persistence as well. - info.readOnly = (!m_user.isEmpty() && m_user != FTP_LOGIN); - - bool disablePassDlg = config()->readBoolEntry( "DisablePassDlg", false ); - if ( disablePassDlg || !openPassDlg( info, errorMsg ) ) - { - error( ERR_USER_CANCELED, m_host ); - return false; - } - else - { - user = info.username; - pass = info.password; - } - } - - tempbuf = "USER "; - tempbuf += user.latin1(); - if ( m_bUseProxy ) - { - tempbuf += '@'; - tempbuf += m_host.latin1(); - if ( m_port > 0 && m_port != DEFAULT_FTP_PORT ) - { - tempbuf += ':'; - tempbuf += TQString::number(m_port).latin1(); - } - } - - kdDebug(7102) << "Sending Login name: " << tempbuf << endl; - - bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) ); - bool needPass = (m_iRespCode == 331); - // Prompt user for login info if we do not - // get back a "230" or "331". - if ( !loggedIn && !needPass ) - { - kdDebug(7102) << "Login failed: " << ftpResponse(0) << endl; - ++failedAuth; - continue; // Well we failed, prompt the user please!! - } - - if( needPass ) - { - tempbuf = "pass "; - tempbuf += pass.latin1(); - kdDebug(7102) << "Sending Login password: " << "[protected]" << endl; - loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) ); - } - - if ( loggedIn ) - { - // Do not cache the default login!! - if( user != FTP_LOGIN && pass != FTP_PASSWD ) - cacheAuthentication( info ); - failedAuth = -1; - } - - } while( ++failedAuth ); - - - kdDebug(7102) << "Login OK" << endl; - infoMessage( i18n("Login OK") ); - - // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix: - // Thanks to jk@soegaard.net (Jens Kristian Søgaard) for this hint - if( ftpSendCmd("SYST") && (m_iRespType == 2) ) - { - if( !strncmp( ftpResponse(0), "215 Windows_NT", 14 ) ) // should do for any version - { - ftpSendCmd( "site dirstyle" ); - // Check if it was already in Unix style - // Patch from Keith Refson - if( !strncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 )) - //It was in Unix style already! - ftpSendCmd( "site dirstyle" ); - // windows won't support chmod before KDE konquers their desktop... - m_extControl |= chmodUnknown; - } - } - else - kdWarning(7102) << "SYST failed" << endl; - - if ( config()->readBoolEntry ("EnableAutoLoginMacro") ) - ftpAutoLoginMacro (); - - // Get the current working directory - kdDebug(7102) << "Searching for pwd" << endl; - if( !ftpSendCmd("PWD") || (m_iRespType != 2) ) - { - kdDebug(7102) << "Couldn't issue pwd command" << endl; - error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.").arg(m_host) ); // or anything better ? - return false; - } - - TQString sTmp = remoteEncoding()->decode( ftpResponse(3) ); - int iBeg = sTmp.find('"'); - int iEnd = sTmp.findRev('"'); - if(iBeg > 0 && iBeg < iEnd) - { - m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1); - if(m_initialPath[0] != '/') m_initialPath.prepend('/'); - kdDebug(7102) << "Initial path set to: " << m_initialPath << endl; - m_currentPath = m_initialPath; - } - return true; -} - -void Ftp::ftpAutoLoginMacro () -{ - TQString macro = metaData( "autoLoginMacro" ); - - if ( macro.isEmpty() ) - return; - - TQStringList list = TQStringList::split('\n', macro); - - for(TQStringList::Iterator it = list.begin() ; it != list.end() ; ++it ) - { - if ( (*it).startsWith("init") ) - { - list = TQStringList::split( '\\', macro); - it = list.begin(); - ++it; // ignore the macro name - - for( ; it != list.end() ; ++it ) - { - // TODO: Add support for arbitrary commands - // besides simply changing directory!! - if ( (*it).startsWith( "cwd" ) ) - ftpFolder( (*it).mid(4).stripWhiteSpace(), false ); - } - - break; - } - } -} - - -/** - * ftpSendCmd - send a command (@p cmd) and read response - * - * @param maxretries number of time it should retry. Since it recursively - * calls itself if it can't read the answer (this happens especially after - * timeouts), we need to limit the recursiveness ;-) - * - * return true if any response received, false on error - */ -bool Ftp::ftpSendCmd( const TQCString& cmd, int maxretries ) -{ - assert(m_control != NULL); // must have control connection socket - - if ( cmd.find( '\r' ) != -1 || cmd.find( '\n' ) != -1) - { - kdWarning(7102) << "Invalid command received (contains CR or LF):" - << cmd.data() << endl; - error( ERR_UNSUPPORTED_ACTION, m_host ); - return false; - } - - // Don't print out the password... - bool isPassCmd = (cmd.left(4).lower() == "pass"); - if ( !isPassCmd ) - kdDebug(7102) << "send> " << cmd.data() << endl; - else - kdDebug(7102) << "send> pass [protected]" << endl; - - // Send the message... - TQCString buf = cmd; - buf += "\r\n"; // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html - int num = m_control->write(buf.data(), buf.length()); - - // If we were able to successfully send the command, then we will - // attempt to read the response. Otherwise, take action to re-attempt - // the login based on the maximum number of retires specified... - if( num > 0 ) - ftpResponse(-1); - else - { m_iRespType = m_iRespCode = 0; - m_control->textClear(); - } - - // If respCh is NULL or the response is 421 (Timed-out), we try to re-send - // the command based on the value of maxretries. - if( (m_iRespType <= 0) || (m_iRespCode == 421) ) - { - // We have not yet logged on... - if (!m_bLoggedOn) - { - // The command was sent from the ftpLogin function, i.e. we are actually - // attempting to login in. NOTE: If we already sent the username, we - // return false and let the user decide whether (s)he wants to start from - // the beginning... - if (maxretries > 0 && !isPassCmd) - { - closeConnection (); - if( ftpOpenConnection(loginDefered) ) - ftpSendCmd ( cmd, maxretries - 1 ); - } - - return false; - } - else - { - if ( maxretries < 1 ) - return false; - else - { - kdDebug(7102) << "Was not able to communicate with " << m_host << endl - << "Attempting to re-establish connection." << endl; - - closeConnection(); // Close the old connection... - openConnection(); // Attempt to re-establish a new connection... - - if (!m_bLoggedOn) - { - if (m_control != NULL) // if openConnection succeeded ... - { - kdDebug(7102) << "Login failure, aborting" << endl; - error (ERR_COULD_NOT_LOGIN, m_host); - closeConnection (); - } - return false; - } - - kdDebug(7102) << "Logged back in, re-issuing command" << endl; - - // If we were able to login, resend the command... - if (maxretries) - maxretries--; - - return ftpSendCmd( cmd, maxretries ); - } - } - } - - return true; -} - -/* - * ftpOpenPASVDataConnection - set up data connection, using PASV mode - * - * return 1 if successful, 0 otherwise - * doesn't set error message, since non-pasv mode will always be tried if - * this one fails - */ -int Ftp::ftpOpenPASVDataConnection() -{ - assert(m_control != NULL); // must have control connection socket - assert(m_data == NULL); // ... but no data connection - - // Check that we can do PASV - const TDESocketAddress *sa = m_control->peerAddress(); - if (sa != NULL && sa->family() != PF_INET) - return ERR_INTERNAL; // no PASV for non-PF_INET connections - - const KInetSocketAddress *sin = static_cast(sa); - - if (m_extControl & pasvUnknown) - return ERR_INTERNAL; // already tried and got "unknown command" - - m_bPasv = true; - - /* Let's PASsiVe*/ - if( !ftpSendCmd("PASV") || (m_iRespType != 2) ) - { - kdDebug(7102) << "PASV attempt failed" << endl; - // unknown command? - if( m_iRespType == 5 ) - { - kdDebug(7102) << "disabling use of PASV" << endl; - m_extControl |= pasvUnknown; - } - return ERR_INTERNAL; - } - - // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)' - // but anonftpd gives '227 =160,39,200,55,6,245' - int i[6]; - const char *start = strchr(ftpResponse(3), '('); - if ( !start ) - start = strchr(ftpResponse(3), '='); - if ( !start || - ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 && - sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) ) - { - kdError(7102) << "parsing IP and port numbers failed. String parsed: " << start << endl; - return ERR_INTERNAL; - } - - // Make hostname and port number ... - int port = i[4] << 8 | i[5]; - - // we ignore the host part on purpose for two reasons - // a) it might be wrong anyway - // b) it would make us being suceptible to a port scanning attack - - // now connect the data socket ... - m_data = new FtpSocket("PASV"); - m_data->setAddress(sin->nodeName(), port); - - kdDebug(7102) << "Connecting to " << sin->nodeName() << " on port " << port << endl; - return m_data->connectSocket(connectTimeout(), false); -} - -/* - * ftpOpenEPSVDataConnection - opens a data connection via EPSV - */ -int Ftp::ftpOpenEPSVDataConnection() -{ - assert(m_control != NULL); // must have control connection socket - assert(m_data == NULL); // ... but no data connection - - const TDESocketAddress *sa = m_control->peerAddress(); - int portnum; - // we are sure sa is a KInetSocketAddress, because we asked for KExtendedSocket::inetSocket - // when we connected - const KInetSocketAddress *sin = static_cast(sa); - - if (m_extControl & epsvUnknown || sa == NULL) - return ERR_INTERNAL; - - m_bPasv = true; - if( !ftpSendCmd("EPSV") || (m_iRespType != 2) ) - { - // unknown command? - if( m_iRespType == 5 ) - { - kdDebug(7102) << "disabling use of EPSV" << endl; - m_extControl |= epsvUnknown; - } - return ERR_INTERNAL; - } - - const char *start = strchr(ftpResponse(3), '|'); - if ( !start || sscanf(start, "|||%d|", &portnum) != 1) - return ERR_INTERNAL; - - m_data = new FtpSocket("EPSV"); - m_data->setAddress(sin->nodeName(), portnum); - return m_data->connectSocket(connectTimeout(), false) != 0; -} - -/* - * ftpOpenEPRTDataConnection - * @return 0 on success, ERR_INTERNAL if mode not acceptable -or- a fatal error code - */ -int Ftp::ftpOpenEPRTDataConnection() -{ - assert(m_control != NULL); // must have control connection socket - assert(m_data == NULL); // ... but no data connection - - // yes, we are sure this is a KInetSocketAddress - const KInetSocketAddress *sin = static_cast(m_control->localAddress()); - m_bPasv = false; - if (m_extControl & eprtUnknown || sin == NULL) - return ERR_INTERNAL; - - m_data = new FtpSocket("EPRT"); - m_data->setHost(sin->nodeName()); - m_data->setPort(0); // setting port to 0 will make us bind to a random, free port - m_data->setSocketFlags(KExtendedSocket::noResolve | KExtendedSocket::passiveSocket | - KExtendedSocket::inetSocket); - - if (m_data->listen(1) < 0) - return ERR_COULD_NOT_LISTEN; - - sin = static_cast(m_data->localAddress()); - if (sin == NULL) - return ERR_INTERNAL; - - // TQString command = TQString::fromLatin1("eprt |%1|%2|%3|").arg(sin->ianaFamily()) - // .arg(sin->nodeName()) - // .arg(sin->port()); - TQCString command; - command.sprintf("eprt |%d|%s|%d|", sin->ianaFamily(), - sin->nodeName().latin1(), sin->port()); - - // FIXME! Encoding for hostnames? - if( ftpSendCmd(command) && (m_iRespType == 2) ) - return 0; - - // unknown command? - if( m_iRespType == 5 ) - { - kdDebug(7102) << "disabling use of EPRT" << endl; - m_extControl |= eprtUnknown; - } - return ERR_INTERNAL; -} - -/* - * ftpOpenDataConnection - set up data connection - * - * The routine calls several ftpOpenXxxxConnection() helpers to find - * the best connection mode. If a helper cannot connect if returns - * ERR_INTERNAL - so this is not really an error! All other error - * codes are treated as fatal, e.g. they are passed back to the caller - * who is responsible for calling error(). ftpOpenPortDataConnection - * can be called as last try and it does never return ERR_INTERNAL. - * - * @return 0 if successful, err code otherwise - */ -int Ftp::ftpOpenDataConnection() -{ - // make sure that we are logged on and have no data connection... - assert( m_bLoggedOn ); - ftpCloseDataConnection(); - - int iErrCode = 0; - int iErrCodePASV = 0; // Remember error code from PASV - - // First try passive (EPSV & PASV) modes - if( !config()->readBoolEntry("DisablePassiveMode", false) ) - { - iErrCode = ftpOpenPASVDataConnection(); - if(iErrCode == 0) - return 0; // success - iErrCodePASV = iErrCode; - ftpCloseDataConnection(); - - if( !config()->readBoolEntry("DisableEPSV", false) ) - { - iErrCode = ftpOpenEPSVDataConnection(); - if(iErrCode == 0) - return 0; // success - ftpCloseDataConnection(); - } - - // if we sent EPSV ALL already and it was accepted, then we can't - // use active connections any more - if (m_extControl & epsvAllSent) - return iErrCodePASV ? iErrCodePASV : iErrCode; - } - - if( !config()->readBoolEntry("DisableEPRT", false) ) - { - iErrCode = ftpOpenEPRTDataConnection(); - if(iErrCode == 0) - return 0; // success - ftpCloseDataConnection(); - } - - // fall back to port mode - iErrCode = ftpOpenPortDataConnection(); - if(iErrCode == 0) - return 0; // success - - ftpCloseDataConnection(); - // prefer to return the error code from PASV if any, since that's what should have worked in the first place - return iErrCodePASV ? iErrCodePASV : iErrCode; -} - -/* - * ftpOpenPortDataConnection - set up data connection - * - * @return 0 if successfull, err code otherwise (but never ERR_INTERNAL - * because this is the last connection mode that is tried) - */ -int Ftp::ftpOpenPortDataConnection() -{ - assert(m_control != NULL); // must have control connection socket - assert(m_data == NULL); // ... but no data connection - - m_bPasv = false; - - // create a socket, bind it and let it listen ... - m_data = new FtpSocket("PORT"); - m_data->setSocketFlags(KExtendedSocket::noResolve | KExtendedSocket::passiveSocket | - KExtendedSocket::inetSocket); - - // yes, we are sure this is a KInetSocketAddress - const KInetSocketAddress* pAddr = static_cast(m_control->localAddress()); - m_data->setAddress(pAddr->nodeName(), "0"); - m_data->setAddressReusable(true); - - if(m_data->listen(1) < 0) - return ERR_COULD_NOT_LISTEN; - struct linger lng = { 0, 0 }; - if ( !m_data->setSocketOption(SO_LINGER, (char*)&lng, sizeof(lng)) ) - return ERR_COULD_NOT_CREATE_SOCKET; - - // send the PORT command ... - pAddr = static_cast(m_data->localAddress()); - struct sockaddr* psa = (struct sockaddr*)pAddr->addressV4(); - unsigned char* pData = (unsigned char*)(psa->sa_data); - TQCString portCmd; - portCmd.sprintf("port %d,%d,%d,%d,%d,%d", - pData[2], pData[3], pData[4], pData[5], pData[0], pData[1]); - if( ftpSendCmd(portCmd) && (m_iRespType == 2) ) - return 0; - return ERR_COULD_NOT_CONNECT; -} - -/* - * ftpAcceptConnect - wait for incoming connection - * Used by @ref ftpOpenCommand - * - * return false on error or timeout - */ -int Ftp::ftpAcceptConnect() -{ - assert(m_data != NULL); - - if ( m_bPasv ) - { - m_data->setServer(-1); - return true; - } - - int sSock = m_data->fd(); - struct sockaddr addr; - for(;;) - { - fd_set mask; - FD_ZERO(&mask); - FD_SET(sSock,&mask); - int r = KSocks::self()->select(sSock + 1, &mask, NULL, NULL, 0L); - if( r < 0 && errno != EINTR && errno != EAGAIN ) - continue; - if( r > 0 ) - break; - } - - ksocklen_t l = sizeof(addr); - m_data->setServer( KSocks::self()->accept(sSock, &addr, &l) ); - return (m_data->server() != -1); -} - -bool Ftp::ftpOpenCommand( const char *_command, const TQString & _path, char _mode, - int errorcode, TDEIO::fileoffset_t _offset ) -{ - int errCode = 0; - if( !ftpDataMode(_mode) ) - errCode = ERR_COULD_NOT_CONNECT; - else - errCode = ftpOpenDataConnection(); - - if(errCode != 0) - { - error(errCode, m_host); - return false; - } - - if ( _offset > 0 ) { - // send rest command if offset > 0, this applies to retr and stor commands - char buf[100]; - sprintf(buf, "rest %lld", _offset); - if ( !ftpSendCmd( buf ) ) - return false; - if( m_iRespType != 3 ) - { - error( ERR_CANNOT_RESUME, _path ); // should never happen - return false; - } - } - - TQCString tmp = _command; - TQString errormessage; - - if ( !_path.isEmpty() ) { - tmp += " "; - tmp += remoteEncoding()->encode(_path); - } - - if( !ftpSendCmd( tmp ) || (m_iRespType != 1) ) - { - if( _offset > 0 && strcmp(_command, "retr") == 0 && (m_iRespType == 4) ) - errorcode = ERR_CANNOT_RESUME; - // The error here depends on the command - errormessage = _path; - } - - else - { - // Only now we know for sure that we can resume - if ( _offset > 0 && strcmp(_command, "retr") == 0 ) - canResume(); - - if( ftpAcceptConnect() ) - { m_bBusy = true; // cleared in ftpCloseCommand - return true; - } - errorcode = ERR_COULD_NOT_ACCEPT; - } - - error(errorcode, errormessage); - return false; -} - - -bool Ftp::ftpCloseCommand() -{ - // first close data sockets (if opened), then read response that - // we got for whatever was used in ftpOpenCommand ( should be 226 ) - if(m_data) - { - delete m_data; - m_data = NULL; - } - if(!m_bBusy) - return true; - - kdDebug(7102) << "ftpCloseCommand: reading command result" << endl; - m_bBusy = false; - - if(!ftpResponse(-1) || (m_iRespType != 2) ) - { - kdDebug(7102) << "ftpCloseCommand: no transfer complete message" << endl; - return false; - } - return true; -} - -void Ftp::mkdir( const KURL & url, int permissions ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - TQString path = remoteEncoding()->encode(url); - TQCString buf = "mkd "; - buf += remoteEncoding()->encode(path); - - if( !ftpSendCmd( buf ) || (m_iRespType != 2) ) - { - TQString currentPath( m_currentPath ); - - // Check whether or not mkdir failed because - // the directory already exists... - if( ftpFolder( path, false ) ) - { - error( ERR_DIR_ALREADY_EXIST, path ); - // Change the directory back to what it was... - (void) ftpFolder( currentPath, false ); - return; - } - - error( ERR_COULD_NOT_MKDIR, path ); - return; - } - - if ( permissions != -1 ) - { - // chmod the dir we just created, ignoring errors. - (void) ftpChmod( path, permissions ); - } - - finished(); -} - -void Ftp::rename( const KURL& src, const KURL& dst, bool overwrite ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - // The actual functionality is in ftpRename because put needs it - if ( ftpRename( src.path(), dst.path(), overwrite ) ) - finished(); - else - error( ERR_CANNOT_RENAME, src.path() ); -} - -bool Ftp::ftpRename( const TQString & src, const TQString & dst, bool overwrite ) -{ - assert( m_bLoggedOn ); - - // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793). - if (!overwrite) { - if (ftpSize(dst, 'I')) { - error(ERR_FILE_ALREADY_EXIST, dst); - return false; - } - } - if (ftpFolder(dst, false)) { - error(ERR_DIR_ALREADY_EXIST, dst); - return false; - } - - // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793). - if (ftpFileExists(dst)) { - error(ERR_FILE_ALREADY_EXIST, dst); - return false; - } - if (ftpFolder(dst, false)) { - error(ERR_DIR_ALREADY_EXIST, dst); - return false; - } - - int pos = src.findRev("/"); - if( !ftpFolder(src.left(pos+1), false) ) - return false; - - TQCString from_cmd = "RNFR "; - from_cmd += remoteEncoding()->encode(src.mid(pos+1)); - if( !ftpSendCmd( from_cmd ) || (m_iRespType != 3) ) - return false; - - TQCString to_cmd = "RNTO "; - to_cmd += remoteEncoding()->encode(dst); - if( !ftpSendCmd( to_cmd ) || (m_iRespType != 2) ) - return false; - - return true; -} - -void Ftp::del( const KURL& url, bool isfile ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - // When deleting a directory, we must exit from it first - // The last command probably went into it (to stat it) - if ( !isfile ) - ftpFolder(remoteEncoding()->directory(url), false); // ignore errors - - TQCString cmd = isfile ? "DELE " : "RMD "; - cmd += remoteEncoding()->encode(url); - - if( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) - error( ERR_CANNOT_DELETE, url.path() ); - else - finished(); -} - -bool Ftp::ftpChmod( const TQString & path, int permissions ) -{ - assert( m_bLoggedOn ); - - if(m_extControl & chmodUnknown) // previous errors? - return false; - - // we need to do bit AND 777 to get permissions, in case - // we were sent a full mode (unlikely) - TQCString cmd; - cmd.sprintf("SITE CHMOD %o ", permissions & 511 ); - cmd += remoteEncoding()->encode(path); - - ftpSendCmd(cmd); - if(m_iRespType == 2) - return true; - - if(m_iRespCode == 500) - { - m_extControl |= chmodUnknown; - kdDebug(7102) << "ftpChmod: CHMOD not supported - disabling"; - } - return false; -} - -void Ftp::chmod( const KURL & url, int permissions ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - if ( !ftpChmod( url.path(), permissions ) ) - error( ERR_CANNOT_CHMOD, url.path() ); - else - finished(); -} - -void Ftp::ftpCreateUDSEntry( const TQString & filename, FtpEntry& ftpEnt, UDSEntry& entry, bool isDir ) -{ - assert(entry.count() == 0); // by contract :-) - UDSAtom atom; - atom.m_uds = UDS_NAME; - atom.m_str = filename; - entry.append( atom ); - - atom.m_uds = UDS_SIZE; - atom.m_long = ftpEnt.size; - entry.append( atom ); - - atom.m_uds = UDS_MODIFICATION_TIME; - atom.m_long = ftpEnt.date; - entry.append( atom ); - - atom.m_uds = UDS_ACCESS; - atom.m_long = ftpEnt.access; - entry.append( atom ); - - atom.m_uds = UDS_USER; - atom.m_str = ftpEnt.owner; - entry.append( atom ); - - if ( !ftpEnt.group.isEmpty() ) - { - atom.m_uds = UDS_GROUP; - atom.m_str = ftpEnt.group; - entry.append( atom ); - } - - if ( !ftpEnt.link.isEmpty() ) - { - atom.m_uds = UDS_LINK_DEST; - atom.m_str = ftpEnt.link; - entry.append( atom ); - - KMimeType::Ptr mime = KMimeType::findByURL( KURL("ftp://host/" + filename ) ); - // Links on ftp sites are often links to dirs, and we have no way to check - // that. Let's do like Netscape : assume dirs generally. - // But we do this only when the mimetype can't be known from the filename. - // --> we do better than Netscape :-) - if ( mime->name() == KMimeType::defaultMimeType() ) - { - kdDebug(7102) << "Setting guessed mime type to inode/directory for " << filename << endl; - atom.m_uds = UDS_GUESSED_MIME_TYPE; - atom.m_str = "inode/directory"; - entry.append( atom ); - isDir = true; - } - } - - atom.m_uds = UDS_FILE_TYPE; - atom.m_long = isDir ? S_IFDIR : ftpEnt.type; - entry.append( atom ); - - /* atom.m_uds = UDS_ACCESS_TIME; - atom.m_long = buff.st_atime; - entry.append( atom ); - - atom.m_uds = UDS_CREATION_TIME; - atom.m_long = buff.st_ctime; - entry.append( atom ); */ -} - - -void Ftp::ftpShortStatAnswer( const TQString& filename, bool isDir ) -{ - UDSEntry entry; - UDSAtom atom; - - atom.m_uds = TDEIO::UDS_NAME; - atom.m_str = filename; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = isDir ? S_IFDIR : S_IFREG; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - entry.append( atom ); - - // No details about size, ownership, group, etc. - - statEntry(entry); - finished(); -} - -void Ftp::ftpStatAnswerNotFound( const TQString & path, const TQString & filename ) -{ - // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source") - // When e.g. uploading a file, we still need stat() to return "not found" - // when the file doesn't exist. - TQString statSide = metaData("statSide"); - kdDebug(7102) << "Ftp::stat statSide=" << statSide << endl; - if ( statSide == "source" ) - { - kdDebug(7102) << "Not found, but assuming found, because some servers don't allow listing" << endl; - // MS Server is incapable of handling "list " in a case insensitive way - // But "retr " works. So lie in stat(), to get going... - // - // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run - // where listing permissions are denied, but downloading is still possible. - ftpShortStatAnswer( filename, false /*file, not dir*/ ); - - return; - } - - error( ERR_DOES_NOT_EXIST, path ); -} - -void Ftp::stat( const KURL &url) -{ - kdDebug(7102) << "Ftp::stat : path='" << url.path() << "'" << endl; - if( !ftpOpenConnection(loginImplicit) ) - return; - - TQString path = TQDir::cleanDirPath( url.path() ); - kdDebug(7102) << "Ftp::stat : cleaned path='" << path << "'" << endl; - - // We can't stat root, but we know it's a dir. - if( path.isEmpty() || path == "/" ) - { - UDSEntry entry; - UDSAtom atom; - - atom.m_uds = TDEIO::UDS_NAME; - atom.m_str = TQString::null; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = S_IFDIR; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_USER; - atom.m_str = "root"; - entry.append( atom ); - atom.m_uds = TDEIO::UDS_GROUP; - entry.append( atom ); - - // no size - - statEntry( entry ); - finished(); - return; - } - - KURL tempurl( url ); - tempurl.setPath( path ); // take the clean one - TQString listarg; // = tempurl.directory(false /*keep trailing slash*/); - TQString parentDir; - TQString filename = tempurl.fileName(); - Q_ASSERT(!filename.isEmpty()); - TQString search = filename; - - // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info) - // if it doesn't work, it's a file (and then we'll use dir filename) - bool isDir = ftpFolder(path, false); - - // if we're only interested in "file or directory", we should stop here - TQString sDetails = metaData("details"); - int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); - kdDebug(7102) << "Ftp::stat details=" << details << endl; - if ( details == 0 ) - { - if ( !isDir && !ftpSize( path, 'I' ) ) // ok, not a dir -> is it a file ? - { // no -> it doesn't exist at all - ftpStatAnswerNotFound( path, filename ); - return; - } - ftpShortStatAnswer( filename, isDir ); // successfully found a dir or a file -> done - return; - } - - if (!isDir) - { - // It is a file or it doesn't exist, try going to parent directory - parentDir = tempurl.directory(false /*keep trailing slash*/); - // With files we can do "LIST " to avoid listing the whole dir - listarg = filename; - } - else - { - // --- New implementation: - // Don't list the parent dir. Too slow, might not show it, etc. - // Just return that it's a dir. - UDSEntry entry; - UDSAtom atom; - - atom.m_uds = TDEIO::UDS_NAME; - atom.m_str = filename; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = S_IFDIR; - entry.append( atom ); - - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; - entry.append( atom ); - - // No clue about size, ownership, group, etc. - - statEntry(entry); - finished(); - return; - - // --- Old implementation: -#if 0 - // It's a dir, remember that - // Reason: it could be a symlink to a dir, in which case ftpReadDir - // in the parent dir will have no idea about that. But we know better. - isDir = true; - // If the dir starts with '.', we'll need '-a' to see it in the listing. - if ( search[0] == '.' ) - listarg = "-a"; - parentDir = ".."; -#endif - } - - // Now cwd the parent dir, to prepare for listing - if( !ftpFolder(parentDir, true) ) - return; - - if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) ) - { - kdError(7102) << "COULD NOT LIST" << endl; - return; - } - kdDebug(7102) << "Starting of list was ok" << endl; - - Q_ASSERT( !search.isEmpty() && search != "/" ); - - bool bFound = false; - KURL linkURL; - FtpEntry ftpEnt; - while( ftpReadDir(ftpEnt) ) - { - // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at) - // return only the filename when doing "dir /full/path/to/file" - if ( !bFound ) - { - if ( ( search == ftpEnt.name || filename == ftpEnt.name ) ) { - if ( !filename.isEmpty() ) { - bFound = true; - UDSEntry entry; - ftpCreateUDSEntry( filename, ftpEnt, entry, isDir ); - statEntry( entry ); - } - } else if ( isDir && ( ftpEnt.name == listarg || ftpEnt.name+'/' == listarg ) ) { - // Damn, the dir we're trying to list is in fact a symlink - // Follow it and try again - if ( ftpEnt.link.isEmpty() ) - kdWarning(7102) << "Got " << listarg << " as answer, but empty link!" << endl; - else - { - linkURL = url; - kdDebug(7102) << "ftpEnt.link=" << ftpEnt.link << endl; - if ( ftpEnt.link[0] == '/' ) - linkURL.setPath( ftpEnt.link ); // Absolute link - else - { - // Relative link (stat will take care of cleaning ../.. etc.) - linkURL.setPath( listarg ); // this is what we were listing (the link) - linkURL.setPath( linkURL.directory() ); // go up one dir - linkURL.addPath( ftpEnt.link ); // replace link by its destination - kdDebug(7102) << "linkURL now " << linkURL.prettyURL() << endl; - } - // Re-add the filename we're looking for - linkURL.addPath( filename ); - } - bFound = true; - } - } - - // kdDebug(7102) << ftpEnt.name << endl; - } - - ftpCloseCommand(); // closes the data connection only - - if ( !bFound ) - { - ftpStatAnswerNotFound( path, filename ); - return; - } - - if ( !linkURL.isEmpty() ) - { - if ( linkURL == url || linkURL == tempurl ) - { - error( ERR_CYCLIC_LINK, linkURL.prettyURL() ); - return; - } - stat( linkURL ); - return; - } - - kdDebug(7102) << "stat : finished successfully" << endl; - finished(); -} - - -void Ftp::listDir( const KURL &url ) -{ - kdDebug(7102) << "Ftp::listDir " << url.prettyURL() << endl; - if( !ftpOpenConnection(loginImplicit) ) - return; - - // No path specified ? - TQString path = url.path(); - if ( path.isEmpty() ) - { - KURL realURL; - realURL.setProtocol( "ftp" ); - if ( m_user != FTP_LOGIN ) - realURL.setUser( m_user ); - // We set the password, so that we don't ask for it if it was given - if ( m_pass != FTP_PASSWD ) - realURL.setPass( m_pass ); - realURL.setHost( m_host ); - realURL.setPort( m_port ); - if ( m_initialPath.isEmpty() ) - m_initialPath = "/"; - realURL.setPath( m_initialPath ); - kdDebug(7102) << "REDIRECTION to " << realURL.prettyURL() << endl; - redirection( realURL ); - finished(); - return; - } - - kdDebug(7102) << "hunting for path '" << path << "'" << endl; - - if (!ftpOpenDir( path ) ) - { - if ( ftpSize( path, 'I' ) ) // is it a file ? - { - error( ERR_IS_FILE, path ); - return; - } - // not sure which to emit - //error( ERR_DOES_NOT_EXIST, path ); - error( ERR_CANNOT_ENTER_DIRECTORY, path ); - return; - } - - UDSEntry entry; - FtpEntry ftpEnt; - while( ftpReadDir(ftpEnt) ) - { - //kdDebug(7102) << ftpEnt.name << endl; - //Q_ASSERT( !ftpEnt.name.isEmpty() ); - if ( !ftpEnt.name.isEmpty() ) - { - //if ( S_ISDIR( (mode_t)ftpEnt.type ) ) - // kdDebug(7102) << "is a dir" << endl; - //if ( !ftpEnt.link.isEmpty() ) - // kdDebug(7102) << "is a link to " << ftpEnt.link << endl; - entry.clear(); - ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false ); - listEntry( entry, false ); - } - } - listEntry( entry, true ); // ready - ftpCloseCommand(); // closes the data connection only - finished(); -} - -void Ftp::slave_status() -{ - kdDebug(7102) << "Got slave_status host = " << (m_host.ascii() ? m_host.ascii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]" << endl; - slaveStatus( m_host, m_bLoggedOn ); -} - -bool Ftp::ftpOpenDir( const TQString & path ) -{ - //TQString path( _url.path(-1) ); - - // We try to change to this directory first to see whether it really is a directory. - // (And also to follow symlinks) - TQString tmp = path.isEmpty() ? TQString("/") : path; - - // We get '550', whether it's a file or doesn't exist... - if( !ftpFolder(tmp, false) ) - return false; - - // Don't use the path in the list command: - // We changed into this directory anyway - so it's enough just to send "list". - // We use '-a' because the application MAY be interested in dot files. - // The only way to really know would be to have a metadata flag for this... - // Since some windows ftp server seems not to support the -a argument, we use a fallback here. - // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com) - if( !ftpOpenCommand( "list -la", TQString::null, 'I', ERR_CANNOT_ENTER_DIRECTORY ) ) - { - if ( !ftpOpenCommand( "list", TQString::null, 'I', ERR_CANNOT_ENTER_DIRECTORY ) ) - { - kdWarning(7102) << "Can't open for listing" << endl; - return false; - } - } - kdDebug(7102) << "Starting of list was ok" << endl; - return true; -} - -bool Ftp::ftpReadDir(FtpEntry& de) -{ - assert(m_data != NULL); - - // get a line from the data connecetion ... - while( !m_data->textEOF() ) - { - if(m_data->textRead() <= 0) - continue; - if(m_data->textTooLong()) - kdWarning(7102) << "ftpReadDir line too long - truncated" << endl; - - const char* buffer = m_data->textLine(); - kdDebug(7102) << "dir > " << buffer << endl; - - //Normally the listing looks like - // -rw-r--r-- 1 dfaure dfaure 102 Nov 9 12:30 log - // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442) - // d [RWCEAFMS] Admin 512 Oct 13 2004 PSI - - // we should always get the following 5 fields ... - const char *p_access, *p_junk, *p_owner, *p_group, *p_size; - if( (p_access = strtok((char*)buffer," ")) == 0) continue; - if( (p_junk = strtok(NULL," ")) == 0) continue; - if( (p_owner = strtok(NULL," ")) == 0) continue; - if( (p_group = strtok(NULL," ")) == 0) continue; - if( (p_size = strtok(NULL," ")) == 0) continue; - - //kdDebug(7102) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size << endl; - - de.access = 0; - if ( strlen( p_access ) == 1 && p_junk[0] == '[' ) { // Netware - de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions - } - - const char *p_date_1, *p_date_2, *p_date_3, *p_name; - - // A special hack for "/dev". A listing may look like this: - // crw-rw-rw- 1 root root 1, 5 Jun 29 1997 zero - // So we just ignore the number in front of the ",". Ok, its a hack :-) - if ( strchr( p_size, ',' ) != 0L ) - { - //kdDebug(7102) << "Size contains a ',' -> reading size again (/dev hack)" << endl; - if ((p_size = strtok(NULL," ")) == 0) - continue; - } - - // Check whether the size we just read was really the size - // or a month (this happens when the server lists no group) - // Used to be the case on sunsite.uio.no, but not anymore - // This is needed for the Netware case, too. - if ( !isdigit( *p_size ) ) - { - p_date_1 = p_size; - p_size = p_group; - p_group = 0; - //kdDebug(7102) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1 << endl; - } - else - { - p_date_1 = strtok(NULL," "); - //kdDebug(7102) << "Size has a digit -> ok. p_date_1=" << p_date_1 << endl; - } - - if ( p_date_1 != 0 && - (p_date_2 = strtok(NULL," ")) != 0 && - (p_date_3 = strtok(NULL," ")) != 0 && - (p_name = strtok(NULL,"\r\n")) != 0 ) - { - { - TQCString tmp( p_name ); - if ( p_access[0] == 'l' ) - { - int i = tmp.findRev( " -> " ); - if ( i != -1 ) { - de.link = remoteEncoding()->decode(p_name + i + 4); - tmp.truncate( i ); - } - else - de.link = TQString::null; - } - else - de.link = TQString::null; - - if ( tmp[0] == '/' ) // listing on ftp://ftp.gnupg.org/ starts with '/' - tmp.remove( 0, 1 ); - - if (tmp.find('/') != -1) - continue; // Don't trick us! - // Some sites put more than one space between the date and the name - // e.g. ftp://ftp.uni-marburg.de/mirror/ - de.name = remoteEncoding()->decode(tmp.stripWhiteSpace()); - } - - de.type = S_IFREG; - switch ( p_access[0] ) { - case 'd': - de.type = S_IFDIR; - break; - case 's': - de.type = S_IFSOCK; - break; - case 'b': - de.type = S_IFBLK; - break; - case 'c': - de.type = S_IFCHR; - break; - case 'l': - de.type = S_IFREG; - // we don't set S_IFLNK here. de.link says it. - break; - default: - break; - } - - if ( p_access[1] == 'r' ) - de.access |= S_IRUSR; - if ( p_access[2] == 'w' ) - de.access |= S_IWUSR; - if ( p_access[3] == 'x' || p_access[3] == 's' ) - de.access |= S_IXUSR; - if ( p_access[4] == 'r' ) - de.access |= S_IRGRP; - if ( p_access[5] == 'w' ) - de.access |= S_IWGRP; - if ( p_access[6] == 'x' || p_access[6] == 's' ) - de.access |= S_IXGRP; - if ( p_access[7] == 'r' ) - de.access |= S_IROTH; - if ( p_access[8] == 'w' ) - de.access |= S_IWOTH; - if ( p_access[9] == 'x' || p_access[9] == 't' ) - de.access |= S_IXOTH; - if ( p_access[3] == 's' || p_access[3] == 'S' ) - de.access |= S_ISUID; - if ( p_access[6] == 's' || p_access[6] == 'S' ) - de.access |= S_ISGID; - if ( p_access[9] == 't' || p_access[9] == 'T' ) - de.access |= S_ISVTX; - - de.owner = remoteEncoding()->decode(p_owner); - de.group = remoteEncoding()->decode(p_group); - de.size = charToLongLong(p_size); - - // Parsing the date is somewhat tricky - // Examples : "Oct 6 22:49", "May 13 1999" - - // First get current time - we need the current month and year - time_t currentTime = time( 0L ); - struct tm * tmptr = gmtime( ¤tTime ); - int currentMonth = tmptr->tm_mon; - //kdDebug(7102) << "Current time :" << asctime( tmptr ) << endl; - // Reset time fields - tmptr->tm_isdst = -1; // We do not know anything about day saving time (of any random day of the year) - tmptr->tm_sec = 0; - tmptr->tm_min = 0; - tmptr->tm_hour = 0; - // Get day number (always second field) - tmptr->tm_mday = atoi( p_date_2 ); - // Get month from first field - // NOTE : no, we don't want to use TDELocale here - // It seems all FTP servers use the English way - //kdDebug(7102) << "Looking for month " << p_date_1 << endl; - static const char * s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - for ( int c = 0 ; c < 12 ; c ++ ) - if ( !strcmp( p_date_1, s_months[c]) ) - { - //kdDebug(7102) << "Found month " << c << " for " << p_date_1 << endl; - tmptr->tm_mon = c; - break; - } - - // Parse third field - if ( strlen( p_date_3 ) == 4 ) // 4 digits, looks like a year - tmptr->tm_year = atoi( p_date_3 ) - 1900; - else - { - // otherwise, the year is implicit - // according to man ls, this happens when it is between than 6 months - // old and 1 hour in the future. - // So the year is : current year if tm_mon <= currentMonth+1 - // otherwise current year minus one - // (The +1 is a security for the "+1 hour" at the end of the month issue) - if ( tmptr->tm_mon > currentMonth + 1 ) - tmptr->tm_year--; - - // and p_date_3 contains probably a time - char * semicolon; - if ( ( semicolon = const_cast(strchr( p_date_3, ':' )) ) ) - { - *semicolon = '\0'; - tmptr->tm_min = atoi( semicolon + 1 ); - tmptr->tm_hour = atoi( p_date_3 ); - } - else - kdWarning(7102) << "Can't parse third field " << p_date_3 << endl; - } - - //kdDebug(7102) << asctime( tmptr ) << endl; - de.date = mktime( tmptr ); - return true; - } - } // line invalid, loop to get another line - return false; -} - -//=============================================================================== -// public: get download file from server -// helper: ftpGet called from get() and copy() -//=============================================================================== -void Ftp::get( const KURL & url ) -{ - kdDebug(7102) << "Ftp::get " << url.url() << endl; - int iError = 0; - ftpGet(iError, -1, url, 0); // iError gets status - if(iError) // can have only server side errs - error(iError, url.path()); - ftpCloseCommand(); // must close command! -} - -Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KURL& url, TDEIO::fileoffset_t llOffset) -{ - // Calls error() by itself! - if( !ftpOpenConnection(loginImplicit) ) - return statusServerError; - - // Try to find the size of the file (and check that it exists at - // the same time). If we get back a 550, "File does not exist" - // or "not a plain file", check if it is a directory. If it is a - // directory, return an error; otherwise simply try to retrieve - // the request... - if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) && - ftpFolder(url.path(), false) ) - { - // Ok it's a dir in fact - kdDebug(7102) << "ftpGet: it is a directory in fact" << endl; - iError = ERR_IS_DIRECTORY; - return statusServerError; - } - - TQString resumeOffset = metaData("resume"); - if ( !resumeOffset.isEmpty() ) - { - llOffset = resumeOffset.toLongLong(); - kdDebug(7102) << "ftpGet: got offset from metadata : " << llOffset << endl; - } - - if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) ) - { - kdWarning(7102) << "ftpGet: Can't open for reading" << endl; - return statusServerError; - } - - // Read the size from the response string - if(m_size == UnknownSize) - { - const char* psz = strrchr( ftpResponse(4), '(' ); - if(psz) m_size = charToLongLong(psz+1); - if (!m_size) m_size = UnknownSize; - } - - TDEIO::filesize_t bytesLeft = 0; - if ( m_size != UnknownSize ) - bytesLeft = m_size - llOffset; - - kdDebug(7102) << "ftpGet: starting with offset=" << llOffset << endl; - TDEIO::fileoffset_t processed_size = llOffset; - - TQByteArray array; - bool mimetypeEmitted = false; - char buffer[maximumIpcSize]; - // start whith small data chunks in case of a slow data source (modem) - // - unfortunately this has a negative impact on performance for large - // - files - so we will increase the block size after a while ... - int iBlockSize = initialIpcSize; - int iBufferCur = 0; - - while(m_size == UnknownSize || bytesLeft > 0) - { // let the buffer size grow if the file is larger 64kByte ... - if(processed_size-llOffset > 1024 * 64) - iBlockSize = maximumIpcSize; - - // read the data and detect EOF or error ... - if(iBlockSize+iBufferCur > (int)sizeof(buffer)) - iBlockSize = sizeof(buffer) - iBufferCur; - int n = m_data->read( buffer+iBufferCur, iBlockSize ); - if(n <= 0) - { // this is how we detect EOF in case of unknown size - if( m_size == UnknownSize && n == 0 ) - break; - // unexpected eof. Happens when the daemon gets killed. - iError = ERR_COULD_NOT_READ; - return statusServerError; - } - processed_size += n; - - // collect very small data chunks in buffer before processing ... - if(m_size != UnknownSize) - { - bytesLeft -= n; - iBufferCur += n; - if(iBufferCur < mimimumMimeSize && bytesLeft > 0) - { - processedSize( processed_size ); - continue; - } - n = iBufferCur; - iBufferCur = 0; - } - - // get the mime type and set the total size ... - if(!mimetypeEmitted) - { - mimetypeEmitted = true; - - // We need a KMimeType::findByNameAndContent(data,filename) - // For now we do: find by extension, and if not found (or extension not reliable) - // then find by content. - bool accurate = false; - KMimeType::Ptr mime = KMimeType::findByURL( url, 0, false, true, &accurate ); - if ( !mime || mime->name() == KMimeType::defaultMimeType() - || !accurate ) - { - array.setRawData(buffer, n); - KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType(array, url.fileName()); - array.resetRawData(buffer, n); - if ( result->mimeType() != KMimeType::defaultMimeType() ) - mime = KMimeType::mimeType( result->mimeType() ); - } - - kdDebug(7102) << "ftpGet: Emitting mimetype " << mime->name() << endl; - mimeType( mime->name() ); - if( m_size != UnknownSize ) // Emit total size AFTER mimetype - totalSize( m_size ); - } - - // write output file or pass to data pump ... - if(iCopyFile == -1) - { - array.setRawData(buffer, n); - data( array ); - array.resetRawData(buffer, n); - } - else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0) - return statusClientError; // client side error - processedSize( processed_size ); - } - - kdDebug(7102) << "ftpGet: done" << endl; - if(iCopyFile == -1) // must signal EOF to data pump ... - data(array); // array is empty and must be empty! - - processedSize( m_size == UnknownSize ? processed_size : m_size ); - kdDebug(7102) << "ftpGet: emitting finished()" << endl; - finished(); - return statusSuccess; -} - -/* -void Ftp::mimetype( const KURL& url ) -{ - if( !ftpOpenConnection(loginImplicit) ) - return; - - if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) { - kdWarning(7102) << "Can't open for reading" << endl; - return; - } - char buffer[ 2048 ]; - TQByteArray array; - // Get one chunk of data only and send it, TDEIO::Job will determine the - // mimetype from it using KMimeMagic - int n = m_data->read( buffer, 2048 ); - array.setRawData(buffer, n); - data( array ); - array.resetRawData(buffer, n); - - kdDebug(7102) << "aborting" << endl; - ftpAbortTransfer(); - - kdDebug(7102) << "finished" << endl; - finished(); - kdDebug(7102) << "after finished" << endl; -} - -void Ftp::ftpAbortTransfer() -{ - // RFC 959, page 34-35 - // IAC (interpret as command) = 255 ; IP (interrupt process) = 254 - // DM = 242 (data mark) - char msg[4]; - // 1. User system inserts the Telnet "Interrupt Process" (IP) signal - // in the Telnet stream. - msg[0] = (char) 255; //IAC - msg[1] = (char) 254; //IP - (void) send(sControl, msg, 2, 0); - // 2. User system sends the Telnet "Sync" signal. - msg[0] = (char) 255; //IAC - msg[1] = (char) 242; //DM - if (send(sControl, msg, 2, MSG_OOB) != 2) - ; // error... - - // Send ABOR - kdDebug(7102) << "send ABOR" << endl; - TQCString buf = "ABOR\r\n"; - if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 ) { - error( ERR_COULD_NOT_WRITE, TQString::null ); - return; - } - - // - kdDebug(7102) << "read resp" << endl; - if ( readresp() != '2' ) - { - error( ERR_COULD_NOT_READ, TQString::null ); - return; - } - - kdDebug(7102) << "close sockets" << endl; - closeSockets(); -} -*/ - -//=============================================================================== -// public: put upload file to server -// helper: ftpPut called from put() and copy() -//=============================================================================== -void Ftp::put(const KURL& url, int permissions, bool overwrite, bool resume) -{ - kdDebug(7102) << "Ftp::put " << url.url() << endl; - int iError = 0; // iError gets status - ftpPut(iError, -1, url, permissions, overwrite, resume); - if(iError) // can have only server side errs - error(iError, url.path()); - ftpCloseCommand(); // must close command! -} - -Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KURL& dest_url, - int permissions, bool overwrite, bool resume) -{ - if( !ftpOpenConnection(loginImplicit) ) - return statusServerError; - - // Don't use mark partial over anonymous FTP. - // My incoming dir allows put but not rename... - bool bMarkPartial; - if (m_user.isEmpty () || m_user == FTP_LOGIN) - bMarkPartial = false; - else - bMarkPartial = config()->readBoolEntry("MarkPartial", true); - - TQString dest_orig = dest_url.path(); - TQString dest_part( dest_orig ); - dest_part += ".part"; - - if ( ftpSize( dest_orig, 'I' ) ) - { - if ( m_size == 0 ) - { // delete files with zero size - TQCString cmd = "DELE "; - cmd += remoteEncoding()->encode(dest_orig); - if( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) - { - iError = ERR_CANNOT_DELETE_PARTIAL; - return statusServerError; - } - } - else if ( !overwrite && !resume ) - { - iError = ERR_FILE_ALREADY_EXIST; - return statusServerError; - } - else if ( bMarkPartial ) - { // when using mark partial, append .part extension - if ( !ftpRename( dest_orig, dest_part, true ) ) - { - iError = ERR_CANNOT_RENAME_PARTIAL; - return statusServerError; - } - } - // Don't chmod an existing file - permissions = -1; - } - else if ( bMarkPartial && ftpSize( dest_part, 'I' ) ) - { // file with extension .part exists - if ( m_size == 0 ) - { // delete files with zero size - TQCString cmd = "DELE "; - cmd += remoteEncoding()->encode(dest_part); - if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) - { - iError = ERR_CANNOT_DELETE_PARTIAL; - return statusServerError; - } - } - else if ( !overwrite && !resume ) - { - resume = canResume (m_size); - if (!resume) - { - iError = ERR_FILE_ALREADY_EXIST; - return statusServerError; - } - } - } - else - m_size = 0; - - TQString dest; - - // if we are using marking of partial downloads -> add .part extension - if ( bMarkPartial ) { - kdDebug(7102) << "Adding .part extension to " << dest_orig << endl; - dest = dest_part; - } else - dest = dest_orig; - - TDEIO::fileoffset_t offset = 0; - - // set the mode according to offset - if( resume && m_size > 0 ) - { - offset = m_size; - if(iCopyFile != -1) - { - if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 ) - { - iError = ERR_CANNOT_RESUME; - return statusClientError; - } - } - } - - if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) ) - return statusServerError; - - kdDebug(7102) << "ftpPut: starting with offset=" << offset << endl; - TDEIO::fileoffset_t processed_size = offset; - - TQByteArray buffer; - int result; - int iBlockSize = initialIpcSize; - // Loop until we got 'dataEnd' - do - { - if(iCopyFile == -1) - { - dataReq(); // Request for data - result = readData( buffer ); - } - else - { // let the buffer size grow if the file is larger 64kByte ... - if(processed_size-offset > 1024 * 64) - iBlockSize = maximumIpcSize; - buffer.resize(iBlockSize); - result = ::read(iCopyFile, buffer.data(), buffer.size()); - if(result < 0) - iError = ERR_COULD_NOT_WRITE; - else - buffer.resize(result); - } - - if (result > 0) - { - m_data->write( buffer.data(), buffer.size() ); - processed_size += result; - processedSize (processed_size); - } - } - while ( result > 0 ); - - if (result != 0) // error - { - ftpCloseCommand(); // don't care about errors - kdDebug(7102) << "Error during 'put'. Aborting." << endl; - if (bMarkPartial) - { - // Remove if smaller than minimum size - if ( ftpSize( dest, 'I' ) && - ( processed_size < (unsigned long) config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) ) - { - TQCString cmd = "DELE "; - cmd += remoteEncoding()->encode(dest); - (void) ftpSendCmd( cmd ); - } - } - return statusServerError; - } - - if ( !ftpCloseCommand() ) - { - iError = ERR_COULD_NOT_WRITE; - return statusServerError; - } - - // after full download rename the file back to original name - if ( bMarkPartial ) - { - kdDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")" << endl; - if ( !ftpRename( dest, dest_orig, true ) ) - { - iError = ERR_CANNOT_RENAME_PARTIAL; - return statusServerError; - } - } - - // set final permissions - if ( permissions != -1 ) - { - if ( m_user == FTP_LOGIN ) - kdDebug(7102) << "Trying to chmod over anonymous FTP ???" << endl; - // chmod the file we just put - if ( ! ftpChmod( dest_orig, permissions ) ) - { - // To be tested - //if ( m_user != FTP_LOGIN ) - // warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) ); - } - } - - // We have done our job => finish - finished(); - return statusSuccess; -} - - -/** Use the SIZE command to get the file size. - Warning : the size depends on the transfer mode, hence the second arg. */ -bool Ftp::ftpSize( const TQString & path, char mode ) -{ - m_size = UnknownSize; - if( !ftpDataMode(mode) ) - return false; - - TQCString buf; - buf = "SIZE "; - buf += remoteEncoding()->encode(path); - if( !ftpSendCmd( buf ) || (m_iRespType != 2) ) - return false; - - // skip leading "213 " (response code) - const char* psz = ftpResponse(4); - if(!psz) - return false; - m_size = charToLongLong(psz); - if (!m_size) m_size = UnknownSize; - return true; -} - -bool Ftp::ftpFileExists(const TQString& path) -{ - TQCString buf; - buf = "SIZE "; - buf += remoteEncoding()->encode(path); - if( !ftpSendCmd( buf ) || (m_iRespType != 2) ) - return false; - - // skip leading "213 " (response code) - const char* psz = ftpResponse(4); - return psz != 0; -} - -// Today the differences between ASCII and BINARY are limited to -// CR or CR/LF line terminators. Many servers ignore ASCII (like -// win2003 -or- vsftp with default config). In the early days of -// computing, when even text-files had structure, this stuff was -// more important. -// Theoretically "list" could return different results in ASCII -// and BINARY mode. But again, most servers ignore ASCII here. -bool Ftp::ftpDataMode(char cMode) -{ - if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I'; - else if(cMode == 'a') cMode = 'A'; - else if(cMode != 'A') cMode = 'I'; - - kdDebug(7102) << "ftpDataMode: want '" << cMode << "' has '" << m_cDataMode << "'" << endl; - if(m_cDataMode == cMode) - return true; - - TQCString buf; - buf.sprintf("TYPE %c", cMode); - if( !ftpSendCmd(buf) || (m_iRespType != 2) ) - return false; - m_cDataMode = cMode; - return true; -} - - -bool Ftp::ftpFolder(const TQString& path, bool bReportError) -{ - TQString newPath = path; - int iLen = newPath.length(); - if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1); - - //kdDebug(7102) << "ftpFolder: want '" << newPath << "' has '" << m_currentPath << "'" << endl; - if(m_currentPath == newPath) - return true; - - TQCString tmp = "cwd "; - tmp += remoteEncoding()->encode(newPath); - if( !ftpSendCmd(tmp) ) - return false; // connection failure - if(m_iRespType != 2) - { - if(bReportError) - error(ERR_CANNOT_ENTER_DIRECTORY, path); - return false; // not a folder - } - m_currentPath = newPath; - return true; -} - - -//=============================================================================== -// public: copy don't use tdeio data pump if one side is a local file -// helper: ftpCopyPut called from copy() on upload -// helper: ftpCopyGet called from copy() on download -//=============================================================================== -void Ftp::copy( const KURL &src, const KURL &dest, int permissions, bool overwrite ) -{ - int iError = 0; - int iCopyFile = -1; - StatusCode cs = statusSuccess; - bool bSrcLocal = src.isLocalFile(); - bool bDestLocal = dest.isLocalFile(); - TQString sCopyFile; - - if(bSrcLocal && !bDestLocal) // File -> Ftp - { - sCopyFile = src.path(); - kdDebug(7102) << "Ftp::copy local file '" << sCopyFile << "' -> ftp '" << dest.path() << "'" << endl; - cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, overwrite); - if( cs == statusServerError) sCopyFile = dest.url(); - } - else if(!bSrcLocal && bDestLocal) // Ftp -> File - { - sCopyFile = dest.path(); - kdDebug(7102) << "Ftp::copy ftp '" << src.path() << "' -> local file '" << sCopyFile << "'" << endl; - cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, overwrite); - if( cs == statusServerError ) sCopyFile = src.url(); - } - else { - error( ERR_UNSUPPORTED_ACTION, TQString::null ); - return; - } - - // perform clean-ups and report error (if any) - if(iCopyFile != -1) - ::close(iCopyFile); - if(iError) - error(iError, sCopyFile); - ftpCloseCommand(); // must close command! -} - - -Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, TQString sCopyFile, - const KURL& url, int permissions, bool overwrite) -{ - // check if source is ok ... - KDE_struct_stat buff; - TQCString sSrc( TQFile::encodeName(sCopyFile) ); - bool bSrcExists = (KDE_stat( sSrc.data(), &buff ) != -1); - if(bSrcExists) - { if(S_ISDIR(buff.st_mode)) - { - iError = ERR_IS_DIRECTORY; - return statusClientError; - } - } - else - { - iError = ERR_DOES_NOT_EXIST; - return statusClientError; - } - - iCopyFile = KDE_open( sSrc.data(), O_RDONLY ); - if(iCopyFile == -1) - { - iError = ERR_CANNOT_OPEN_FOR_READING; - return statusClientError; - } - - // delegate the real work (iError gets status) ... - totalSize(buff.st_size); -#ifdef ENABLE_CAN_RESUME - return ftpPut(iError, iCopyFile, url, permissions, overwrite, false); -#else - return ftpPut(iError, iCopyFile, url, permissions, overwrite, true); -#endif -} - - -Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const TQString sCopyFile, - const KURL& url, int permissions, bool overwrite) -{ - // check if destination is ok ... - KDE_struct_stat buff; - TQCString sDest( TQFile::encodeName(sCopyFile) ); - bool bDestExists = (KDE_stat( sDest.data(), &buff ) != -1); - if(bDestExists) - { if(S_ISDIR(buff.st_mode)) - { - iError = ERR_IS_DIRECTORY; - return statusClientError; - } - if(!overwrite) - { - iError = ERR_FILE_ALREADY_EXIST; - return statusClientError; - } - } - - // do we have a ".part" file? - TQCString sPart = TQFile::encodeName(sCopyFile + ".part"); - bool bResume = false; - bool bPartExists = (KDE_stat( sPart.data(), &buff ) != -1); - const bool bMarkPartial = config()->readBoolEntry("MarkPartial", true); - - if(!bMarkPartial) - { - sPart = TQFile::encodeName(sCopyFile); - } - else if(bPartExists && buff.st_size > 0) - { // must not be a folder! please fix a similar bug in tdeio_file!! - if(S_ISDIR(buff.st_mode)) - { - iError = ERR_DIR_ALREADY_EXIST; - return statusClientError; // client side error - } - //doesn't work for copy? -> design flaw? -#ifdef ENABLE_CAN_RESUME - bResume = canResume( buff.st_size ); -#else - bResume = true; -#endif - } - - if(bPartExists && !bResume) // get rid of an unwanted ".part" file - remove(sPart.data()); - - // JPF: in tdeio_file overwrite disables ".part" operations. I do not believe - // JPF: that this is a good behaviour! - if(bDestExists) // must delete for overwrite - remove(sDest.data()); - - // WABA: Make sure that we keep writing permissions ourselves, - // otherwise we can be in for a surprise on NFS. - mode_t initialMode; - if (permissions != -1) - initialMode = permissions | S_IWUSR; - else - initialMode = 0666; - - // open the output file ... - TDEIO::fileoffset_t hCopyOffset = 0; - if(bResume) - { - iCopyFile = KDE_open( sPart.data(), O_RDWR ); // append if resuming - hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END); - if(hCopyOffset < 0) - { - iError = ERR_CANNOT_RESUME; - return statusClientError; // client side error - } - kdDebug(7102) << "copy: resuming at " << hCopyOffset << endl; - } - else - iCopyFile = KDE_open(sPart.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); - - if(iCopyFile == -1) - { - kdDebug(7102) << "copy: ### COULD NOT WRITE " << sCopyFile << endl; - iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED - : ERR_CANNOT_OPEN_FOR_WRITING; - return statusClientError; - } - - // delegate the real work (iError gets status) ... - StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset); - if( ::close(iCopyFile) && iRes == statusSuccess ) - { - iError = ERR_COULD_NOT_WRITE; - iRes = statusClientError; - } - - // handle renaming or deletion of a partial file ... - if(bMarkPartial) - { - if(iRes == statusSuccess) - { // rename ".part" on success - if ( ::rename( sPart.data(), sDest.data() ) ) - { - kdDebug(7102) << "copy: cannot rename " << sPart << " to " << sDest << endl; - iError = ERR_CANNOT_RENAME_PARTIAL; - iRes = statusClientError; - } - } - else if(KDE_stat( sPart.data(), &buff ) == 0) - { // should a very small ".part" be deleted? - int size = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); - if (buff.st_size < size) - remove(sPart.data()); - } - } - return iRes; -} diff --git a/tdeioslave/ftp/ftp.cpp b/tdeioslave/ftp/ftp.cpp new file mode 100644 index 000000000..8e0f96b5d --- /dev/null +++ b/tdeioslave/ftp/ftp.cpp @@ -0,0 +1,2674 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 David Faure + + 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. + + Recommended reading explaining FTP details and quirks: + http://cr.yp.to/ftp.html (by D.J. Bernstein) +*/ + + +#define TDEIO_FTP_PRIVATE_INCLUDE +#include "ftp.h" + +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if TIME_WITH_SYS_TIME +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_STRTOLL + #define charToLongLong(a) strtoll(a, 0, 10) +#else + #define charToLongLong(a) strtol(a, 0, 10) +#endif + +// JPF: a remark on coding style (2004-03-06): +// Some calls to TQString::fromLatin1() were removed from the code. In most places +// the KDE code relies on implicit creation of QStrings. Also Qt has a lot of +// const char* overloads, so that using TQString::fromLatin1() can be ineffectient! + +#define FTP_LOGIN "anonymous" +#define FTP_PASSWD "anonymous@" + +//#undef kdDebug +#define ENABLE_CAN_RESUME + +// JPF: somebody should find a better solution for this or move this to TDEIO +// JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions! +namespace TDEIO { + enum buffersizes + { /** + * largest buffer size that should be used to transfer data between + * TDEIO slaves using the data() function + */ + maximumIpcSize = 32 * 1024, + /** + * this is a reasonable value for an initial read() that a TDEIO slave + * can do to obtain data via a slow network connection. + */ + initialIpcSize = 2 * 1024, + /** + * recommended size of a data block passed to findBufferFileType() + */ + mimimumMimeSize = 1024 + }; + + // JPF: this helper was derived from write_all in file.cpp (FileProtocol). + static // JPF: in ftp.cpp we make it static + /** + * This helper handles some special issues (blocking and interrupted + * system call) when writing to a file handle. + * + * @return 0 on success or an error code on failure (ERR_COULD_NOT_WRITE, + * ERR_DISK_FULL, ERR_CONNECTION_BROKEN). + */ + int WriteToFile(int fd, const char *buf, size_t len) + { + while (len > 0) + { // JPF: shouldn't there be a KDE_write? + ssize_t written = write(fd, buf, len); + if (written >= 0) + { buf += written; + len -= written; + continue; + } + switch(errno) + { case EINTR: continue; + case EPIPE: return ERR_CONNECTION_BROKEN; + case ENOSPC: return ERR_DISK_FULL; + default: return ERR_COULD_NOT_WRITE; + } + } + return 0; + } +} + +TDEIO::filesize_t Ftp::UnknownSize = (TDEIO::filesize_t)-1; + +using namespace TDEIO; + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + TDELocale::setMainCatalogue("tdelibs"); + TDEInstance instance( "tdeio_ftp" ); + ( void ) TDEGlobal::locale(); + + kdDebug(7102) << "Starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: tdeio_ftp protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + Ftp slave(argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7102) << "Done" << endl; + return 0; +} + +//=============================================================================== +// FtpTextReader Read Text lines from a file (or socket) +//=============================================================================== + +void FtpTextReader::textClear() +{ m_iTextLine = m_iTextBuff = 0; + m_szText[0] = 0; + m_bTextEOF = m_bTextTruncated = false; +} + +int FtpTextReader::textRead(FtpSocket *pSock) +{ + // if we have still buffered data then move it to the left + char* pEOL; + if(m_iTextLine < m_iTextBuff) + { m_iTextBuff -= m_iTextLine; + memmove(m_szText, m_szText+m_iTextLine, m_iTextBuff); + pEOL = (char*)memchr(m_szText, '\n', m_iTextBuff); // have a complete line? + } + else + { m_iTextBuff = 0; + pEOL = NULL; + } + m_bTextEOF = m_bTextTruncated = false; + + // read data from the control socket until a complete line is read + int nBytes; + while(pEOL == NULL) + { + if(m_iTextBuff > textReadLimit) + { m_bTextTruncated = true; + m_iTextBuff = textReadLimit; + } + nBytes = pSock->read(m_szText+m_iTextBuff, sizeof(m_szText)-m_iTextBuff); + if(nBytes <= 0) + { + // This error can occur after the server closed the connection (after a timeout) + if(nBytes < 0) + pSock->debugMessage("textRead failed"); + m_bTextEOF = true; + pEOL = m_szText + m_iTextBuff; + } + else + { + m_iTextBuff += nBytes; + pEOL = (char*)memchr(m_szText, '\n', m_iTextBuff); + } + } + + nBytes = pEOL - m_szText; + m_iTextLine = nBytes + 1; + + if(nBytes > textReadLimit) + { m_bTextTruncated = true; + nBytes = textReadLimit; + } + if(nBytes && m_szText[nBytes-1] == '\r') + nBytes--; + m_szText[nBytes] = 0; + return nBytes; +} + +//=============================================================================== +// FtpSocket Helper Class for Data or Control Connections +//=============================================================================== +void FtpSocket::debugMessage(const char* pszMsg) const +{ + kdDebug(7102) << m_pszName << ": " << pszMsg << endl; +} + +int FtpSocket::errorMessage(int iErrorCode, const char* pszMsg) const +{ + kdError(7102) << m_pszName << ": " << pszMsg << endl; + return iErrorCode; +} + +int FtpSocket::connectSocket(int iTimeOutSec, bool bControl) +{ + closeSocket(); + + int iOpt = bControl ? KExtendedSocket::inetSocket + : KExtendedSocket::noResolve; + setSocketFlags(iOpt | socketFlags()); + setTimeout(iTimeOutSec); + + int iCon = KExtendedSocket::connect(); + if(iCon < 0) + { int iErrorCode = (status() == IO_LookupError) + ? ERR_UNKNOWN_HOST : ERR_COULD_NOT_CONNECT; + TQString strMsg = KExtendedSocket::strError(status(), systemError()); + strMsg.prepend("connect failed (code %1): "); + return errorMessage(iErrorCode, TQString(strMsg.arg(iCon)).latin1()); + } + if( !setAddressReusable(true) ) + return errorMessage(ERR_COULD_NOT_CREATE_SOCKET, "setAddressReusable failed"); + + if(!bControl) + { int on=1; + if( !setSocketOption(SO_KEEPALIVE, (char *)&on, sizeof(on)) ) + errorMessage(0, "Keepalive not allowed"); + + struct linger lng = { 1, 120 }; + if( !setSocketOption(SO_LINGER, (char *)&lng, sizeof (lng)) ) + errorMessage(0, "Linger mode was not allowed."); + } + + debugMessage("connected"); + return 0; +} + +void FtpSocket::closeSocket() +{ + if(m_server != -1 || fd() != -1) + debugMessage("disconnected"); + + if(m_server != -1) + { + ::shutdown(m_server, SHUT_RDWR); + ::close(m_server); + m_server = -1; + } + if(socketStatus() > nothing) + reset(); + textClear(); +} + +bool FtpSocket::setSocketOption(int opt, char*arg, socklen_t len) const +{ + return (setsockopt(sock(), SOL_SOCKET, opt, arg, len) != -1); +} + +//=============================================================================== +// Ftp +//=============================================================================== + +Ftp::Ftp( const TQCString &pool, const TQCString &app ) + : SlaveBase( "ftp", pool, app ) +{ + // init the socket data + m_data = m_control = NULL; + ftpCloseControlConnection(); + + // init other members + m_port = 0; + kdDebug(7102) << "Ftp::Ftp()" << endl; +} + + +Ftp::~Ftp() +{ + kdDebug(7102) << "Ftp::~Ftp()" << endl; + closeConnection(); +} + +/** + * This closes a data connection opened by ftpOpenDataConnection(). + */ +void Ftp::ftpCloseDataConnection() +{ + if(m_data != NULL) + { delete m_data; + m_data = NULL; + } +} + +/** + * This closes a control connection opened by ftpOpenControlConnection() and reinits the + * related states. This method gets called from the constructor with m_control = NULL. + */ +void Ftp::ftpCloseControlConnection() +{ + m_extControl = 0; + if(m_control) + delete m_control; + m_control = NULL; + m_cDataMode = 0; + m_bLoggedOn = false; // logon needs control connction + m_bTextMode = false; + m_bBusy = false; +} + +/** + * Returns the last response from the server (iOffset >= 0) -or- reads a new response + * (iOffset < 0). The result is returned (with iOffset chars skipped for iOffset > 0). + */ +const char* Ftp::ftpResponse(int iOffset) +{ + assert(m_control != NULL); // must have control connection socket + const char *pTxt = m_control->textLine(); + + // read the next line ... + if(iOffset < 0) + { + int iMore = 0; + m_iRespCode = 0; + + // If the server sends multiline responses "nnn-text" we loop here until + // a final "nnn text" line is reached. Only data from the final line will + // be stored. Some servers (OpenBSD) send a single "nnn-" followed by + // optional lines that start with a space and a final "nnn text" line. + do { + int nBytes = m_control->textRead(); + int iCode = atoi(pTxt); + if(iCode > 0) m_iRespCode = iCode; + + // ignore lines starting with a space in multiline response + if(iMore != 0 && pTxt[0] == 32) + ; + // otherwise the line should start with "nnn-" or "nnn " + else if(nBytes < 4 || iCode < 100) + iMore = 0; + // we got a valid line, now check for multiline responses ... + else if(iMore == 0 && pTxt[3] == '-') + iMore = iCode; + // "nnn " ends multiline mode ... + else if(iMore != 0 && (iMore != iCode || pTxt[3] != '-')) + iMore = 0; + + if(iMore != 0) + kdDebug(7102) << " > " << pTxt << endl; + } while(iMore != 0); + kdDebug(7102) << "resp> " << pTxt << endl; + + m_iRespType = (m_iRespCode > 0) ? m_iRespCode / 100 : 0; + } + + // return text with offset ... + while(iOffset-- > 0 && pTxt[0]) + pTxt++; + return pTxt; +} + + +void Ftp::closeConnection() +{ + if(m_control != NULL || m_data != NULL) + kdDebug(7102) << "Ftp::closeConnection m_bLoggedOn=" << m_bLoggedOn << " m_bBusy=" << m_bBusy << endl; + + if(m_bBusy) // ftpCloseCommand not called + { + kdWarning(7102) << "Ftp::closeConnection Abandoned data stream" << endl; + ftpCloseDataConnection(); + } + + if(m_bLoggedOn) // send quit + { + if( !ftpSendCmd( "quit", 0 ) || (m_iRespType != 2) ) + kdWarning(7102) << "Ftp::closeConnection QUIT returned error: " << m_iRespCode << endl; + } + + // close the data and control connections ... + ftpCloseDataConnection(); + ftpCloseControlConnection(); +} + +void Ftp::setHost( const TQString& _host, int _port, const TQString& _user, + const TQString& _pass ) +{ + kdDebug(7102) << "Ftp::setHost (" << getpid() << "): " << _host << endl; + + m_proxyURL = metaData("UseProxy"); + m_bUseProxy = (m_proxyURL.isValid() && m_proxyURL.protocol() == "ftp"); + + if ( m_host != _host || m_port != _port || + m_user != _user || m_pass != _pass ) + closeConnection(); + + m_host = _host; + m_port = _port; + m_user = _user; + m_pass = _pass; +} + +void Ftp::openConnection() +{ + ftpOpenConnection(loginExplicit); +} + +bool Ftp::ftpOpenConnection (LoginMode loginMode) +{ + // check for implicit login if we are already logged on ... + if(loginMode == loginImplicit && m_bLoggedOn) + { + assert(m_control != NULL); // must have control connection socket + return true; + } + + kdDebug(7102) << "ftpOpenConnection " << m_host << ":" << m_port << " " + << m_user << " [password hidden]" << endl; + + infoMessage( i18n("Opening connection to host %1").arg(m_host) ); + + if ( m_host.isEmpty() ) + { + error( ERR_UNKNOWN_HOST, TQString::null ); + return false; + } + + assert( !m_bLoggedOn ); + + m_initialPath = TQString::null; + m_currentPath = TQString::null; + + TQString host = m_bUseProxy ? m_proxyURL.host() : m_host; + unsigned short int port = m_bUseProxy ? m_proxyURL.port() : m_port; + + if (!ftpOpenControlConnection(host, port) ) + return false; // error emitted by ftpOpenControlConnection + infoMessage( i18n("Connected to host %1").arg(m_host) ); + + if(loginMode != loginDefered) + { + m_bLoggedOn = ftpLogin(); + if( !m_bLoggedOn ) + return false; // error emitted by ftpLogin + } + + m_bTextMode = config()->readBoolEntry("textmode", false); + connected(); + return true; +} + + +/** + * Called by @ref openConnection. It opens the control connection to the ftp server. + * + * @return true on success. + */ +bool Ftp::ftpOpenControlConnection( const TQString &host, unsigned short int port ) +{ + if ( port == 0 ) { + struct servent *pse; + if ( ( pse = getservbyname( "ftp", "tcp" ) ) == NULL ) + port = 21; + else + port = ntohs(pse->s_port); + } + + // implicitly close, then try to open a new connection ... + closeConnection(); + int iErrorCode = ERR_OUT_OF_MEMORY; + TQString sErrorMsg; + m_control = new FtpSocket("CNTL"); + if(m_control != NULL) + { + // now connect to the server and read the login message ... + m_control->setAddress(host, port); + iErrorCode = m_control->connectSocket(connectTimeout(), true); + sErrorMsg = host; + + // on connect success try to read the server message... + if(iErrorCode == 0) + { + const char* psz = ftpResponse(-1); + if(m_iRespType != 2) + { // login not successful, do we have an message text? + if(psz[0]) + sErrorMsg = i18n("%1.\n\nReason: %2").arg(host).arg(psz); + iErrorCode = ERR_COULD_NOT_CONNECT; + } + } + } + + // if there was a problem - report it ... + if(iErrorCode == 0) // OK, return success + return true; + closeConnection(); // clean-up on error + error(iErrorCode, sErrorMsg); + return false; +} + +/** + * Called by @ref openConnection. It logs us in. + * @ref m_initialPath is set to the current working directory + * if logging on was successful. + * + * @return true on success. + */ +bool Ftp::ftpLogin() +{ + infoMessage( i18n("Sending login information") ); + + assert( !m_bLoggedOn ); + + TQString user = m_user; + TQString pass = m_pass; + + if ( config()->readBoolEntry("EnableAutoLogin") ) + { + TQString au = config()->readEntry("autoLoginUser"); + if ( !au.isEmpty() ) + { + user = au; + pass = config()->readEntry("autoLoginPass"); + } + } + + // Try anonymous login if both username/password + // information is blank. + if (user.isEmpty() && pass.isEmpty()) + { + user = FTP_LOGIN; + pass = FTP_PASSWD; + } + + AuthInfo info; + info.url.setProtocol( "ftp" ); + info.url.setHost( m_host ); + info.url.setPort( m_port ); + info.url.setUser( user ); + + TQCString tempbuf; + int failedAuth = 0; + + do + { + // Check the cache and/or prompt user for password if 1st + // login attempt failed OR the user supplied a login name, + // but no password. + if ( failedAuth > 0 || (!user.isEmpty() && pass.isEmpty()) ) + { + TQString errorMsg; + kdDebug(7102) << "Prompting user for login info..." << endl; + + // Ask user if we should retry after when login fails! + if( failedAuth > 0 ) + { + errorMsg = i18n("Message sent:\nLogin using username=%1 and " + "password=[hidden]\n\nServer replied:\n%2\n\n" + ).arg(user).arg(ftpResponse(0)); + } + + if ( user != FTP_LOGIN ) + info.username = user; + + info.prompt = i18n("You need to supply a username and a password " + "to access this site."); + info.commentLabel = i18n( "Site:" ); + info.comment = i18n("%1").arg( m_host ); + info.keepPassword = true; // Prompt the user for persistence as well. + info.readOnly = (!m_user.isEmpty() && m_user != FTP_LOGIN); + + bool disablePassDlg = config()->readBoolEntry( "DisablePassDlg", false ); + if ( disablePassDlg || !openPassDlg( info, errorMsg ) ) + { + error( ERR_USER_CANCELED, m_host ); + return false; + } + else + { + user = info.username; + pass = info.password; + } + } + + tempbuf = "USER "; + tempbuf += user.latin1(); + if ( m_bUseProxy ) + { + tempbuf += '@'; + tempbuf += m_host.latin1(); + if ( m_port > 0 && m_port != DEFAULT_FTP_PORT ) + { + tempbuf += ':'; + tempbuf += TQString::number(m_port).latin1(); + } + } + + kdDebug(7102) << "Sending Login name: " << tempbuf << endl; + + bool loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) ); + bool needPass = (m_iRespCode == 331); + // Prompt user for login info if we do not + // get back a "230" or "331". + if ( !loggedIn && !needPass ) + { + kdDebug(7102) << "Login failed: " << ftpResponse(0) << endl; + ++failedAuth; + continue; // Well we failed, prompt the user please!! + } + + if( needPass ) + { + tempbuf = "pass "; + tempbuf += pass.latin1(); + kdDebug(7102) << "Sending Login password: " << "[protected]" << endl; + loggedIn = ( ftpSendCmd(tempbuf) && (m_iRespCode == 230) ); + } + + if ( loggedIn ) + { + // Do not cache the default login!! + if( user != FTP_LOGIN && pass != FTP_PASSWD ) + cacheAuthentication( info ); + failedAuth = -1; + } + + } while( ++failedAuth ); + + + kdDebug(7102) << "Login OK" << endl; + infoMessage( i18n("Login OK") ); + + // Okay, we're logged in. If this is IIS 4, switch dir listing style to Unix: + // Thanks to jk@soegaard.net (Jens Kristian Søgaard) for this hint + if( ftpSendCmd("SYST") && (m_iRespType == 2) ) + { + if( !strncmp( ftpResponse(0), "215 Windows_NT", 14 ) ) // should do for any version + { + ftpSendCmd( "site dirstyle" ); + // Check if it was already in Unix style + // Patch from Keith Refson + if( !strncmp( ftpResponse(0), "200 MSDOS-like directory output is on", 37 )) + //It was in Unix style already! + ftpSendCmd( "site dirstyle" ); + // windows won't support chmod before KDE konquers their desktop... + m_extControl |= chmodUnknown; + } + } + else + kdWarning(7102) << "SYST failed" << endl; + + if ( config()->readBoolEntry ("EnableAutoLoginMacro") ) + ftpAutoLoginMacro (); + + // Get the current working directory + kdDebug(7102) << "Searching for pwd" << endl; + if( !ftpSendCmd("PWD") || (m_iRespType != 2) ) + { + kdDebug(7102) << "Couldn't issue pwd command" << endl; + error( ERR_COULD_NOT_LOGIN, i18n("Could not login to %1.").arg(m_host) ); // or anything better ? + return false; + } + + TQString sTmp = remoteEncoding()->decode( ftpResponse(3) ); + int iBeg = sTmp.find('"'); + int iEnd = sTmp.findRev('"'); + if(iBeg > 0 && iBeg < iEnd) + { + m_initialPath = sTmp.mid(iBeg+1, iEnd-iBeg-1); + if(m_initialPath[0] != '/') m_initialPath.prepend('/'); + kdDebug(7102) << "Initial path set to: " << m_initialPath << endl; + m_currentPath = m_initialPath; + } + return true; +} + +void Ftp::ftpAutoLoginMacro () +{ + TQString macro = metaData( "autoLoginMacro" ); + + if ( macro.isEmpty() ) + return; + + TQStringList list = TQStringList::split('\n', macro); + + for(TQStringList::Iterator it = list.begin() ; it != list.end() ; ++it ) + { + if ( (*it).startsWith("init") ) + { + list = TQStringList::split( '\\', macro); + it = list.begin(); + ++it; // ignore the macro name + + for( ; it != list.end() ; ++it ) + { + // TODO: Add support for arbitrary commands + // besides simply changing directory!! + if ( (*it).startsWith( "cwd" ) ) + ftpFolder( (*it).mid(4).stripWhiteSpace(), false ); + } + + break; + } + } +} + + +/** + * ftpSendCmd - send a command (@p cmd) and read response + * + * @param maxretries number of time it should retry. Since it recursively + * calls itself if it can't read the answer (this happens especially after + * timeouts), we need to limit the recursiveness ;-) + * + * return true if any response received, false on error + */ +bool Ftp::ftpSendCmd( const TQCString& cmd, int maxretries ) +{ + assert(m_control != NULL); // must have control connection socket + + if ( cmd.find( '\r' ) != -1 || cmd.find( '\n' ) != -1) + { + kdWarning(7102) << "Invalid command received (contains CR or LF):" + << cmd.data() << endl; + error( ERR_UNSUPPORTED_ACTION, m_host ); + return false; + } + + // Don't print out the password... + bool isPassCmd = (cmd.left(4).lower() == "pass"); + if ( !isPassCmd ) + kdDebug(7102) << "send> " << cmd.data() << endl; + else + kdDebug(7102) << "send> pass [protected]" << endl; + + // Send the message... + TQCString buf = cmd; + buf += "\r\n"; // Yes, must use CR/LF - see http://cr.yp.to/ftp/request.html + int num = m_control->write(buf.data(), buf.length()); + + // If we were able to successfully send the command, then we will + // attempt to read the response. Otherwise, take action to re-attempt + // the login based on the maximum number of retires specified... + if( num > 0 ) + ftpResponse(-1); + else + { m_iRespType = m_iRespCode = 0; + m_control->textClear(); + } + + // If respCh is NULL or the response is 421 (Timed-out), we try to re-send + // the command based on the value of maxretries. + if( (m_iRespType <= 0) || (m_iRespCode == 421) ) + { + // We have not yet logged on... + if (!m_bLoggedOn) + { + // The command was sent from the ftpLogin function, i.e. we are actually + // attempting to login in. NOTE: If we already sent the username, we + // return false and let the user decide whether (s)he wants to start from + // the beginning... + if (maxretries > 0 && !isPassCmd) + { + closeConnection (); + if( ftpOpenConnection(loginDefered) ) + ftpSendCmd ( cmd, maxretries - 1 ); + } + + return false; + } + else + { + if ( maxretries < 1 ) + return false; + else + { + kdDebug(7102) << "Was not able to communicate with " << m_host << endl + << "Attempting to re-establish connection." << endl; + + closeConnection(); // Close the old connection... + openConnection(); // Attempt to re-establish a new connection... + + if (!m_bLoggedOn) + { + if (m_control != NULL) // if openConnection succeeded ... + { + kdDebug(7102) << "Login failure, aborting" << endl; + error (ERR_COULD_NOT_LOGIN, m_host); + closeConnection (); + } + return false; + } + + kdDebug(7102) << "Logged back in, re-issuing command" << endl; + + // If we were able to login, resend the command... + if (maxretries) + maxretries--; + + return ftpSendCmd( cmd, maxretries ); + } + } + } + + return true; +} + +/* + * ftpOpenPASVDataConnection - set up data connection, using PASV mode + * + * return 1 if successful, 0 otherwise + * doesn't set error message, since non-pasv mode will always be tried if + * this one fails + */ +int Ftp::ftpOpenPASVDataConnection() +{ + assert(m_control != NULL); // must have control connection socket + assert(m_data == NULL); // ... but no data connection + + // Check that we can do PASV + const TDESocketAddress *sa = m_control->peerAddress(); + if (sa != NULL && sa->family() != PF_INET) + return ERR_INTERNAL; // no PASV for non-PF_INET connections + + const KInetSocketAddress *sin = static_cast(sa); + + if (m_extControl & pasvUnknown) + return ERR_INTERNAL; // already tried and got "unknown command" + + m_bPasv = true; + + /* Let's PASsiVe*/ + if( !ftpSendCmd("PASV") || (m_iRespType != 2) ) + { + kdDebug(7102) << "PASV attempt failed" << endl; + // unknown command? + if( m_iRespType == 5 ) + { + kdDebug(7102) << "disabling use of PASV" << endl; + m_extControl |= pasvUnknown; + } + return ERR_INTERNAL; + } + + // The usual answer is '227 Entering Passive Mode. (160,39,200,55,6,245)' + // but anonftpd gives '227 =160,39,200,55,6,245' + int i[6]; + const char *start = strchr(ftpResponse(3), '('); + if ( !start ) + start = strchr(ftpResponse(3), '='); + if ( !start || + ( sscanf(start, "(%d,%d,%d,%d,%d,%d)",&i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 && + sscanf(start, "=%d,%d,%d,%d,%d,%d", &i[0], &i[1], &i[2], &i[3], &i[4], &i[5]) != 6 ) ) + { + kdError(7102) << "parsing IP and port numbers failed. String parsed: " << start << endl; + return ERR_INTERNAL; + } + + // Make hostname and port number ... + int port = i[4] << 8 | i[5]; + + // we ignore the host part on purpose for two reasons + // a) it might be wrong anyway + // b) it would make us being suceptible to a port scanning attack + + // now connect the data socket ... + m_data = new FtpSocket("PASV"); + m_data->setAddress(sin->nodeName(), port); + + kdDebug(7102) << "Connecting to " << sin->nodeName() << " on port " << port << endl; + return m_data->connectSocket(connectTimeout(), false); +} + +/* + * ftpOpenEPSVDataConnection - opens a data connection via EPSV + */ +int Ftp::ftpOpenEPSVDataConnection() +{ + assert(m_control != NULL); // must have control connection socket + assert(m_data == NULL); // ... but no data connection + + const TDESocketAddress *sa = m_control->peerAddress(); + int portnum; + // we are sure sa is a KInetSocketAddress, because we asked for KExtendedSocket::inetSocket + // when we connected + const KInetSocketAddress *sin = static_cast(sa); + + if (m_extControl & epsvUnknown || sa == NULL) + return ERR_INTERNAL; + + m_bPasv = true; + if( !ftpSendCmd("EPSV") || (m_iRespType != 2) ) + { + // unknown command? + if( m_iRespType == 5 ) + { + kdDebug(7102) << "disabling use of EPSV" << endl; + m_extControl |= epsvUnknown; + } + return ERR_INTERNAL; + } + + const char *start = strchr(ftpResponse(3), '|'); + if ( !start || sscanf(start, "|||%d|", &portnum) != 1) + return ERR_INTERNAL; + + m_data = new FtpSocket("EPSV"); + m_data->setAddress(sin->nodeName(), portnum); + return m_data->connectSocket(connectTimeout(), false) != 0; +} + +/* + * ftpOpenEPRTDataConnection + * @return 0 on success, ERR_INTERNAL if mode not acceptable -or- a fatal error code + */ +int Ftp::ftpOpenEPRTDataConnection() +{ + assert(m_control != NULL); // must have control connection socket + assert(m_data == NULL); // ... but no data connection + + // yes, we are sure this is a KInetSocketAddress + const KInetSocketAddress *sin = static_cast(m_control->localAddress()); + m_bPasv = false; + if (m_extControl & eprtUnknown || sin == NULL) + return ERR_INTERNAL; + + m_data = new FtpSocket("EPRT"); + m_data->setHost(sin->nodeName()); + m_data->setPort(0); // setting port to 0 will make us bind to a random, free port + m_data->setSocketFlags(KExtendedSocket::noResolve | KExtendedSocket::passiveSocket | + KExtendedSocket::inetSocket); + + if (m_data->listen(1) < 0) + return ERR_COULD_NOT_LISTEN; + + sin = static_cast(m_data->localAddress()); + if (sin == NULL) + return ERR_INTERNAL; + + // TQString command = TQString::fromLatin1("eprt |%1|%2|%3|").arg(sin->ianaFamily()) + // .arg(sin->nodeName()) + // .arg(sin->port()); + TQCString command; + command.sprintf("eprt |%d|%s|%d|", sin->ianaFamily(), + sin->nodeName().latin1(), sin->port()); + + // FIXME! Encoding for hostnames? + if( ftpSendCmd(command) && (m_iRespType == 2) ) + return 0; + + // unknown command? + if( m_iRespType == 5 ) + { + kdDebug(7102) << "disabling use of EPRT" << endl; + m_extControl |= eprtUnknown; + } + return ERR_INTERNAL; +} + +/* + * ftpOpenDataConnection - set up data connection + * + * The routine calls several ftpOpenXxxxConnection() helpers to find + * the best connection mode. If a helper cannot connect if returns + * ERR_INTERNAL - so this is not really an error! All other error + * codes are treated as fatal, e.g. they are passed back to the caller + * who is responsible for calling error(). ftpOpenPortDataConnection + * can be called as last try and it does never return ERR_INTERNAL. + * + * @return 0 if successful, err code otherwise + */ +int Ftp::ftpOpenDataConnection() +{ + // make sure that we are logged on and have no data connection... + assert( m_bLoggedOn ); + ftpCloseDataConnection(); + + int iErrCode = 0; + int iErrCodePASV = 0; // Remember error code from PASV + + // First try passive (EPSV & PASV) modes + if( !config()->readBoolEntry("DisablePassiveMode", false) ) + { + iErrCode = ftpOpenPASVDataConnection(); + if(iErrCode == 0) + return 0; // success + iErrCodePASV = iErrCode; + ftpCloseDataConnection(); + + if( !config()->readBoolEntry("DisableEPSV", false) ) + { + iErrCode = ftpOpenEPSVDataConnection(); + if(iErrCode == 0) + return 0; // success + ftpCloseDataConnection(); + } + + // if we sent EPSV ALL already and it was accepted, then we can't + // use active connections any more + if (m_extControl & epsvAllSent) + return iErrCodePASV ? iErrCodePASV : iErrCode; + } + + if( !config()->readBoolEntry("DisableEPRT", false) ) + { + iErrCode = ftpOpenEPRTDataConnection(); + if(iErrCode == 0) + return 0; // success + ftpCloseDataConnection(); + } + + // fall back to port mode + iErrCode = ftpOpenPortDataConnection(); + if(iErrCode == 0) + return 0; // success + + ftpCloseDataConnection(); + // prefer to return the error code from PASV if any, since that's what should have worked in the first place + return iErrCodePASV ? iErrCodePASV : iErrCode; +} + +/* + * ftpOpenPortDataConnection - set up data connection + * + * @return 0 if successfull, err code otherwise (but never ERR_INTERNAL + * because this is the last connection mode that is tried) + */ +int Ftp::ftpOpenPortDataConnection() +{ + assert(m_control != NULL); // must have control connection socket + assert(m_data == NULL); // ... but no data connection + + m_bPasv = false; + + // create a socket, bind it and let it listen ... + m_data = new FtpSocket("PORT"); + m_data->setSocketFlags(KExtendedSocket::noResolve | KExtendedSocket::passiveSocket | + KExtendedSocket::inetSocket); + + // yes, we are sure this is a KInetSocketAddress + const KInetSocketAddress* pAddr = static_cast(m_control->localAddress()); + m_data->setAddress(pAddr->nodeName(), "0"); + m_data->setAddressReusable(true); + + if(m_data->listen(1) < 0) + return ERR_COULD_NOT_LISTEN; + struct linger lng = { 0, 0 }; + if ( !m_data->setSocketOption(SO_LINGER, (char*)&lng, sizeof(lng)) ) + return ERR_COULD_NOT_CREATE_SOCKET; + + // send the PORT command ... + pAddr = static_cast(m_data->localAddress()); + struct sockaddr* psa = (struct sockaddr*)pAddr->addressV4(); + unsigned char* pData = (unsigned char*)(psa->sa_data); + TQCString portCmd; + portCmd.sprintf("port %d,%d,%d,%d,%d,%d", + pData[2], pData[3], pData[4], pData[5], pData[0], pData[1]); + if( ftpSendCmd(portCmd) && (m_iRespType == 2) ) + return 0; + return ERR_COULD_NOT_CONNECT; +} + +/* + * ftpAcceptConnect - wait for incoming connection + * Used by @ref ftpOpenCommand + * + * return false on error or timeout + */ +int Ftp::ftpAcceptConnect() +{ + assert(m_data != NULL); + + if ( m_bPasv ) + { + m_data->setServer(-1); + return true; + } + + int sSock = m_data->fd(); + struct sockaddr addr; + for(;;) + { + fd_set mask; + FD_ZERO(&mask); + FD_SET(sSock,&mask); + int r = KSocks::self()->select(sSock + 1, &mask, NULL, NULL, 0L); + if( r < 0 && errno != EINTR && errno != EAGAIN ) + continue; + if( r > 0 ) + break; + } + + ksocklen_t l = sizeof(addr); + m_data->setServer( KSocks::self()->accept(sSock, &addr, &l) ); + return (m_data->server() != -1); +} + +bool Ftp::ftpOpenCommand( const char *_command, const TQString & _path, char _mode, + int errorcode, TDEIO::fileoffset_t _offset ) +{ + int errCode = 0; + if( !ftpDataMode(_mode) ) + errCode = ERR_COULD_NOT_CONNECT; + else + errCode = ftpOpenDataConnection(); + + if(errCode != 0) + { + error(errCode, m_host); + return false; + } + + if ( _offset > 0 ) { + // send rest command if offset > 0, this applies to retr and stor commands + char buf[100]; + sprintf(buf, "rest %lld", _offset); + if ( !ftpSendCmd( buf ) ) + return false; + if( m_iRespType != 3 ) + { + error( ERR_CANNOT_RESUME, _path ); // should never happen + return false; + } + } + + TQCString tmp = _command; + TQString errormessage; + + if ( !_path.isEmpty() ) { + tmp += " "; + tmp += remoteEncoding()->encode(_path); + } + + if( !ftpSendCmd( tmp ) || (m_iRespType != 1) ) + { + if( _offset > 0 && strcmp(_command, "retr") == 0 && (m_iRespType == 4) ) + errorcode = ERR_CANNOT_RESUME; + // The error here depends on the command + errormessage = _path; + } + + else + { + // Only now we know for sure that we can resume + if ( _offset > 0 && strcmp(_command, "retr") == 0 ) + canResume(); + + if( ftpAcceptConnect() ) + { m_bBusy = true; // cleared in ftpCloseCommand + return true; + } + errorcode = ERR_COULD_NOT_ACCEPT; + } + + error(errorcode, errormessage); + return false; +} + + +bool Ftp::ftpCloseCommand() +{ + // first close data sockets (if opened), then read response that + // we got for whatever was used in ftpOpenCommand ( should be 226 ) + if(m_data) + { + delete m_data; + m_data = NULL; + } + if(!m_bBusy) + return true; + + kdDebug(7102) << "ftpCloseCommand: reading command result" << endl; + m_bBusy = false; + + if(!ftpResponse(-1) || (m_iRespType != 2) ) + { + kdDebug(7102) << "ftpCloseCommand: no transfer complete message" << endl; + return false; + } + return true; +} + +void Ftp::mkdir( const KURL & url, int permissions ) +{ + if( !ftpOpenConnection(loginImplicit) ) + return; + + TQString path = remoteEncoding()->encode(url); + TQCString buf = "mkd "; + buf += remoteEncoding()->encode(path); + + if( !ftpSendCmd( buf ) || (m_iRespType != 2) ) + { + TQString currentPath( m_currentPath ); + + // Check whether or not mkdir failed because + // the directory already exists... + if( ftpFolder( path, false ) ) + { + error( ERR_DIR_ALREADY_EXIST, path ); + // Change the directory back to what it was... + (void) ftpFolder( currentPath, false ); + return; + } + + error( ERR_COULD_NOT_MKDIR, path ); + return; + } + + if ( permissions != -1 ) + { + // chmod the dir we just created, ignoring errors. + (void) ftpChmod( path, permissions ); + } + + finished(); +} + +void Ftp::rename( const KURL& src, const KURL& dst, bool overwrite ) +{ + if( !ftpOpenConnection(loginImplicit) ) + return; + + // The actual functionality is in ftpRename because put needs it + if ( ftpRename( src.path(), dst.path(), overwrite ) ) + finished(); + else + error( ERR_CANNOT_RENAME, src.path() ); +} + +bool Ftp::ftpRename( const TQString & src, const TQString & dst, bool overwrite ) +{ + assert( m_bLoggedOn ); + + // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793). + if (!overwrite) { + if (ftpSize(dst, 'I')) { + error(ERR_FILE_ALREADY_EXIST, dst); + return false; + } + } + if (ftpFolder(dst, false)) { + error(ERR_DIR_ALREADY_EXIST, dst); + return false; + } + + // Must check if dst already exists, RNFR+RNTO overwrites by default (#127793). + if (ftpFileExists(dst)) { + error(ERR_FILE_ALREADY_EXIST, dst); + return false; + } + if (ftpFolder(dst, false)) { + error(ERR_DIR_ALREADY_EXIST, dst); + return false; + } + + int pos = src.findRev("/"); + if( !ftpFolder(src.left(pos+1), false) ) + return false; + + TQCString from_cmd = "RNFR "; + from_cmd += remoteEncoding()->encode(src.mid(pos+1)); + if( !ftpSendCmd( from_cmd ) || (m_iRespType != 3) ) + return false; + + TQCString to_cmd = "RNTO "; + to_cmd += remoteEncoding()->encode(dst); + if( !ftpSendCmd( to_cmd ) || (m_iRespType != 2) ) + return false; + + return true; +} + +void Ftp::del( const KURL& url, bool isfile ) +{ + if( !ftpOpenConnection(loginImplicit) ) + return; + + // When deleting a directory, we must exit from it first + // The last command probably went into it (to stat it) + if ( !isfile ) + ftpFolder(remoteEncoding()->directory(url), false); // ignore errors + + TQCString cmd = isfile ? "DELE " : "RMD "; + cmd += remoteEncoding()->encode(url); + + if( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) + error( ERR_CANNOT_DELETE, url.path() ); + else + finished(); +} + +bool Ftp::ftpChmod( const TQString & path, int permissions ) +{ + assert( m_bLoggedOn ); + + if(m_extControl & chmodUnknown) // previous errors? + return false; + + // we need to do bit AND 777 to get permissions, in case + // we were sent a full mode (unlikely) + TQCString cmd; + cmd.sprintf("SITE CHMOD %o ", permissions & 511 ); + cmd += remoteEncoding()->encode(path); + + ftpSendCmd(cmd); + if(m_iRespType == 2) + return true; + + if(m_iRespCode == 500) + { + m_extControl |= chmodUnknown; + kdDebug(7102) << "ftpChmod: CHMOD not supported - disabling"; + } + return false; +} + +void Ftp::chmod( const KURL & url, int permissions ) +{ + if( !ftpOpenConnection(loginImplicit) ) + return; + + if ( !ftpChmod( url.path(), permissions ) ) + error( ERR_CANNOT_CHMOD, url.path() ); + else + finished(); +} + +void Ftp::ftpCreateUDSEntry( const TQString & filename, FtpEntry& ftpEnt, UDSEntry& entry, bool isDir ) +{ + assert(entry.count() == 0); // by contract :-) + UDSAtom atom; + atom.m_uds = UDS_NAME; + atom.m_str = filename; + entry.append( atom ); + + atom.m_uds = UDS_SIZE; + atom.m_long = ftpEnt.size; + entry.append( atom ); + + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = ftpEnt.date; + entry.append( atom ); + + atom.m_uds = UDS_ACCESS; + atom.m_long = ftpEnt.access; + entry.append( atom ); + + atom.m_uds = UDS_USER; + atom.m_str = ftpEnt.owner; + entry.append( atom ); + + if ( !ftpEnt.group.isEmpty() ) + { + atom.m_uds = UDS_GROUP; + atom.m_str = ftpEnt.group; + entry.append( atom ); + } + + if ( !ftpEnt.link.isEmpty() ) + { + atom.m_uds = UDS_LINK_DEST; + atom.m_str = ftpEnt.link; + entry.append( atom ); + + KMimeType::Ptr mime = KMimeType::findByURL( KURL("ftp://host/" + filename ) ); + // Links on ftp sites are often links to dirs, and we have no way to check + // that. Let's do like Netscape : assume dirs generally. + // But we do this only when the mimetype can't be known from the filename. + // --> we do better than Netscape :-) + if ( mime->name() == KMimeType::defaultMimeType() ) + { + kdDebug(7102) << "Setting guessed mime type to inode/directory for " << filename << endl; + atom.m_uds = UDS_GUESSED_MIME_TYPE; + atom.m_str = "inode/directory"; + entry.append( atom ); + isDir = true; + } + } + + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = isDir ? S_IFDIR : ftpEnt.type; + entry.append( atom ); + + /* atom.m_uds = UDS_ACCESS_TIME; + atom.m_long = buff.st_atime; + entry.append( atom ); + + atom.m_uds = UDS_CREATION_TIME; + atom.m_long = buff.st_ctime; + entry.append( atom ); */ +} + + +void Ftp::ftpShortStatAnswer( const TQString& filename, bool isDir ) +{ + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = TDEIO::UDS_NAME; + atom.m_str = filename; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = isDir ? S_IFDIR : S_IFREG; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + entry.append( atom ); + + // No details about size, ownership, group, etc. + + statEntry(entry); + finished(); +} + +void Ftp::ftpStatAnswerNotFound( const TQString & path, const TQString & filename ) +{ + // Only do the 'hack' below if we want to download an existing file (i.e. when looking at the "source") + // When e.g. uploading a file, we still need stat() to return "not found" + // when the file doesn't exist. + TQString statSide = metaData("statSide"); + kdDebug(7102) << "Ftp::stat statSide=" << statSide << endl; + if ( statSide == "source" ) + { + kdDebug(7102) << "Not found, but assuming found, because some servers don't allow listing" << endl; + // MS Server is incapable of handling "list " in a case insensitive way + // But "retr " works. So lie in stat(), to get going... + // + // There's also the case of ftp://ftp2.3ddownloads.com/90380/linuxgames/loki/patches/ut/ut-patch-436.run + // where listing permissions are denied, but downloading is still possible. + ftpShortStatAnswer( filename, false /*file, not dir*/ ); + + return; + } + + error( ERR_DOES_NOT_EXIST, path ); +} + +void Ftp::stat( const KURL &url) +{ + kdDebug(7102) << "Ftp::stat : path='" << url.path() << "'" << endl; + if( !ftpOpenConnection(loginImplicit) ) + return; + + TQString path = TQDir::cleanDirPath( url.path() ); + kdDebug(7102) << "Ftp::stat : cleaned path='" << path << "'" << endl; + + // We can't stat root, but we know it's a dir. + if( path.isEmpty() || path == "/" ) + { + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = TDEIO::UDS_NAME; + atom.m_str = TQString::null; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_USER; + atom.m_str = "root"; + entry.append( atom ); + atom.m_uds = TDEIO::UDS_GROUP; + entry.append( atom ); + + // no size + + statEntry( entry ); + finished(); + return; + } + + KURL tempurl( url ); + tempurl.setPath( path ); // take the clean one + TQString listarg; // = tempurl.directory(false /*keep trailing slash*/); + TQString parentDir; + TQString filename = tempurl.fileName(); + Q_ASSERT(!filename.isEmpty()); + TQString search = filename; + + // Try cwd into it, if it works it's a dir (and then we'll list the parent directory to get more info) + // if it doesn't work, it's a file (and then we'll use dir filename) + bool isDir = ftpFolder(path, false); + + // if we're only interested in "file or directory", we should stop here + TQString sDetails = metaData("details"); + int details = sDetails.isEmpty() ? 2 : sDetails.toInt(); + kdDebug(7102) << "Ftp::stat details=" << details << endl; + if ( details == 0 ) + { + if ( !isDir && !ftpSize( path, 'I' ) ) // ok, not a dir -> is it a file ? + { // no -> it doesn't exist at all + ftpStatAnswerNotFound( path, filename ); + return; + } + ftpShortStatAnswer( filename, isDir ); // successfully found a dir or a file -> done + return; + } + + if (!isDir) + { + // It is a file or it doesn't exist, try going to parent directory + parentDir = tempurl.directory(false /*keep trailing slash*/); + // With files we can do "LIST " to avoid listing the whole dir + listarg = filename; + } + else + { + // --- New implementation: + // Don't list the parent dir. Too slow, might not show it, etc. + // Just return that it's a dir. + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = TDEIO::UDS_NAME; + atom.m_str = filename; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + entry.append( atom ); + + // No clue about size, ownership, group, etc. + + statEntry(entry); + finished(); + return; + + // --- Old implementation: +#if 0 + // It's a dir, remember that + // Reason: it could be a symlink to a dir, in which case ftpReadDir + // in the parent dir will have no idea about that. But we know better. + isDir = true; + // If the dir starts with '.', we'll need '-a' to see it in the listing. + if ( search[0] == '.' ) + listarg = "-a"; + parentDir = ".."; +#endif + } + + // Now cwd the parent dir, to prepare for listing + if( !ftpFolder(parentDir, true) ) + return; + + if( !ftpOpenCommand( "list", listarg, 'I', ERR_DOES_NOT_EXIST ) ) + { + kdError(7102) << "COULD NOT LIST" << endl; + return; + } + kdDebug(7102) << "Starting of list was ok" << endl; + + Q_ASSERT( !search.isEmpty() && search != "/" ); + + bool bFound = false; + KURL linkURL; + FtpEntry ftpEnt; + while( ftpReadDir(ftpEnt) ) + { + // We look for search or filename, since some servers (e.g. ftp.tuwien.ac.at) + // return only the filename when doing "dir /full/path/to/file" + if ( !bFound ) + { + if ( ( search == ftpEnt.name || filename == ftpEnt.name ) ) { + if ( !filename.isEmpty() ) { + bFound = true; + UDSEntry entry; + ftpCreateUDSEntry( filename, ftpEnt, entry, isDir ); + statEntry( entry ); + } + } else if ( isDir && ( ftpEnt.name == listarg || ftpEnt.name+'/' == listarg ) ) { + // Damn, the dir we're trying to list is in fact a symlink + // Follow it and try again + if ( ftpEnt.link.isEmpty() ) + kdWarning(7102) << "Got " << listarg << " as answer, but empty link!" << endl; + else + { + linkURL = url; + kdDebug(7102) << "ftpEnt.link=" << ftpEnt.link << endl; + if ( ftpEnt.link[0] == '/' ) + linkURL.setPath( ftpEnt.link ); // Absolute link + else + { + // Relative link (stat will take care of cleaning ../.. etc.) + linkURL.setPath( listarg ); // this is what we were listing (the link) + linkURL.setPath( linkURL.directory() ); // go up one dir + linkURL.addPath( ftpEnt.link ); // replace link by its destination + kdDebug(7102) << "linkURL now " << linkURL.prettyURL() << endl; + } + // Re-add the filename we're looking for + linkURL.addPath( filename ); + } + bFound = true; + } + } + + // kdDebug(7102) << ftpEnt.name << endl; + } + + ftpCloseCommand(); // closes the data connection only + + if ( !bFound ) + { + ftpStatAnswerNotFound( path, filename ); + return; + } + + if ( !linkURL.isEmpty() ) + { + if ( linkURL == url || linkURL == tempurl ) + { + error( ERR_CYCLIC_LINK, linkURL.prettyURL() ); + return; + } + stat( linkURL ); + return; + } + + kdDebug(7102) << "stat : finished successfully" << endl; + finished(); +} + + +void Ftp::listDir( const KURL &url ) +{ + kdDebug(7102) << "Ftp::listDir " << url.prettyURL() << endl; + if( !ftpOpenConnection(loginImplicit) ) + return; + + // No path specified ? + TQString path = url.path(); + if ( path.isEmpty() ) + { + KURL realURL; + realURL.setProtocol( "ftp" ); + if ( m_user != FTP_LOGIN ) + realURL.setUser( m_user ); + // We set the password, so that we don't ask for it if it was given + if ( m_pass != FTP_PASSWD ) + realURL.setPass( m_pass ); + realURL.setHost( m_host ); + realURL.setPort( m_port ); + if ( m_initialPath.isEmpty() ) + m_initialPath = "/"; + realURL.setPath( m_initialPath ); + kdDebug(7102) << "REDIRECTION to " << realURL.prettyURL() << endl; + redirection( realURL ); + finished(); + return; + } + + kdDebug(7102) << "hunting for path '" << path << "'" << endl; + + if (!ftpOpenDir( path ) ) + { + if ( ftpSize( path, 'I' ) ) // is it a file ? + { + error( ERR_IS_FILE, path ); + return; + } + // not sure which to emit + //error( ERR_DOES_NOT_EXIST, path ); + error( ERR_CANNOT_ENTER_DIRECTORY, path ); + return; + } + + UDSEntry entry; + FtpEntry ftpEnt; + while( ftpReadDir(ftpEnt) ) + { + //kdDebug(7102) << ftpEnt.name << endl; + //Q_ASSERT( !ftpEnt.name.isEmpty() ); + if ( !ftpEnt.name.isEmpty() ) + { + //if ( S_ISDIR( (mode_t)ftpEnt.type ) ) + // kdDebug(7102) << "is a dir" << endl; + //if ( !ftpEnt.link.isEmpty() ) + // kdDebug(7102) << "is a link to " << ftpEnt.link << endl; + entry.clear(); + ftpCreateUDSEntry( ftpEnt.name, ftpEnt, entry, false ); + listEntry( entry, false ); + } + } + listEntry( entry, true ); // ready + ftpCloseCommand(); // closes the data connection only + finished(); +} + +void Ftp::slave_status() +{ + kdDebug(7102) << "Got slave_status host = " << (m_host.ascii() ? m_host.ascii() : "[None]") << " [" << (m_bLoggedOn ? "Connected" : "Not connected") << "]" << endl; + slaveStatus( m_host, m_bLoggedOn ); +} + +bool Ftp::ftpOpenDir( const TQString & path ) +{ + //TQString path( _url.path(-1) ); + + // We try to change to this directory first to see whether it really is a directory. + // (And also to follow symlinks) + TQString tmp = path.isEmpty() ? TQString("/") : path; + + // We get '550', whether it's a file or doesn't exist... + if( !ftpFolder(tmp, false) ) + return false; + + // Don't use the path in the list command: + // We changed into this directory anyway - so it's enough just to send "list". + // We use '-a' because the application MAY be interested in dot files. + // The only way to really know would be to have a metadata flag for this... + // Since some windows ftp server seems not to support the -a argument, we use a fallback here. + // In fact we have to use -la otherwise -a removes the default -l (e.g. ftp.trolltech.com) + if( !ftpOpenCommand( "list -la", TQString::null, 'I', ERR_CANNOT_ENTER_DIRECTORY ) ) + { + if ( !ftpOpenCommand( "list", TQString::null, 'I', ERR_CANNOT_ENTER_DIRECTORY ) ) + { + kdWarning(7102) << "Can't open for listing" << endl; + return false; + } + } + kdDebug(7102) << "Starting of list was ok" << endl; + return true; +} + +bool Ftp::ftpReadDir(FtpEntry& de) +{ + assert(m_data != NULL); + + // get a line from the data connecetion ... + while( !m_data->textEOF() ) + { + if(m_data->textRead() <= 0) + continue; + if(m_data->textTooLong()) + kdWarning(7102) << "ftpReadDir line too long - truncated" << endl; + + const char* buffer = m_data->textLine(); + kdDebug(7102) << "dir > " << buffer << endl; + + //Normally the listing looks like + // -rw-r--r-- 1 dfaure dfaure 102 Nov 9 12:30 log + // but on Netware servers like ftp://ci-1.ci.pwr.wroc.pl/ it looks like (#76442) + // d [RWCEAFMS] Admin 512 Oct 13 2004 PSI + + // we should always get the following 5 fields ... + const char *p_access, *p_junk, *p_owner, *p_group, *p_size; + if( (p_access = strtok((char*)buffer," ")) == 0) continue; + if( (p_junk = strtok(NULL," ")) == 0) continue; + if( (p_owner = strtok(NULL," ")) == 0) continue; + if( (p_group = strtok(NULL," ")) == 0) continue; + if( (p_size = strtok(NULL," ")) == 0) continue; + + //kdDebug(7102) << "p_access=" << p_access << " p_junk=" << p_junk << " p_owner=" << p_owner << " p_group=" << p_group << " p_size=" << p_size << endl; + + de.access = 0; + if ( strlen( p_access ) == 1 && p_junk[0] == '[' ) { // Netware + de.access = S_IRWXU | S_IRWXG | S_IRWXO; // unknown -> give all permissions + } + + const char *p_date_1, *p_date_2, *p_date_3, *p_name; + + // A special hack for "/dev". A listing may look like this: + // crw-rw-rw- 1 root root 1, 5 Jun 29 1997 zero + // So we just ignore the number in front of the ",". Ok, its a hack :-) + if ( strchr( p_size, ',' ) != 0L ) + { + //kdDebug(7102) << "Size contains a ',' -> reading size again (/dev hack)" << endl; + if ((p_size = strtok(NULL," ")) == 0) + continue; + } + + // Check whether the size we just read was really the size + // or a month (this happens when the server lists no group) + // Used to be the case on sunsite.uio.no, but not anymore + // This is needed for the Netware case, too. + if ( !isdigit( *p_size ) ) + { + p_date_1 = p_size; + p_size = p_group; + p_group = 0; + //kdDebug(7102) << "Size didn't have a digit -> size=" << p_size << " date_1=" << p_date_1 << endl; + } + else + { + p_date_1 = strtok(NULL," "); + //kdDebug(7102) << "Size has a digit -> ok. p_date_1=" << p_date_1 << endl; + } + + if ( p_date_1 != 0 && + (p_date_2 = strtok(NULL," ")) != 0 && + (p_date_3 = strtok(NULL," ")) != 0 && + (p_name = strtok(NULL,"\r\n")) != 0 ) + { + { + TQCString tmp( p_name ); + if ( p_access[0] == 'l' ) + { + int i = tmp.findRev( " -> " ); + if ( i != -1 ) { + de.link = remoteEncoding()->decode(p_name + i + 4); + tmp.truncate( i ); + } + else + de.link = TQString::null; + } + else + de.link = TQString::null; + + if ( tmp[0] == '/' ) // listing on ftp://ftp.gnupg.org/ starts with '/' + tmp.remove( 0, 1 ); + + if (tmp.find('/') != -1) + continue; // Don't trick us! + // Some sites put more than one space between the date and the name + // e.g. ftp://ftp.uni-marburg.de/mirror/ + de.name = remoteEncoding()->decode(tmp.stripWhiteSpace()); + } + + de.type = S_IFREG; + switch ( p_access[0] ) { + case 'd': + de.type = S_IFDIR; + break; + case 's': + de.type = S_IFSOCK; + break; + case 'b': + de.type = S_IFBLK; + break; + case 'c': + de.type = S_IFCHR; + break; + case 'l': + de.type = S_IFREG; + // we don't set S_IFLNK here. de.link says it. + break; + default: + break; + } + + if ( p_access[1] == 'r' ) + de.access |= S_IRUSR; + if ( p_access[2] == 'w' ) + de.access |= S_IWUSR; + if ( p_access[3] == 'x' || p_access[3] == 's' ) + de.access |= S_IXUSR; + if ( p_access[4] == 'r' ) + de.access |= S_IRGRP; + if ( p_access[5] == 'w' ) + de.access |= S_IWGRP; + if ( p_access[6] == 'x' || p_access[6] == 's' ) + de.access |= S_IXGRP; + if ( p_access[7] == 'r' ) + de.access |= S_IROTH; + if ( p_access[8] == 'w' ) + de.access |= S_IWOTH; + if ( p_access[9] == 'x' || p_access[9] == 't' ) + de.access |= S_IXOTH; + if ( p_access[3] == 's' || p_access[3] == 'S' ) + de.access |= S_ISUID; + if ( p_access[6] == 's' || p_access[6] == 'S' ) + de.access |= S_ISGID; + if ( p_access[9] == 't' || p_access[9] == 'T' ) + de.access |= S_ISVTX; + + de.owner = remoteEncoding()->decode(p_owner); + de.group = remoteEncoding()->decode(p_group); + de.size = charToLongLong(p_size); + + // Parsing the date is somewhat tricky + // Examples : "Oct 6 22:49", "May 13 1999" + + // First get current time - we need the current month and year + time_t currentTime = time( 0L ); + struct tm * tmptr = gmtime( ¤tTime ); + int currentMonth = tmptr->tm_mon; + //kdDebug(7102) << "Current time :" << asctime( tmptr ) << endl; + // Reset time fields + tmptr->tm_isdst = -1; // We do not know anything about day saving time (of any random day of the year) + tmptr->tm_sec = 0; + tmptr->tm_min = 0; + tmptr->tm_hour = 0; + // Get day number (always second field) + tmptr->tm_mday = atoi( p_date_2 ); + // Get month from first field + // NOTE : no, we don't want to use TDELocale here + // It seems all FTP servers use the English way + //kdDebug(7102) << "Looking for month " << p_date_1 << endl; + static const char * s_months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + for ( int c = 0 ; c < 12 ; c ++ ) + if ( !strcmp( p_date_1, s_months[c]) ) + { + //kdDebug(7102) << "Found month " << c << " for " << p_date_1 << endl; + tmptr->tm_mon = c; + break; + } + + // Parse third field + if ( strlen( p_date_3 ) == 4 ) // 4 digits, looks like a year + tmptr->tm_year = atoi( p_date_3 ) - 1900; + else + { + // otherwise, the year is implicit + // according to man ls, this happens when it is between than 6 months + // old and 1 hour in the future. + // So the year is : current year if tm_mon <= currentMonth+1 + // otherwise current year minus one + // (The +1 is a security for the "+1 hour" at the end of the month issue) + if ( tmptr->tm_mon > currentMonth + 1 ) + tmptr->tm_year--; + + // and p_date_3 contains probably a time + char * semicolon; + if ( ( semicolon = const_cast(strchr( p_date_3, ':' )) ) ) + { + *semicolon = '\0'; + tmptr->tm_min = atoi( semicolon + 1 ); + tmptr->tm_hour = atoi( p_date_3 ); + } + else + kdWarning(7102) << "Can't parse third field " << p_date_3 << endl; + } + + //kdDebug(7102) << asctime( tmptr ) << endl; + de.date = mktime( tmptr ); + return true; + } + } // line invalid, loop to get another line + return false; +} + +//=============================================================================== +// public: get download file from server +// helper: ftpGet called from get() and copy() +//=============================================================================== +void Ftp::get( const KURL & url ) +{ + kdDebug(7102) << "Ftp::get " << url.url() << endl; + int iError = 0; + ftpGet(iError, -1, url, 0); // iError gets status + if(iError) // can have only server side errs + error(iError, url.path()); + ftpCloseCommand(); // must close command! +} + +Ftp::StatusCode Ftp::ftpGet(int& iError, int iCopyFile, const KURL& url, TDEIO::fileoffset_t llOffset) +{ + // Calls error() by itself! + if( !ftpOpenConnection(loginImplicit) ) + return statusServerError; + + // Try to find the size of the file (and check that it exists at + // the same time). If we get back a 550, "File does not exist" + // or "not a plain file", check if it is a directory. If it is a + // directory, return an error; otherwise simply try to retrieve + // the request... + if ( !ftpSize( url.path(), '?' ) && (m_iRespCode == 550) && + ftpFolder(url.path(), false) ) + { + // Ok it's a dir in fact + kdDebug(7102) << "ftpGet: it is a directory in fact" << endl; + iError = ERR_IS_DIRECTORY; + return statusServerError; + } + + TQString resumeOffset = metaData("resume"); + if ( !resumeOffset.isEmpty() ) + { + llOffset = resumeOffset.toLongLong(); + kdDebug(7102) << "ftpGet: got offset from metadata : " << llOffset << endl; + } + + if( !ftpOpenCommand("retr", url.path(), '?', ERR_CANNOT_OPEN_FOR_READING, llOffset) ) + { + kdWarning(7102) << "ftpGet: Can't open for reading" << endl; + return statusServerError; + } + + // Read the size from the response string + if(m_size == UnknownSize) + { + const char* psz = strrchr( ftpResponse(4), '(' ); + if(psz) m_size = charToLongLong(psz+1); + if (!m_size) m_size = UnknownSize; + } + + TDEIO::filesize_t bytesLeft = 0; + if ( m_size != UnknownSize ) + bytesLeft = m_size - llOffset; + + kdDebug(7102) << "ftpGet: starting with offset=" << llOffset << endl; + TDEIO::fileoffset_t processed_size = llOffset; + + TQByteArray array; + bool mimetypeEmitted = false; + char buffer[maximumIpcSize]; + // start whith small data chunks in case of a slow data source (modem) + // - unfortunately this has a negative impact on performance for large + // - files - so we will increase the block size after a while ... + int iBlockSize = initialIpcSize; + int iBufferCur = 0; + + while(m_size == UnknownSize || bytesLeft > 0) + { // let the buffer size grow if the file is larger 64kByte ... + if(processed_size-llOffset > 1024 * 64) + iBlockSize = maximumIpcSize; + + // read the data and detect EOF or error ... + if(iBlockSize+iBufferCur > (int)sizeof(buffer)) + iBlockSize = sizeof(buffer) - iBufferCur; + int n = m_data->read( buffer+iBufferCur, iBlockSize ); + if(n <= 0) + { // this is how we detect EOF in case of unknown size + if( m_size == UnknownSize && n == 0 ) + break; + // unexpected eof. Happens when the daemon gets killed. + iError = ERR_COULD_NOT_READ; + return statusServerError; + } + processed_size += n; + + // collect very small data chunks in buffer before processing ... + if(m_size != UnknownSize) + { + bytesLeft -= n; + iBufferCur += n; + if(iBufferCur < mimimumMimeSize && bytesLeft > 0) + { + processedSize( processed_size ); + continue; + } + n = iBufferCur; + iBufferCur = 0; + } + + // get the mime type and set the total size ... + if(!mimetypeEmitted) + { + mimetypeEmitted = true; + + // We need a KMimeType::findByNameAndContent(data,filename) + // For now we do: find by extension, and if not found (or extension not reliable) + // then find by content. + bool accurate = false; + KMimeType::Ptr mime = KMimeType::findByURL( url, 0, false, true, &accurate ); + if ( !mime || mime->name() == KMimeType::defaultMimeType() + || !accurate ) + { + array.setRawData(buffer, n); + KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType(array, url.fileName()); + array.resetRawData(buffer, n); + if ( result->mimeType() != KMimeType::defaultMimeType() ) + mime = KMimeType::mimeType( result->mimeType() ); + } + + kdDebug(7102) << "ftpGet: Emitting mimetype " << mime->name() << endl; + mimeType( mime->name() ); + if( m_size != UnknownSize ) // Emit total size AFTER mimetype + totalSize( m_size ); + } + + // write output file or pass to data pump ... + if(iCopyFile == -1) + { + array.setRawData(buffer, n); + data( array ); + array.resetRawData(buffer, n); + } + else if( (iError = WriteToFile(iCopyFile, buffer, n)) != 0) + return statusClientError; // client side error + processedSize( processed_size ); + } + + kdDebug(7102) << "ftpGet: done" << endl; + if(iCopyFile == -1) // must signal EOF to data pump ... + data(array); // array is empty and must be empty! + + processedSize( m_size == UnknownSize ? processed_size : m_size ); + kdDebug(7102) << "ftpGet: emitting finished()" << endl; + finished(); + return statusSuccess; +} + +/* +void Ftp::mimetype( const KURL& url ) +{ + if( !ftpOpenConnection(loginImplicit) ) + return; + + if ( !ftpOpenCommand( "retr", url.path(), 'I', ERR_CANNOT_OPEN_FOR_READING, 0 ) ) { + kdWarning(7102) << "Can't open for reading" << endl; + return; + } + char buffer[ 2048 ]; + TQByteArray array; + // Get one chunk of data only and send it, TDEIO::Job will determine the + // mimetype from it using KMimeMagic + int n = m_data->read( buffer, 2048 ); + array.setRawData(buffer, n); + data( array ); + array.resetRawData(buffer, n); + + kdDebug(7102) << "aborting" << endl; + ftpAbortTransfer(); + + kdDebug(7102) << "finished" << endl; + finished(); + kdDebug(7102) << "after finished" << endl; +} + +void Ftp::ftpAbortTransfer() +{ + // RFC 959, page 34-35 + // IAC (interpret as command) = 255 ; IP (interrupt process) = 254 + // DM = 242 (data mark) + char msg[4]; + // 1. User system inserts the Telnet "Interrupt Process" (IP) signal + // in the Telnet stream. + msg[0] = (char) 255; //IAC + msg[1] = (char) 254; //IP + (void) send(sControl, msg, 2, 0); + // 2. User system sends the Telnet "Sync" signal. + msg[0] = (char) 255; //IAC + msg[1] = (char) 242; //DM + if (send(sControl, msg, 2, MSG_OOB) != 2) + ; // error... + + // Send ABOR + kdDebug(7102) << "send ABOR" << endl; + TQCString buf = "ABOR\r\n"; + if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 ) { + error( ERR_COULD_NOT_WRITE, TQString::null ); + return; + } + + // + kdDebug(7102) << "read resp" << endl; + if ( readresp() != '2' ) + { + error( ERR_COULD_NOT_READ, TQString::null ); + return; + } + + kdDebug(7102) << "close sockets" << endl; + closeSockets(); +} +*/ + +//=============================================================================== +// public: put upload file to server +// helper: ftpPut called from put() and copy() +//=============================================================================== +void Ftp::put(const KURL& url, int permissions, bool overwrite, bool resume) +{ + kdDebug(7102) << "Ftp::put " << url.url() << endl; + int iError = 0; // iError gets status + ftpPut(iError, -1, url, permissions, overwrite, resume); + if(iError) // can have only server side errs + error(iError, url.path()); + ftpCloseCommand(); // must close command! +} + +Ftp::StatusCode Ftp::ftpPut(int& iError, int iCopyFile, const KURL& dest_url, + int permissions, bool overwrite, bool resume) +{ + if( !ftpOpenConnection(loginImplicit) ) + return statusServerError; + + // Don't use mark partial over anonymous FTP. + // My incoming dir allows put but not rename... + bool bMarkPartial; + if (m_user.isEmpty () || m_user == FTP_LOGIN) + bMarkPartial = false; + else + bMarkPartial = config()->readBoolEntry("MarkPartial", true); + + TQString dest_orig = dest_url.path(); + TQString dest_part( dest_orig ); + dest_part += ".part"; + + if ( ftpSize( dest_orig, 'I' ) ) + { + if ( m_size == 0 ) + { // delete files with zero size + TQCString cmd = "DELE "; + cmd += remoteEncoding()->encode(dest_orig); + if( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) + { + iError = ERR_CANNOT_DELETE_PARTIAL; + return statusServerError; + } + } + else if ( !overwrite && !resume ) + { + iError = ERR_FILE_ALREADY_EXIST; + return statusServerError; + } + else if ( bMarkPartial ) + { // when using mark partial, append .part extension + if ( !ftpRename( dest_orig, dest_part, true ) ) + { + iError = ERR_CANNOT_RENAME_PARTIAL; + return statusServerError; + } + } + // Don't chmod an existing file + permissions = -1; + } + else if ( bMarkPartial && ftpSize( dest_part, 'I' ) ) + { // file with extension .part exists + if ( m_size == 0 ) + { // delete files with zero size + TQCString cmd = "DELE "; + cmd += remoteEncoding()->encode(dest_part); + if ( !ftpSendCmd( cmd ) || (m_iRespType != 2) ) + { + iError = ERR_CANNOT_DELETE_PARTIAL; + return statusServerError; + } + } + else if ( !overwrite && !resume ) + { + resume = canResume (m_size); + if (!resume) + { + iError = ERR_FILE_ALREADY_EXIST; + return statusServerError; + } + } + } + else + m_size = 0; + + TQString dest; + + // if we are using marking of partial downloads -> add .part extension + if ( bMarkPartial ) { + kdDebug(7102) << "Adding .part extension to " << dest_orig << endl; + dest = dest_part; + } else + dest = dest_orig; + + TDEIO::fileoffset_t offset = 0; + + // set the mode according to offset + if( resume && m_size > 0 ) + { + offset = m_size; + if(iCopyFile != -1) + { + if( KDE_lseek(iCopyFile, offset, SEEK_SET) < 0 ) + { + iError = ERR_CANNOT_RESUME; + return statusClientError; + } + } + } + + if (! ftpOpenCommand( "stor", dest, '?', ERR_COULD_NOT_WRITE, offset ) ) + return statusServerError; + + kdDebug(7102) << "ftpPut: starting with offset=" << offset << endl; + TDEIO::fileoffset_t processed_size = offset; + + TQByteArray buffer; + int result; + int iBlockSize = initialIpcSize; + // Loop until we got 'dataEnd' + do + { + if(iCopyFile == -1) + { + dataReq(); // Request for data + result = readData( buffer ); + } + else + { // let the buffer size grow if the file is larger 64kByte ... + if(processed_size-offset > 1024 * 64) + iBlockSize = maximumIpcSize; + buffer.resize(iBlockSize); + result = ::read(iCopyFile, buffer.data(), buffer.size()); + if(result < 0) + iError = ERR_COULD_NOT_WRITE; + else + buffer.resize(result); + } + + if (result > 0) + { + m_data->write( buffer.data(), buffer.size() ); + processed_size += result; + processedSize (processed_size); + } + } + while ( result > 0 ); + + if (result != 0) // error + { + ftpCloseCommand(); // don't care about errors + kdDebug(7102) << "Error during 'put'. Aborting." << endl; + if (bMarkPartial) + { + // Remove if smaller than minimum size + if ( ftpSize( dest, 'I' ) && + ( processed_size < (unsigned long) config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE) ) ) + { + TQCString cmd = "DELE "; + cmd += remoteEncoding()->encode(dest); + (void) ftpSendCmd( cmd ); + } + } + return statusServerError; + } + + if ( !ftpCloseCommand() ) + { + iError = ERR_COULD_NOT_WRITE; + return statusServerError; + } + + // after full download rename the file back to original name + if ( bMarkPartial ) + { + kdDebug(7102) << "renaming dest (" << dest << ") back to dest_orig (" << dest_orig << ")" << endl; + if ( !ftpRename( dest, dest_orig, true ) ) + { + iError = ERR_CANNOT_RENAME_PARTIAL; + return statusServerError; + } + } + + // set final permissions + if ( permissions != -1 ) + { + if ( m_user == FTP_LOGIN ) + kdDebug(7102) << "Trying to chmod over anonymous FTP ???" << endl; + // chmod the file we just put + if ( ! ftpChmod( dest_orig, permissions ) ) + { + // To be tested + //if ( m_user != FTP_LOGIN ) + // warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) ); + } + } + + // We have done our job => finish + finished(); + return statusSuccess; +} + + +/** Use the SIZE command to get the file size. + Warning : the size depends on the transfer mode, hence the second arg. */ +bool Ftp::ftpSize( const TQString & path, char mode ) +{ + m_size = UnknownSize; + if( !ftpDataMode(mode) ) + return false; + + TQCString buf; + buf = "SIZE "; + buf += remoteEncoding()->encode(path); + if( !ftpSendCmd( buf ) || (m_iRespType != 2) ) + return false; + + // skip leading "213 " (response code) + const char* psz = ftpResponse(4); + if(!psz) + return false; + m_size = charToLongLong(psz); + if (!m_size) m_size = UnknownSize; + return true; +} + +bool Ftp::ftpFileExists(const TQString& path) +{ + TQCString buf; + buf = "SIZE "; + buf += remoteEncoding()->encode(path); + if( !ftpSendCmd( buf ) || (m_iRespType != 2) ) + return false; + + // skip leading "213 " (response code) + const char* psz = ftpResponse(4); + return psz != 0; +} + +// Today the differences between ASCII and BINARY are limited to +// CR or CR/LF line terminators. Many servers ignore ASCII (like +// win2003 -or- vsftp with default config). In the early days of +// computing, when even text-files had structure, this stuff was +// more important. +// Theoretically "list" could return different results in ASCII +// and BINARY mode. But again, most servers ignore ASCII here. +bool Ftp::ftpDataMode(char cMode) +{ + if(cMode == '?') cMode = m_bTextMode ? 'A' : 'I'; + else if(cMode == 'a') cMode = 'A'; + else if(cMode != 'A') cMode = 'I'; + + kdDebug(7102) << "ftpDataMode: want '" << cMode << "' has '" << m_cDataMode << "'" << endl; + if(m_cDataMode == cMode) + return true; + + TQCString buf; + buf.sprintf("TYPE %c", cMode); + if( !ftpSendCmd(buf) || (m_iRespType != 2) ) + return false; + m_cDataMode = cMode; + return true; +} + + +bool Ftp::ftpFolder(const TQString& path, bool bReportError) +{ + TQString newPath = path; + int iLen = newPath.length(); + if(iLen > 1 && newPath[iLen-1] == '/') newPath.truncate(iLen-1); + + //kdDebug(7102) << "ftpFolder: want '" << newPath << "' has '" << m_currentPath << "'" << endl; + if(m_currentPath == newPath) + return true; + + TQCString tmp = "cwd "; + tmp += remoteEncoding()->encode(newPath); + if( !ftpSendCmd(tmp) ) + return false; // connection failure + if(m_iRespType != 2) + { + if(bReportError) + error(ERR_CANNOT_ENTER_DIRECTORY, path); + return false; // not a folder + } + m_currentPath = newPath; + return true; +} + + +//=============================================================================== +// public: copy don't use tdeio data pump if one side is a local file +// helper: ftpCopyPut called from copy() on upload +// helper: ftpCopyGet called from copy() on download +//=============================================================================== +void Ftp::copy( const KURL &src, const KURL &dest, int permissions, bool overwrite ) +{ + int iError = 0; + int iCopyFile = -1; + StatusCode cs = statusSuccess; + bool bSrcLocal = src.isLocalFile(); + bool bDestLocal = dest.isLocalFile(); + TQString sCopyFile; + + if(bSrcLocal && !bDestLocal) // File -> Ftp + { + sCopyFile = src.path(); + kdDebug(7102) << "Ftp::copy local file '" << sCopyFile << "' -> ftp '" << dest.path() << "'" << endl; + cs = ftpCopyPut(iError, iCopyFile, sCopyFile, dest, permissions, overwrite); + if( cs == statusServerError) sCopyFile = dest.url(); + } + else if(!bSrcLocal && bDestLocal) // Ftp -> File + { + sCopyFile = dest.path(); + kdDebug(7102) << "Ftp::copy ftp '" << src.path() << "' -> local file '" << sCopyFile << "'" << endl; + cs = ftpCopyGet(iError, iCopyFile, sCopyFile, src, permissions, overwrite); + if( cs == statusServerError ) sCopyFile = src.url(); + } + else { + error( ERR_UNSUPPORTED_ACTION, TQString::null ); + return; + } + + // perform clean-ups and report error (if any) + if(iCopyFile != -1) + ::close(iCopyFile); + if(iError) + error(iError, sCopyFile); + ftpCloseCommand(); // must close command! +} + + +Ftp::StatusCode Ftp::ftpCopyPut(int& iError, int& iCopyFile, TQString sCopyFile, + const KURL& url, int permissions, bool overwrite) +{ + // check if source is ok ... + KDE_struct_stat buff; + TQCString sSrc( TQFile::encodeName(sCopyFile) ); + bool bSrcExists = (KDE_stat( sSrc.data(), &buff ) != -1); + if(bSrcExists) + { if(S_ISDIR(buff.st_mode)) + { + iError = ERR_IS_DIRECTORY; + return statusClientError; + } + } + else + { + iError = ERR_DOES_NOT_EXIST; + return statusClientError; + } + + iCopyFile = KDE_open( sSrc.data(), O_RDONLY ); + if(iCopyFile == -1) + { + iError = ERR_CANNOT_OPEN_FOR_READING; + return statusClientError; + } + + // delegate the real work (iError gets status) ... + totalSize(buff.st_size); +#ifdef ENABLE_CAN_RESUME + return ftpPut(iError, iCopyFile, url, permissions, overwrite, false); +#else + return ftpPut(iError, iCopyFile, url, permissions, overwrite, true); +#endif +} + + +Ftp::StatusCode Ftp::ftpCopyGet(int& iError, int& iCopyFile, const TQString sCopyFile, + const KURL& url, int permissions, bool overwrite) +{ + // check if destination is ok ... + KDE_struct_stat buff; + TQCString sDest( TQFile::encodeName(sCopyFile) ); + bool bDestExists = (KDE_stat( sDest.data(), &buff ) != -1); + if(bDestExists) + { if(S_ISDIR(buff.st_mode)) + { + iError = ERR_IS_DIRECTORY; + return statusClientError; + } + if(!overwrite) + { + iError = ERR_FILE_ALREADY_EXIST; + return statusClientError; + } + } + + // do we have a ".part" file? + TQCString sPart = TQFile::encodeName(sCopyFile + ".part"); + bool bResume = false; + bool bPartExists = (KDE_stat( sPart.data(), &buff ) != -1); + const bool bMarkPartial = config()->readBoolEntry("MarkPartial", true); + + if(!bMarkPartial) + { + sPart = TQFile::encodeName(sCopyFile); + } + else if(bPartExists && buff.st_size > 0) + { // must not be a folder! please fix a similar bug in tdeio_file!! + if(S_ISDIR(buff.st_mode)) + { + iError = ERR_DIR_ALREADY_EXIST; + return statusClientError; // client side error + } + //doesn't work for copy? -> design flaw? +#ifdef ENABLE_CAN_RESUME + bResume = canResume( buff.st_size ); +#else + bResume = true; +#endif + } + + if(bPartExists && !bResume) // get rid of an unwanted ".part" file + remove(sPart.data()); + + // JPF: in tdeio_file overwrite disables ".part" operations. I do not believe + // JPF: that this is a good behaviour! + if(bDestExists) // must delete for overwrite + remove(sDest.data()); + + // WABA: Make sure that we keep writing permissions ourselves, + // otherwise we can be in for a surprise on NFS. + mode_t initialMode; + if (permissions != -1) + initialMode = permissions | S_IWUSR; + else + initialMode = 0666; + + // open the output file ... + TDEIO::fileoffset_t hCopyOffset = 0; + if(bResume) + { + iCopyFile = KDE_open( sPart.data(), O_RDWR ); // append if resuming + hCopyOffset = KDE_lseek(iCopyFile, 0, SEEK_END); + if(hCopyOffset < 0) + { + iError = ERR_CANNOT_RESUME; + return statusClientError; // client side error + } + kdDebug(7102) << "copy: resuming at " << hCopyOffset << endl; + } + else + iCopyFile = KDE_open(sPart.data(), O_CREAT | O_TRUNC | O_WRONLY, initialMode); + + if(iCopyFile == -1) + { + kdDebug(7102) << "copy: ### COULD NOT WRITE " << sCopyFile << endl; + iError = (errno == EACCES) ? ERR_WRITE_ACCESS_DENIED + : ERR_CANNOT_OPEN_FOR_WRITING; + return statusClientError; + } + + // delegate the real work (iError gets status) ... + StatusCode iRes = ftpGet(iError, iCopyFile, url, hCopyOffset); + if( ::close(iCopyFile) && iRes == statusSuccess ) + { + iError = ERR_COULD_NOT_WRITE; + iRes = statusClientError; + } + + // handle renaming or deletion of a partial file ... + if(bMarkPartial) + { + if(iRes == statusSuccess) + { // rename ".part" on success + if ( ::rename( sPart.data(), sDest.data() ) ) + { + kdDebug(7102) << "copy: cannot rename " << sPart << " to " << sDest << endl; + iError = ERR_CANNOT_RENAME_PARTIAL; + iRes = statusClientError; + } + } + else if(KDE_stat( sPart.data(), &buff ) == 0) + { // should a very small ".part" be deleted? + int size = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + if (buff.st_size < size) + remove(sPart.data()); + } + } + return iRes; +} diff --git a/tdeioslave/http/CMakeLists.txt b/tdeioslave/http/CMakeLists.txt index a1a6c296a..a8570d60f 100644 --- a/tdeioslave/http/CMakeLists.txt +++ b/tdeioslave/http/CMakeLists.txt @@ -62,7 +62,7 @@ tde_add_tdeinit_executable( ${target} AUTOMOC set( target tdeio_http ) set( ${target}_SRCS - http.cc + http.cpp ) tde_add_kpart( ${target} AUTOMOC diff --git a/tdeioslave/http/Makefile.am b/tdeioslave/http/Makefile.am index c68f90bea..b93bd0d6b 100644 --- a/tdeioslave/http/Makefile.am +++ b/tdeioslave/http/Makefile.am @@ -13,7 +13,7 @@ lib_LTLIBRARIES= tdeinit_LTLIBRARIES = tdeio_http_cache_cleaner.la kde_module_LTLIBRARIES = tdeio_http.la -tdeio_http_la_SOURCES = http.cc +tdeio_http_la_SOURCES = http.cpp tdeio_http_la_METASOURCES = AUTO tdeio_http_la_LIBADD = $(LIB_TDEIO) $(top_builddir)/tdeio/httpfilter/libhttpfilter.la $(LIB_QT) $(LIB_TDECORE) $(LIBZ) $(top_builddir)/dcop/libDCOP.la $(top_builddir)/tdeio/misc/tdentlm/libtdentlm.la tdeio_http_la_LDFLAGS = $(all_libraries) $(GSSAPI_RPATH) -module $(KDE_PLUGIN) $(GSSAPI_LIBS) diff --git a/tdeioslave/http/README.webdav b/tdeioslave/http/README.webdav index d7f6cca26..e2a0f5d35 100644 --- a/tdeioslave/http/README.webdav +++ b/tdeioslave/http/README.webdav @@ -13,7 +13,7 @@ Applications supporting extended webdav features [none currently] Much of the info here is elaborated by rfc #2518; the rest can be understood by reading -davPropStat() in http.cc, specifically the setMetaData() calls. +davPropStat() in http.cpp, specifically the setMetaData() calls. Extended information is transferred via tdeio's metadata system... diff --git a/tdeioslave/http/http.cc b/tdeioslave/http/http.cc deleted file mode 100644 index 8cd0f7a64..000000000 --- a/tdeioslave/http/http.cc +++ /dev/null @@ -1,6131 +0,0 @@ -/* - Copyright (C) 2000-2003 Waldo Bastian - Copyright (C) 2000-2002 George Staikos - Copyright (C) 2000-2002 Dawit Alemayehu - Copyright (C) 2001,2002 Hamish Rodda - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License (LGPL) 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 - -#include -#include -#include -#include -#include -#include -#include -#include // Required for AIX -#include -#include // must be explicitly included for MacOSX - -/* -#include -#include -#include -*/ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "tdeio/ioslave_defaults.h" -#include "tdeio/http_slave_defaults.h" - -#include "httpfilter.h" -#include "http.h" - -#ifdef HAVE_LIBGSSAPI -#ifdef GSSAPI_MIT -#include -#else -#include -#endif /* GSSAPI_MIT */ - -// Catch uncompatible crap (BR86019) -#if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0) -#include -#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name -#endif - -#endif /* HAVE_LIBGSSAPI */ - -#include - -using namespace TDEIO; - -extern "C" { - KDE_EXPORT int kdemain(int argc, char **argv); -} - -int kdemain( int argc, char **argv ) -{ - TDELocale::setMainCatalogue("tdelibs"); - TDEInstance instance( "tdeio_http" ); - ( void ) TDEGlobal::locale(); - - if (argc != 4) - { - fprintf(stderr, "Usage: tdeio_http protocol domain-socket1 domain-socket2\n"); - exit(-1); - } - - HTTPProtocol slave(argv[1], argv[2], argv[3]); - slave.dispatchLoop(); - return 0; -} - -/*********************************** Generic utility functions ********************/ - -static char * trimLead (char *orig_string) -{ - while (*orig_string == ' ') - orig_string++; - return orig_string; -} - -static bool isCrossDomainRequest( const TQString& fqdn, const TQString& originURL ) -{ - if (originURL == "true") // Backwards compatibility - return true; - - KURL url ( originURL ); - - // Document Origin domain - TQString a = url.host(); - - // Current request domain - TQString b = fqdn; - - if (a == b) - return false; - - TQStringList l1 = TQStringList::split('.', a); - TQStringList l2 = TQStringList::split('.', b); - - while(l1.count() > l2.count()) - l1.pop_front(); - - while(l2.count() > l1.count()) - l2.pop_front(); - - while(l2.count() >= 2) - { - if (l1 == l2) - return false; - - l1.pop_front(); - l2.pop_front(); - } - - return true; -} - -/* - Eliminates any custom header that could potentically alter the request -*/ -static TQString sanitizeCustomHTTPHeader(const TQString& _header) -{ - TQString sanitizedHeaders; - TQStringList headers = TQStringList::split(TQRegExp("[\r\n]"), _header); - - for(TQStringList::Iterator it = headers.begin(); it != headers.end(); ++it) - { - TQString header = (*it).lower(); - // Do not allow Request line to be specified and ignore - // the other HTTP headers. - if (header.find(':') == -1 || - header.startsWith("host") || - header.startsWith("via")) - continue; - - sanitizedHeaders += (*it); - sanitizedHeaders += "\r\n"; - } - - return sanitizedHeaders.stripWhiteSpace(); -} - -static TQString htmlEscape(const TQString &plain) -{ - TQString rich; - rich.reserve(uint(plain.length() * 1.1)); - for (uint i = 0; i < plain.length(); ++i) { - if (plain.at(i) == '<') { - rich += "<"; - } else if (plain.at(i) == '>') { - rich += ">"; - } else if (plain.at(i) == '&') { - rich += "&"; - } else if (plain.at(i) == '"') { - rich += """; - } else { - rich += plain.at(i); - } - } - rich.squeeze(); - return rich; -} - - -#define NO_SIZE ((TDEIO::filesize_t) -1) - -#ifdef HAVE_STRTOLL -#define STRTOLL strtoll -#else -#define STRTOLL strtol -#endif - - -/************************************** HTTPProtocol **********************************************/ - -HTTPProtocol::HTTPProtocol( const TQCString &protocol, const TQCString &pool, - const TQCString &app ) - :TCPSlaveBase( 0, protocol , pool, app, - (protocol == "https" || protocol == "webdavs") ) -{ - m_requestQueue.setAutoDelete(true); - - m_bBusy = false; - m_bFirstRequest = false; - m_bProxyAuthValid = false; - - m_iSize = NO_SIZE; - m_lineBufUnget = 0; - - m_protocol = protocol; - - m_maxCacheAge = DEFAULT_MAX_CACHE_AGE; - m_maxCacheSize = DEFAULT_MAX_CACHE_SIZE / 2; - m_remoteConnTimeout = DEFAULT_CONNECT_TIMEOUT; - m_remoteRespTimeout = DEFAULT_RESPONSE_TIMEOUT; - m_proxyConnTimeout = DEFAULT_PROXY_CONNECT_TIMEOUT; - - m_pid = getpid(); - - setMultipleAuthCaching( true ); - reparseConfiguration(); -} - -HTTPProtocol::~HTTPProtocol() -{ - httpClose(false); -} - -void HTTPProtocol::reparseConfiguration() -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::reparseConfiguration" << endl; - - m_strProxyRealm = TQString::null; - m_strProxyAuthorization = TQString::null; - ProxyAuthentication = AUTH_None; - m_bUseProxy = false; - - if (m_protocol == "https" || m_protocol == "webdavs") - m_iDefaultPort = DEFAULT_HTTPS_PORT; - else if (m_protocol == "ftp") - m_iDefaultPort = DEFAULT_FTP_PORT; - else - m_iDefaultPort = DEFAULT_HTTP_PORT; -} - -void HTTPProtocol::resetConnectionSettings() -{ - m_bEOF = false; - m_bError = false; - m_lineCount = 0; - m_iWWWAuthCount = 0; - m_lineCountUnget = 0; - m_iProxyAuthCount = 0; - -} - -void HTTPProtocol::resetResponseSettings() -{ - m_bRedirect = false; - m_redirectLocation = KURL(); - m_bChunked = false; - m_iSize = NO_SIZE; - - m_responseHeader.clear(); - m_qContentEncodings.clear(); - m_qTransferEncodings.clear(); - m_sContentMD5 = TQString::null; - m_strMimeType = TQString::null; - - setMetaData("request-id", m_request.id); -} - -void HTTPProtocol::resetSessionSettings() -{ - // Do not reset the URL on redirection if the proxy - // URL, username or password has not changed! - KURL proxy ( config()->readEntry("UseProxy") ); - - if ( m_strProxyRealm.isEmpty() || !proxy.isValid() || - m_proxyURL.host() != proxy.host() || - (!proxy.user().isNull() && proxy.user() != m_proxyURL.user()) || - (!proxy.pass().isNull() && proxy.pass() != m_proxyURL.pass()) ) - { - m_bProxyAuthValid = false; - m_proxyURL = proxy; - m_bUseProxy = m_proxyURL.isValid(); - - kdDebug(7113) << "(" << m_pid << ") Using proxy: " << m_bUseProxy << - " URL: " << m_proxyURL.prettyURL() << - " Realm: " << m_strProxyRealm << endl; - } - - m_bPersistentProxyConnection = config()->readBoolEntry("PersistentProxyConnection", false); - kdDebug(7113) << "(" << m_pid << ") Enable Persistent Proxy Connection: " - << m_bPersistentProxyConnection << endl; - - m_request.bUseCookiejar = config()->readBoolEntry("Cookies"); - m_request.bUseCache = config()->readBoolEntry("UseCache", true); - m_request.bErrorPage = config()->readBoolEntry("errorPage", true); - m_request.bNoAuth = config()->readBoolEntry("no-auth"); - m_strCacheDir = config()->readPathEntry("CacheDir"); - m_maxCacheAge = config()->readNumEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE); - m_request.window = config()->readEntry("window-id"); - - kdDebug(7113) << "(" << m_pid << ") Window Id = " << m_request.window << endl; - kdDebug(7113) << "(" << m_pid << ") ssl_was_in_use = " - << metaData ("ssl_was_in_use") << endl; - - m_request.referrer = TQString::null; - if ( config()->readBoolEntry("SendReferrer", true) && - (m_protocol == "https" || m_protocol == "webdavs" || - metaData ("ssl_was_in_use") != "TRUE" ) ) - { - KURL referrerURL ( metaData("referrer") ); - if (referrerURL.isValid()) - { - // Sanitize - TQString protocol = referrerURL.protocol(); - if (protocol.startsWith("webdav")) - { - protocol.replace(0, 6, "http"); - referrerURL.setProtocol(protocol); - } - - if (protocol.startsWith("http")) - { - referrerURL.setRef(TQString::null); - referrerURL.setUser(TQString::null); - referrerURL.setPass(TQString::null); - m_request.referrer = referrerURL.url(); - } - } - } - - if ( config()->readBoolEntry("SendLanguageSettings", true) ) - { - m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" ); - - if ( !m_request.charsets.isEmpty() ) - m_request.charsets += DEFAULT_PARTIAL_CHARSET_HEADER; - - m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER ); - } - else - { - m_request.charsets = TQString::null; - m_request.languages = TQString::null; - } - - // Adjust the offset value based on the "resume" meta-data. - TQString resumeOffset = metaData("resume"); - if ( !resumeOffset.isEmpty() ) - m_request.offset = resumeOffset.toInt(); // TODO: Convert to 64 bit - else - m_request.offset = 0; - - m_request.disablePassDlg = config()->readBoolEntry("DisablePassDlg", false); - m_request.allowCompressedPage = config()->readBoolEntry("AllowCompressedPage", true); - m_request.id = metaData("request-id"); - - // Store user agent for this host. - if ( config()->readBoolEntry("SendUserAgent", true) ) - m_request.userAgent = metaData("UserAgent"); - else - m_request.userAgent = TQString::null; - - // Deal with cache cleaning. - // TODO: Find a smarter way to deal with cleaning the - // cache ? - if ( m_request.bUseCache ) - cleanCache(); - - // Deal with HTTP tunneling - if ( m_bIsSSL && m_bUseProxy && m_proxyURL.protocol() != "https" && - m_proxyURL.protocol() != "webdavs") - { - m_bNeedTunnel = true; - setRealHost( m_request.hostname ); - kdDebug(7113) << "(" << m_pid << ") SSL tunnel: Setting real hostname to: " - << m_request.hostname << endl; - } - else - { - m_bNeedTunnel = false; - setRealHost( TQString::null); - } - - m_responseCode = 0; - m_prevResponseCode = 0; - - m_strRealm = TQString::null; - m_strAuthorization = TQString::null; - Authentication = AUTH_None; - - // Obtain the proxy and remote server timeout values - m_proxyConnTimeout = proxyConnectTimeout(); - m_remoteConnTimeout = connectTimeout(); - m_remoteRespTimeout = responseTimeout(); - - // Set the SSL meta-data here... - setSSLMetaData(); - - // Bounce back the actual referrer sent - setMetaData("referrer", m_request.referrer); - - // Follow HTTP/1.1 spec and enable keep-alive by default - // unless the remote side tells us otherwise or we determine - // the persistent link has been terminated by the remote end. - m_bKeepAlive = true; - m_keepAliveTimeout = 0; - m_bUnauthorized = false; - - // A single request can require multiple exchanges with the remote - // server due to authentication challenges or SSL tunneling. - // m_bFirstRequest is a flag that indicates whether we are - // still processing the first request. This is important because we - // should not force a close of a keep-alive connection in the middle - // of the first request. - // m_bFirstRequest is set to "true" whenever a new connection is - // made in httpOpenConnection() - m_bFirstRequest = false; -} - -void HTTPProtocol::setHost( const TQString& host, int port, - const TQString& user, const TQString& pass ) -{ - // Reset the webdav-capable flags for this host - if ( m_request.hostname != host ) - m_davHostOk = m_davHostUnsupported = false; - - // is it an IPv6 address? - if (host.find(':') == -1) - { - m_request.hostname = host; - m_request.encoded_hostname = KIDNA::toAscii(host); - } - else - { - m_request.hostname = host; - int pos = host.find('%'); - if (pos == -1) - m_request.encoded_hostname = '[' + host + ']'; - else - // don't send the scope-id in IPv6 addresses to the server - m_request.encoded_hostname = '[' + host.left(pos) + ']'; - } - m_request.port = (port == 0) ? m_iDefaultPort : port; - m_request.user = user; - m_request.passwd = pass; - - m_bIsTunneled = false; - - kdDebug(7113) << "(" << m_pid << ") Hostname is now: " << m_request.hostname << - " (" << m_request.encoded_hostname << ")" <\r\n"; - request.append( "\r\n" ); - request.append( query.utf8() ); - request.append( "\r\n" ); - - davSetRequest( request ); - } else { - // We are only after certain features... - TQCString request; - request = "" - ""; - - // insert additional XML request from the davRequestResponse metadata - if ( hasMetaData( "davRequestResponse" ) ) - request += metaData( "davRequestResponse" ).utf8(); - else { - // No special request, ask for default properties - request += "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - ""; - } - request += ""; - - davSetRequest( request ); - } - - // WebDAV Stat or List... - m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH; - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - m_request.davData.depth = stat ? 0 : 1; - if (!stat) - m_request.url.adjustPath(+1); - - retrieveContent( true ); - - // Has a redirection already been called? If so, we're done. - if (m_bRedirect) { - finished(); - return; - } - - TQDomDocument multiResponse; - multiResponse.setContent( m_bufWebDavData, true ); - - bool hasResponse = false; - - for ( TQDomNode n = multiResponse.documentElement().firstChild(); - !n.isNull(); n = n.nextSibling()) - { - TQDomElement thisResponse = n.toElement(); - if (thisResponse.isNull()) - continue; - - hasResponse = true; - - TQDomElement href = thisResponse.namedItem( "href" ).toElement(); - if ( !href.isNull() ) - { - entry.clear(); - - TQString urlStr = href.text(); -#if 0 - int encoding = remoteEncoding()->encodingMib(); - if ((encoding == 106) && (!KStringHandler::isUtf8(KURL::decode_string(urlStr, 4).latin1()))) - encoding = 4; // Use latin1 if the file is not actually utf-8 -#else - TQUrl::decode(urlStr); - int encoding = 106; -#endif - - KURL thisURL ( urlStr, encoding ); - - atom.m_uds = TDEIO::UDS_NAME; - - if ( thisURL.isValid() ) { - // don't list the base dir of a listDir() - if ( !stat && thisURL.path(+1).length() == url.path(+1).length() ) - continue; - - atom.m_str = thisURL.fileName(); - } else { - // This is a relative URL. - atom.m_str = href.text(); - } - - entry.append( atom ); - - TQDomNodeList propstats = thisResponse.elementsByTagName( "propstat" ); - - davParsePropstats( propstats, entry ); - - if ( stat ) - { - // return an item - statEntry( entry ); - finished(); - return; - } - else - { - listEntry( entry, false ); - } - } - else - { - kdDebug(7113) << "Error: no URL contained in response to PROPFIND on " - << url.prettyURL() << endl; - } - } - - if ( stat || !hasResponse ) - { - error( ERR_DOES_NOT_EXIST, url.prettyURL() ); - } - else - { - listEntry( entry, true ); - finished(); - } -} - -void HTTPProtocol::davGeneric( const KURL& url, TDEIO::HTTP_METHOD method ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davGeneric " << url.prettyURL() - << endl; - - if ( !checkRequestURL( url ) ) - return; - - // check to make sure this host supports WebDAV - if ( !davHostOk() ) - return; - - // WebDAV method - m_request.method = method; - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveContent( false ); -} - -int HTTPProtocol::codeFromResponse( const TQString& response ) -{ - int firstSpace = response.find( ' ' ); - int secondSpace = response.find( ' ', firstSpace + 1 ); - return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt(); -} - -void HTTPProtocol::davParsePropstats( const TQDomNodeList& propstats, UDSEntry& entry ) -{ - TQString mimeType; - UDSAtom atom; - bool foundExecutable = false; - bool isDirectory = false; - uint lockCount = 0; - uint supportedLockCount = 0; - - for ( uint i = 0; i < propstats.count(); i++) - { - TQDomElement propstat = propstats.item(i).toElement(); - - TQDomElement status = propstat.namedItem( "status" ).toElement(); - if ( status.isNull() ) - { - // error, no status code in this propstat - kdDebug(7113) << "Error, no status code in this propstat" << endl; - return; - } - - int code = codeFromResponse( status.text() ); - - if ( code != 200 ) - { - kdDebug(7113) << "Warning: status code " << code << " (this may mean that some properties are unavailable" << endl; - continue; - } - - TQDomElement prop = propstat.namedItem( "prop" ).toElement(); - if ( prop.isNull() ) - { - kdDebug(7113) << "Error: no prop segment in this propstat." << endl; - return; - } - - if ( hasMetaData( "davRequestResponse" ) ) - { - atom.m_uds = TDEIO::UDS_XML_PROPERTIES; - TQDomDocument doc; - doc.appendChild(prop); - atom.m_str = doc.toString(); - entry.append( atom ); - } - - for ( TQDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() ) - { - TQDomElement property = n.toElement(); - if (property.isNull()) - continue; - - if ( property.namespaceURI() != "DAV:" ) - { - // break out - we're only interested in properties from the DAV namespace - continue; - } - - if ( property.tagName() == "creationdate" ) - { - // Resource creation date. Should be is ISO 8601 format. - atom.m_uds = TDEIO::UDS_CREATION_TIME; - atom.m_long = parseDateTime( property.text(), property.attribute("dt") ); - entry.append( atom ); - } - else if ( property.tagName() == "getcontentlength" ) - { - // Content length (file size) - atom.m_uds = TDEIO::UDS_SIZE; - atom.m_long = property.text().toULong(); - entry.append( atom ); - } - else if ( property.tagName() == "displayname" ) - { - // Name suitable for presentation to the user - setMetaData( "davDisplayName", property.text() ); - } - else if ( property.tagName() == "source" ) - { - // Source template location - TQDomElement source = property.namedItem( "link" ).toElement() - .namedItem( "dst" ).toElement(); - if ( !source.isNull() ) - setMetaData( "davSource", source.text() ); - } - else if ( property.tagName() == "getcontentlanguage" ) - { - // equiv. to Content-Language header on a GET - setMetaData( "davContentLanguage", property.text() ); - } - else if ( property.tagName() == "getcontenttype" ) - { - // Content type (mime type) - // This may require adjustments for other server-side webdav implementations - // (tested with Apache + mod_dav 1.0.3) - if ( property.text() == "httpd/unix-directory" ) - { - isDirectory = true; - } - else - { - mimeType = property.text(); - } - } - else if ( property.tagName() == "executable" ) - { - // File executable status - if ( property.text() == "T" ) - foundExecutable = true; - - } - else if ( property.tagName() == "getlastmodified" ) - { - // Last modification date - atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; - atom.m_long = parseDateTime( property.text(), property.attribute("dt") ); - entry.append( atom ); - - } - else if ( property.tagName() == "getetag" ) - { - // Entity tag - setMetaData( "davEntityTag", property.text() ); - } - else if ( property.tagName() == "supportedlock" ) - { - // Supported locking specifications - for ( TQDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() ) - { - TQDomElement lockEntry = n2.toElement(); - if ( lockEntry.tagName() == "lockentry" ) - { - TQDomElement lockScope = lockEntry.namedItem( "lockscope" ).toElement(); - TQDomElement lockType = lockEntry.namedItem( "locktype" ).toElement(); - if ( !lockScope.isNull() && !lockType.isNull() ) - { - // Lock type was properly specified - supportedLockCount++; - TQString scope = lockScope.firstChild().toElement().tagName(); - TQString type = lockType.firstChild().toElement().tagName(); - - setMetaData( TQString("davSupportedLockScope%1").arg(supportedLockCount), scope ); - setMetaData( TQString("davSupportedLockType%1").arg(supportedLockCount), type ); - } - } - } - } - else if ( property.tagName() == "lockdiscovery" ) - { - // Lists the available locks - davParseActiveLocks( property.elementsByTagName( "activelock" ), lockCount ); - } - else if ( property.tagName() == "resourcetype" ) - { - // Resource type. "Specifies the nature of the resource." - if ( !property.namedItem( "collection" ).toElement().isNull() ) - { - // This is a collection (directory) - isDirectory = true; - } - } - else - { - kdDebug(7113) << "Found unknown webdav property: " << property.tagName() << endl; - } - } - } - - setMetaData( "davLockCount", TQString("%1").arg(lockCount) ); - setMetaData( "davSupportedLockCount", TQString("%1").arg(supportedLockCount) ); - - atom.m_uds = TDEIO::UDS_FILE_TYPE; - atom.m_long = isDirectory ? S_IFDIR : S_IFREG; - entry.append( atom ); - - if ( foundExecutable || isDirectory ) - { - // File was executable, or is a directory. - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = 0700; - entry.append(atom); - } - else - { - atom.m_uds = TDEIO::UDS_ACCESS; - atom.m_long = 0600; - entry.append(atom); - } - - if ( !isDirectory && !mimeType.isEmpty() ) - { - atom.m_uds = TDEIO::UDS_MIME_TYPE; - atom.m_str = mimeType; - entry.append( atom ); - } -} - -void HTTPProtocol::davParseActiveLocks( const TQDomNodeList& activeLocks, - uint& lockCount ) -{ - for ( uint i = 0; i < activeLocks.count(); i++ ) - { - TQDomElement activeLock = activeLocks.item(i).toElement(); - - lockCount++; - // required - TQDomElement lockScope = activeLock.namedItem( "lockscope" ).toElement(); - TQDomElement lockType = activeLock.namedItem( "locktype" ).toElement(); - TQDomElement lockDepth = activeLock.namedItem( "depth" ).toElement(); - // optional - TQDomElement lockOwner = activeLock.namedItem( "owner" ).toElement(); - TQDomElement lockTimeout = activeLock.namedItem( "timeout" ).toElement(); - TQDomElement lockToken = activeLock.namedItem( "locktoken" ).toElement(); - - if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() ) - { - // lock was properly specified - lockCount++; - TQString scope = lockScope.firstChild().toElement().tagName(); - TQString type = lockType.firstChild().toElement().tagName(); - TQString depth = lockDepth.text(); - - setMetaData( TQString("davLockScope%1").arg( lockCount ), scope ); - setMetaData( TQString("davLockType%1").arg( lockCount ), type ); - setMetaData( TQString("davLockDepth%1").arg( lockCount ), depth ); - - if ( !lockOwner.isNull() ) - setMetaData( TQString("davLockOwner%1").arg( lockCount ), lockOwner.text() ); - - if ( !lockTimeout.isNull() ) - setMetaData( TQString("davLockTimeout%1").arg( lockCount ), lockTimeout.text() ); - - if ( !lockToken.isNull() ) - { - TQDomElement tokenVal = lockScope.namedItem( "href" ).toElement(); - if ( !tokenVal.isNull() ) - setMetaData( TQString("davLockToken%1").arg( lockCount ), tokenVal.text() ); - } - } - } -} - -long HTTPProtocol::parseDateTime( const TQString& input, const TQString& type ) -{ - if ( type == "dateTime.tz" ) - { - return KRFCDate::parseDateISO8601( input ); - } - else if ( type == "dateTime.rfc1123" ) - { - return KRFCDate::parseDate( input ); - } - - // format not advertised... try to parse anyway - time_t time = KRFCDate::parseDate( input ); - if ( time != 0 ) - return time; - - return KRFCDate::parseDateISO8601( input ); -} - -TQString HTTPProtocol::davProcessLocks() -{ - if ( hasMetaData( "davLockCount" ) ) - { - TQString response("If:"); - int numLocks; - numLocks = metaData( "davLockCount" ).toInt(); - bool bracketsOpen = false; - for ( int i = 0; i < numLocks; i++ ) - { - if ( hasMetaData( TQString("davLockToken%1").arg(i) ) ) - { - if ( hasMetaData( TQString("davLockURL%1").arg(i) ) ) - { - if ( bracketsOpen ) - { - response += ")"; - bracketsOpen = false; - } - response += " <" + metaData( TQString("davLockURL%1").arg(i) ) + ">"; - } - - if ( !bracketsOpen ) - { - response += " ("; - bracketsOpen = true; - } - else - { - response += " "; - } - - if ( hasMetaData( TQString("davLockNot%1").arg(i) ) ) - response += "Not "; - - response += "<" + metaData( TQString("davLockToken%1").arg(i) ) + ">"; - } - } - - if ( bracketsOpen ) - response += ")"; - - response += "\r\n"; - return response; - } - - return TQString::null; -} - -bool HTTPProtocol::davHostOk() -{ - // FIXME needs to be reworked. Switched off for now. - return true; - - // cached? - if ( m_davHostOk ) - { - kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " true" << endl; - return true; - } - else if ( m_davHostUnsupported ) - { - kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " false" << endl; - davError( -2 ); - return false; - } - - m_request.method = HTTP_OPTIONS; - - // query the server's capabilities generally, not for a specific URL - m_request.path = "*"; - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - // clear davVersions variable, which holds the response to the DAV: header - m_davCapabilities.clear(); - - retrieveHeader(false); - - if (m_davCapabilities.count()) - { - for (uint i = 0; i < m_davCapabilities.count(); i++) - { - bool ok; - uint verNo = m_davCapabilities[i].toUInt(&ok); - if (ok && verNo > 0 && verNo < 3) - { - m_davHostOk = true; - kdDebug(7113) << "Server supports DAV version " << verNo << "." << endl; - } - } - - if ( m_davHostOk ) - return true; - } - - m_davHostUnsupported = true; - davError( -2 ); - return false; -} - -// This function is for closing retrieveHeader( false ); requests -// Required because there may or may not be further info expected -void HTTPProtocol::davFinished() -{ - // TODO: Check with the DAV extension developers - httpClose(m_bKeepAlive); - finished(); -} - -void HTTPProtocol::mkdir( const KURL& url, int ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mkdir " << url.prettyURL() - << endl; - - if ( !checkRequestURL( url ) ) - return; - - m_request.method = DAV_MKCOL; - m_request.path = url.path(); - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveHeader( false ); - - if ( m_responseCode == 201 ) - davFinished(); - else - davError(); -} - -void HTTPProtocol::get( const KURL& url ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::get " << url.prettyURL() - << endl; - - if ( !checkRequestURL( url ) ) - return; - - m_request.method = HTTP_GET; - m_request.path = url.path(); - m_request.query = url.query(); - - TQString tmp = metaData("cache"); - if (!tmp.isEmpty()) - m_request.cache = parseCacheControl(tmp); - else - m_request.cache = DEFAULT_CACHE_CONTROL; - - m_request.passwd = url.pass(); - m_request.user = url.user(); - m_request.doProxy = m_bUseProxy; - - retrieveContent(); -} - -void HTTPProtocol::put( const KURL &url, int, bool overwrite, bool) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put " << url.prettyURL() - << endl; - - if ( !checkRequestURL( url ) ) - return; - - // Webdav hosts are capable of observing overwrite == false - if (!overwrite && m_protocol.left(6) == "webdav") { - // check to make sure this host supports WebDAV - if ( !davHostOk() ) - return; - - TQCString request; - request = "" - "" - "" - "" - "" - "" - ""; - - davSetRequest( request ); - - // WebDAV Stat or List... - m_request.method = DAV_PROPFIND; - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - m_request.davData.depth = 0; - - retrieveContent(true); - - if (m_responseCode == 207) { - error(ERR_FILE_ALREADY_EXIST, TQString::null); - return; - } - - m_bError = false; - } - - m_request.method = HTTP_PUT; - m_request.path = url.path(); - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveHeader( false ); - - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put error = " << m_bError << endl; - if (m_bError) - return; - - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put responseCode = " << m_responseCode << endl; - - httpClose(false); // Always close connection. - - if ( (m_responseCode >= 200) && (m_responseCode < 300) ) - finished(); - else - httpError(); -} - -void HTTPProtocol::copy( const KURL& src, const KURL& dest, int, bool overwrite ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::copy " << src.prettyURL() - << " -> " << dest.prettyURL() << endl; - - if ( !checkRequestURL( dest ) || !checkRequestURL( src ) ) - return; - - // destination has to be "http(s)://..." - KURL newDest = dest; - if (newDest.protocol() == "webdavs") - newDest.setProtocol("https"); - else - newDest.setProtocol("http"); - - m_request.method = DAV_COPY; - m_request.path = src.path(); - m_request.davData.desturl = newDest.url(); - m_request.davData.overwrite = overwrite; - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveHeader( false ); - - // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion - if ( m_responseCode == 201 || m_responseCode == 204 ) - davFinished(); - else - davError(); -} - -void HTTPProtocol::rename( const KURL& src, const KURL& dest, bool overwrite ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::rename " << src.prettyURL() - << " -> " << dest.prettyURL() << endl; - - if ( !checkRequestURL( dest ) || !checkRequestURL( src ) ) - return; - - // destination has to be "http://..." - KURL newDest = dest; - if (newDest.protocol() == "webdavs") - newDest.setProtocol("https"); - else - newDest.setProtocol("http"); - - m_request.method = DAV_MOVE; - m_request.path = src.path(); - m_request.davData.desturl = newDest.url(); - m_request.davData.overwrite = overwrite; - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveHeader( false ); - - if ( m_responseCode == 301 ) - { - // Work around strict Apache-2 WebDAV implementation which refuses to cooperate - // with webdav://host/directory, instead requiring webdav://host/directory/ - // (strangely enough it accepts Destination: without a trailing slash) - - if (m_redirectLocation.protocol() == "https") - m_redirectLocation.setProtocol("webdavs"); - else - m_redirectLocation.setProtocol("webdav"); - - if ( !checkRequestURL( m_redirectLocation ) ) - return; - - m_request.method = DAV_MOVE; - m_request.path = m_redirectLocation.path(); - m_request.davData.desturl = newDest.url(); - m_request.davData.overwrite = overwrite; - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveHeader( false ); - } - - if ( m_responseCode == 201 ) - davFinished(); - else - davError(); -} - -void HTTPProtocol::del( const KURL& url, bool ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::del " << url.prettyURL() - << endl; - - if ( !checkRequestURL( url ) ) - return; - - m_request.method = HTTP_DELETE; - m_request.path = url.path(); - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveHeader( false ); - - // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content - // on successful completion - if ( m_responseCode == 200 || m_responseCode == 204 ) - davFinished(); - else - davError(); -} - -void HTTPProtocol::post( const KURL& url ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::post " - << url.prettyURL() << endl; - - if ( !checkRequestURL( url ) ) - return; - - m_request.method = HTTP_POST; - m_request.path = url.path(); - m_request.query = url.query(); - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveContent(); -} - -void HTTPProtocol::davLock( const KURL& url, const TQString& scope, - const TQString& type, const TQString& owner ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davLock " - << url.prettyURL() << endl; - - if ( !checkRequestURL( url ) ) - return; - - m_request.method = DAV_LOCK; - m_request.path = url.path(); - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - /* Create appropriate lock XML request. */ - TQDomDocument lockReq; - - TQDomElement lockInfo = lockReq.createElementNS( "DAV:", "lockinfo" ); - lockReq.appendChild( lockInfo ); - - TQDomElement lockScope = lockReq.createElement( "lockscope" ); - lockInfo.appendChild( lockScope ); - - lockScope.appendChild( lockReq.createElement( scope ) ); - - TQDomElement lockType = lockReq.createElement( "locktype" ); - lockInfo.appendChild( lockType ); - - lockType.appendChild( lockReq.createElement( type ) ); - - if ( !owner.isNull() ) { - TQDomElement ownerElement = lockReq.createElement( "owner" ); - lockReq.appendChild( ownerElement ); - - TQDomElement ownerHref = lockReq.createElement( "href" ); - ownerElement.appendChild( ownerHref ); - - ownerHref.appendChild( lockReq.createTextNode( owner ) ); - } - - // insert the document into the POST buffer - m_bufPOST = lockReq.toCString(); - - retrieveContent( true ); - - if ( m_responseCode == 200 ) { - // success - TQDomDocument multiResponse; - multiResponse.setContent( m_bufWebDavData, true ); - - TQDomElement prop = multiResponse.documentElement().namedItem( "prop" ).toElement(); - - TQDomElement lockdiscovery = prop.namedItem( "lockdiscovery" ).toElement(); - - uint lockCount = 0; - davParseActiveLocks( lockdiscovery.elementsByTagName( "activelock" ), lockCount ); - - setMetaData( "davLockCount", TQString("%1").arg( lockCount ) ); - - finished(); - - } else - davError(); -} - -void HTTPProtocol::davUnlock( const KURL& url ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davUnlock " - << url.prettyURL() << endl; - - if ( !checkRequestURL( url ) ) - return; - - m_request.method = DAV_UNLOCK; - m_request.path = url.path(); - m_request.query = TQString::null; - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - retrieveContent( true ); - - if ( m_responseCode == 200 ) - finished(); - else - davError(); -} - -TQString HTTPProtocol::davError( int code /* = -1 */, TQString url ) -{ - bool callError = false; - if ( code == -1 ) { - code = m_responseCode; - callError = true; - } - if ( code == -2 ) { - callError = true; - } - - // Huh? This looks like inverted logic to me (it doesn't make sense to me as - // written), but I'm only fixing the CVE now. -- Kevin Kofler - if ( !url.isNull() ) - url = m_request.url.prettyURL(); - - TQString action, errorString; - TDEIO::Error kError; - - // for 412 Precondition Failed - TQString ow = i18n( "Otherwise, the request would have succeeded." ); - - switch ( m_request.method ) { - case DAV_PROPFIND: - action = i18n( "retrieve property values" ); - break; - case DAV_PROPPATCH: - action = i18n( "set property values" ); - break; - case DAV_MKCOL: - action = i18n( "create the requested folder" ); - break; - case DAV_COPY: - action = i18n( "copy the specified file or folder" ); - break; - case DAV_MOVE: - action = i18n( "move the specified file or folder" ); - break; - case DAV_SEARCH: - action = i18n( "search in the specified folder" ); - break; - case DAV_LOCK: - action = i18n( "lock the specified file or folder" ); - break; - case DAV_UNLOCK: - action = i18n( "unlock the specified file or folder" ); - break; - case HTTP_DELETE: - action = i18n( "delete the specified file or folder" ); - break; - case HTTP_OPTIONS: - action = i18n( "query the server's capabilities" ); - break; - case HTTP_GET: - action = i18n( "retrieve the contents of the specified file or folder" ); - break; - case HTTP_PUT: - case HTTP_POST: - case HTTP_HEAD: - default: - // this should not happen, this function is for webdav errors only - Q_ASSERT(0); - } - - // default error message if the following code fails - kError = ERR_INTERNAL; - errorString = i18n("An unexpected error (%1) occurred while attempting to %2.") - .arg( code ).arg( action ); - - switch ( code ) - { - case -2: - // internal error: OPTIONS request did not specify DAV compliance - kError = ERR_UNSUPPORTED_PROTOCOL; - errorString = i18n("The server does not support the WebDAV protocol."); - break; - case 207: - // 207 Multi-status - { - // our error info is in the returned XML document. - // retrieve the XML document - - // there was an error retrieving the XML document. - // ironic, eh? - if ( !readBody( true ) && m_bError ) - return TQString::null; - - TQStringList errors; - TQDomDocument multiResponse; - - multiResponse.setContent( m_bufWebDavData, true ); - - TQDomElement multistatus = multiResponse.documentElement().namedItem( "multistatus" ).toElement(); - - TQDomNodeList responses = multistatus.elementsByTagName( "response" ); - - for (uint i = 0; i < responses.count(); i++) - { - int errCode; - TQString errUrl; - - TQDomElement response = responses.item(i).toElement(); - TQDomElement code = response.namedItem( "status" ).toElement(); - - if ( !code.isNull() ) - { - errCode = codeFromResponse( code.text() ); - TQDomElement href = response.namedItem( "href" ).toElement(); - if ( !href.isNull() ) - errUrl = href.text(); - errors << davError( errCode, errUrl ); - } - } - - //kError = ERR_SLAVE_DEFINED; - errorString = i18n("An error occurred while attempting to %1, %2. A " - "summary of the reasons is below.
    ").arg( action ).arg( url ); - - for ( TQStringList::Iterator it = errors.begin(); it != errors.end(); ++it ) - errorString += "
  • " + *it + "
  • "; - - errorString += "
"; - } - case 403: - case 500: // hack: Apache mod_dav returns this instead of 403 (!) - // 403 Forbidden - kError = ERR_ACCESS_DENIED; - errorString = i18n("Access was denied while attempting to %1.").arg( action ); - break; - case 405: - // 405 Method Not Allowed - if ( m_request.method == DAV_MKCOL ) - { - kError = ERR_DIR_ALREADY_EXIST; - errorString = i18n("The specified folder already exists."); - } - break; - case 409: - // 409 Conflict - kError = ERR_ACCESS_DENIED; - errorString = i18n("A resource cannot be created at the destination " - "until one or more intermediate collections (folders) " - "have been created."); - break; - case 412: - // 412 Precondition failed - if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) - { - kError = ERR_ACCESS_DENIED; - errorString = i18n("The server was unable to maintain the liveness of " - "the properties listed in the propertybehavior XML " - "element or you attempted to overwrite a file while " - "requesting that files are not overwritten. %1") - .arg( ow ); - - } - else if ( m_request.method == DAV_LOCK ) - { - kError = ERR_ACCESS_DENIED; - errorString = i18n("The requested lock could not be granted. %1").arg( ow ); - } - break; - case 415: - // 415 Unsupported Media Type - kError = ERR_ACCESS_DENIED; - errorString = i18n("The server does not support the request type of the body."); - break; - case 423: - // 423 Locked - kError = ERR_ACCESS_DENIED; - errorString = i18n("Unable to %1 because the resource is locked.").arg( action ); - break; - case 425: - // 424 Failed Dependency - errorString = i18n("This action was prevented by another error."); - break; - case 502: - // 502 Bad Gateway - if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) - { - kError = ERR_WRITE_ACCESS_DENIED; - errorString = i18n("Unable to %1 because the destination server refuses " - "to accept the file or folder.").arg( action ); - } - break; - case 507: - // 507 Insufficient Storage - kError = ERR_DISK_FULL; - errorString = i18n("The destination resource does not have sufficient space " - "to record the state of the resource after the execution " - "of this method."); - break; - } - - // if ( kError != ERR_SLAVE_DEFINED ) - //errorString += " (" + url + ")"; - - if ( callError ) - error( ERR_SLAVE_DEFINED, errorString ); - - return errorString; -} - -void HTTPProtocol::httpError() -{ - TQString action, errorString; - TDEIO::Error kError; - - switch ( m_request.method ) { - case HTTP_PUT: - action = i18n( "upload %1" ).arg(m_request.url.prettyURL()); - break; - default: - // this should not happen, this function is for http errors only - Q_ASSERT(0); - } - - // default error message if the following code fails - kError = ERR_INTERNAL; - errorString = i18n("An unexpected error (%1) occurred while attempting to %2.") - .arg( m_responseCode ).arg( action ); - - switch ( m_responseCode ) - { - case 403: - case 405: - case 500: // hack: Apache mod_dav returns this instead of 403 (!) - // 403 Forbidden - // 405 Method Not Allowed - kError = ERR_ACCESS_DENIED; - errorString = i18n("Access was denied while attempting to %1.").arg( action ); - break; - case 409: - // 409 Conflict - kError = ERR_ACCESS_DENIED; - errorString = i18n("A resource cannot be created at the destination " - "until one or more intermediate collections (folders) " - "have been created."); - break; - case 423: - // 423 Locked - kError = ERR_ACCESS_DENIED; - errorString = i18n("Unable to %1 because the resource is locked.").arg( action ); - break; - case 502: - // 502 Bad Gateway - kError = ERR_WRITE_ACCESS_DENIED; - errorString = i18n("Unable to %1 because the destination server refuses " - "to accept the file or folder.").arg( action ); - break; - case 507: - // 507 Insufficient Storage - kError = ERR_DISK_FULL; - errorString = i18n("The destination resource does not have sufficient space " - "to record the state of the resource after the execution " - "of this method."); - break; - } - - // if ( kError != ERR_SLAVE_DEFINED ) - //errorString += " (" + url + ")"; - - error( ERR_SLAVE_DEFINED, errorString ); -} - -bool HTTPProtocol::isOffline(const KURL &url) -{ - const int NetWorkStatusUnknown = 1; - const int NetWorkStatusOnline = 8; - TQCString replyType; - TQByteArray params; - TQByteArray reply; - - TQDataStream stream(params, IO_WriteOnly); - - if ( url.host() == TQString::fromLatin1("localhost") || url.host() == TQString::fromLatin1("127.0.0.1") || url.host() == TQString::fromLatin1("::") ) { - return false; - } - if ( dcopClient()->call( "kded", "networkstatus", "status()", - params, replyType, reply ) && (replyType == "int") ) - { - int result; - TQDataStream stream2( reply, IO_ReadOnly ); - stream2 >> result; - kdDebug(7113) << "(" << m_pid << ") networkstatus status = " << result << endl; - return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline); - } - kdDebug(7113) << "(" << m_pid << ") networkstatus " << endl; - return false; // On error, assume we are online -} - -void HTTPProtocol::multiGet(const TQByteArray &data) -{ - TQDataStream stream(data, IO_ReadOnly); - TQ_UINT32 n; - stream >> n; - - kdDebug(7113) << "(" << m_pid << ") HTTPProtcool::multiGet n = " << n << endl; - - HTTPRequest saveRequest; - if (m_bBusy) - saveRequest = m_request; - -// m_requestQueue.clear(); - for(unsigned i = 0; i < n; i++) - { - KURL url; - stream >> url >> mIncomingMetaData; - - if ( !checkRequestURL( url ) ) - continue; - - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::multi_get " << url.prettyURL() << endl; - - m_request.method = HTTP_GET; - m_request.path = url.path(); - m_request.query = url.query(); - TQString tmp = metaData("cache"); - if (!tmp.isEmpty()) - m_request.cache = parseCacheControl(tmp); - else - m_request.cache = DEFAULT_CACHE_CONTROL; - - m_request.passwd = url.pass(); - m_request.user = url.user(); - m_request.doProxy = m_bUseProxy; - - HTTPRequest *newRequest = new HTTPRequest(m_request); - m_requestQueue.append(newRequest); - } - - if (m_bBusy) - m_request = saveRequest; - - if (!m_bBusy) - { - m_bBusy = true; - while(!m_requestQueue.isEmpty()) - { - HTTPRequest *request = m_requestQueue.take(0); - m_request = *request; - delete request; - retrieveContent(); - } - m_bBusy = false; - } -} - -ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes) -{ - int bytes_sent = 0; - const char* buf = static_cast(_buf); - while ( nbytes > 0 ) - { - int n = TCPSlaveBase::write(buf, nbytes); - - if ( n <= 0 ) - { - // remote side closed connection ? - if ( n == 0 ) - break; - // a valid exception(s) occurred, let's retry... - if (n < 0 && ((errno == EINTR) || (errno == EAGAIN))) - continue; - // some other error occurred ? - return -1; - } - - nbytes -= n; - buf += n; - bytes_sent += n; - } - - return bytes_sent; -} - -void HTTPProtocol::setRewindMarker() -{ - m_rewindCount = 0; -} - -void HTTPProtocol::rewind() -{ - m_linePtrUnget = m_rewindBuf, - m_lineCountUnget = m_rewindCount; - m_rewindCount = 0; -} - - -char *HTTPProtocol::gets (char *s, int size) -{ - int len=0; - char *buf=s; - char mybuf[2]={0,0}; - - while (len < size) - { - read(mybuf, 1); - if (m_bEOF) - break; - - if (m_rewindCount < sizeof(m_rewindBuf)) - m_rewindBuf[m_rewindCount++] = *mybuf; - - if (*mybuf == '\r') // Ignore! - continue; - - if ((*mybuf == '\n') || !*mybuf) - break; - - *buf++ = *mybuf; - len++; - } - - *buf=0; - return s; -} - -ssize_t HTTPProtocol::read (void *b, size_t nbytes) -{ - ssize_t ret = 0; - - if (m_lineCountUnget > 0) - { - ret = ( nbytes < m_lineCountUnget ? nbytes : m_lineCountUnget ); - m_lineCountUnget -= ret; - memcpy(b, m_linePtrUnget, ret); - m_linePtrUnget += ret; - - return ret; - } - - if (m_lineCount > 0) - { - ret = ( nbytes < m_lineCount ? nbytes : m_lineCount ); - m_lineCount -= ret; - memcpy(b, m_linePtr, ret); - m_linePtr += ret; - return ret; - } - - if (nbytes == 1) - { - ret = read(m_lineBuf, 1024); // Read into buffer - m_linePtr = m_lineBuf; - if (ret <= 0) - { - m_lineCount = 0; - return ret; - } - m_lineCount = ret; - return read(b, 1); // Read from buffer - } - - do - { - ret = TCPSlaveBase::read( b, nbytes); - if (ret == 0) - m_bEOF = true; - - } while ((ret == -1) && (errno == EAGAIN || errno == EINTR)); - - return ret; -} - -void HTTPProtocol::httpCheckConnection() -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCheckConnection: " << - " Socket status: " << m_iSock << - " Keep Alive: " << m_bKeepAlive << - " First: " << m_bFirstRequest << endl; - - if ( !m_bFirstRequest && (m_iSock != -1) ) - { - bool closeDown = false; - if ( !isConnectionValid()) - { - kdDebug(7113) << "(" << m_pid << ") Connection lost!" << endl; - closeDown = true; - } - else if ( m_request.method != HTTP_GET ) - { - closeDown = true; - } - else if ( !m_state.doProxy && !m_request.doProxy ) - { - if (m_state.hostname != m_request.hostname || - m_state.port != m_request.port || - m_state.user != m_request.user || - m_state.passwd != m_request.passwd) - closeDown = true; - } - else - { - // Keep the connection to the proxy. - if ( !(m_request.doProxy && m_state.doProxy) ) - closeDown = true; - } - - if (closeDown) - httpCloseConnection(); - } - - // Let's update our current state - m_state.hostname = m_request.hostname; - m_state.encoded_hostname = m_request.encoded_hostname; - m_state.port = m_request.port; - m_state.user = m_request.user; - m_state.passwd = m_request.passwd; - m_state.doProxy = m_request.doProxy; -} - -bool HTTPProtocol::httpOpenConnection() -{ - int errCode; - TQString errMsg; - - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpenConnection" << endl; - - setBlockConnection( true ); - // tdeio_http uses its own proxying: - KSocks::self()->disableSocks(); - - if ( m_state.doProxy ) - { - TQString proxy_host = m_proxyURL.host(); - int proxy_port = m_proxyURL.port(); - - kdDebug(7113) << "(" << m_pid << ") Connecting to proxy server: " - << proxy_host << ", port: " << proxy_port << endl; - - infoMessage( i18n("Connecting to %1...").arg(m_state.hostname) ); - - setConnectTimeout( m_proxyConnTimeout ); - - if ( !connectToHost(proxy_host, proxy_port, false) ) - { - if (userAborted()) { - error(ERR_NO_CONTENT, ""); - return false; - } - - switch ( connectResult() ) - { - case IO_LookupError: - errMsg = proxy_host; - errCode = ERR_UNKNOWN_PROXY_HOST; - break; - case IO_TimeOutError: - errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port); - errCode = ERR_SERVER_TIMEOUT; - break; - default: - errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port); - errCode = ERR_COULD_NOT_CONNECT; - } - error( errCode, errMsg ); - return false; - } - } - else - { - // Apparently we don't want a proxy. let's just connect directly - setConnectTimeout(m_remoteConnTimeout); - - if ( !connectToHost(m_state.hostname, m_state.port, false ) ) - { - if (userAborted()) { - error(ERR_NO_CONTENT, ""); - return false; - } - - switch ( connectResult() ) - { - case IO_LookupError: - errMsg = m_state.hostname; - errCode = ERR_UNKNOWN_HOST; - break; - case IO_TimeOutError: - errMsg = i18n("Connection was to %1 at port %2").arg(m_state.hostname).arg(m_state.port); - errCode = ERR_SERVER_TIMEOUT; - break; - default: - errCode = ERR_COULD_NOT_CONNECT; - if (m_state.port != m_iDefaultPort) - errMsg = i18n("%1 (port %2)").arg(m_state.hostname).arg(m_state.port); - else - errMsg = m_state.hostname; - } - error( errCode, errMsg ); - return false; - } - } - - // Set our special socket option!! - int on = 1; - (void) setsockopt( m_iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on) ); - - m_bFirstRequest = true; - - connected(); - return true; -} - - -/** - * This function is responsible for opening up the connection to the remote - * HTTP server and sending the header. If this requires special - * authentication or other such fun stuff, then it will handle it. This - * function will NOT receive anything from the server, however. This is in - * contrast to previous incarnations of 'httpOpen'. - * - * The reason for the change is due to one small fact: some requests require - * data to be sent in addition to the header (POST requests) and there is no - * way for this function to get that data. This function is called in the - * slotPut() or slotGet() functions which, in turn, are called (indirectly) as - * a result of a TDEIOJob::put() or TDEIOJob::get(). It is those latter functions - * which are responsible for starting up this ioslave in the first place. - * This means that 'httpOpen' is called (essentially) as soon as the ioslave - * is created -- BEFORE any data gets to this slave. - * - * The basic process now is this: - * - * 1) Open up the socket and port - * 2) Format our request/header - * 3) Send the header to the remote server - */ -bool HTTPProtocol::httpOpen() -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen" << endl; - - // Cannot have an https request without the m_bIsSSL being set! This can - // only happen if TCPSlaveBase::InitializeSSL() function failed in which it - // means the current installation does not support SSL... - if ( (m_protocol == "https" || m_protocol == "webdavs") && !m_bIsSSL ) - { - error( ERR_UNSUPPORTED_PROTOCOL, m_protocol ); - return false; - } - - m_request.fcache = 0; - m_request.bCachedRead = false; - m_request.bCachedWrite = false; - m_request.bMustRevalidate = false; - m_request.expireDate = 0; - m_request.creationDate = 0; - - if (m_request.bUseCache) - { - m_request.fcache = checkCacheEntry( ); - - bool bCacheOnly = (m_request.cache == TDEIO::CC_CacheOnly); - bool bOffline = isOffline(m_request.doProxy ? m_proxyURL : m_request.url); - if (bOffline && (m_request.cache != TDEIO::CC_Reload)) - m_request.cache = TDEIO::CC_CacheOnly; - - if (m_request.cache == CC_Reload && m_request.fcache) - { - if (m_request.fcache) - fclose(m_request.fcache); - m_request.fcache = 0; - } - if ((m_request.cache == TDEIO::CC_CacheOnly) || (m_request.cache == TDEIO::CC_Cache)) - m_request.bMustRevalidate = false; - - m_request.bCachedWrite = true; - - if (m_request.fcache && !m_request.bMustRevalidate) - { - // Cache entry is OK. - m_request.bCachedRead = true; // Cache hit. - return true; - } - else if (!m_request.fcache) - { - m_request.bMustRevalidate = false; // Cache miss - } - else - { - // Conditional cache hit. (Validate) - } - - if (bCacheOnly && bOffline) - { - error( ERR_OFFLINE_MODE, m_request.url.prettyURL() ); - return false; - } - if (bCacheOnly) - { - error( ERR_DOES_NOT_EXIST, m_request.url.prettyURL() ); - return false; - } - if (bOffline) - { - error( ERR_OFFLINE_MODE, m_request.url.prettyURL() ); - return false; - } - } - - TQString header; - TQString davHeader; - - bool moreData = false; - bool davData = false; - - // Clear out per-connection settings... - resetConnectionSettings (); - - // Check the validity of the current connection, if one exists. - httpCheckConnection(); - - if ( !m_bIsTunneled && m_bNeedTunnel ) - { - setEnableSSLTunnel( true ); - // We send a HTTP 1.0 header since some proxies refuse HTTP 1.1 and we don't - // need any HTTP 1.1 capabilities for CONNECT - Waba - header = TQString("CONNECT %1:%2 HTTP/1.0" - "\r\n").arg( m_request.encoded_hostname).arg(m_request.port); - - // Identify who you are to the proxy server! - if (!m_request.userAgent.isEmpty()) - header += "User-Agent: " + m_request.userAgent + "\r\n"; - - /* Add hostname information */ - header += "Host: " + m_state.encoded_hostname; - - if (m_state.port != m_iDefaultPort) - header += TQString(":%1").arg(m_state.port); - header += "\r\n"; - - header += proxyAuthenticationHeader(); - } - else - { - // Determine if this is a POST or GET method - switch (m_request.method) - { - case HTTP_GET: - header = "GET "; - break; - case HTTP_PUT: - header = "PUT "; - moreData = true; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case HTTP_POST: - header = "POST "; - moreData = true; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case HTTP_HEAD: - header = "HEAD "; - break; - case HTTP_DELETE: - header = "DELETE "; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case HTTP_OPTIONS: - header = "OPTIONS "; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case DAV_PROPFIND: - header = "PROPFIND "; - davData = true; - davHeader = "Depth: "; - if ( hasMetaData( "davDepth" ) ) - { - kdDebug(7113) << "Reading DAV depth from metadata: " << metaData( "davDepth" ) << endl; - davHeader += metaData( "davDepth" ); - } - else - { - if ( m_request.davData.depth == 2 ) - davHeader += "infinity"; - else - davHeader += TQString("%1").arg( m_request.davData.depth ); - } - davHeader += "\r\n"; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case DAV_PROPPATCH: - header = "PROPPATCH "; - davData = true; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case DAV_MKCOL: - header = "MKCOL "; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case DAV_COPY: - case DAV_MOVE: - header = ( m_request.method == DAV_COPY ) ? "COPY " : "MOVE "; - davHeader = "Destination: " + m_request.davData.desturl; - // infinity depth means copy recursively - // (optional for copy -> but is the desired action) - davHeader += "\r\nDepth: infinity\r\nOverwrite: "; - davHeader += m_request.davData.overwrite ? "T" : "F"; - davHeader += "\r\n"; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case DAV_LOCK: - header = "LOCK "; - davHeader = "Timeout: "; - { - uint timeout = 0; - if ( hasMetaData( "davTimeout" ) ) - timeout = metaData( "davTimeout" ).toUInt(); - if ( timeout == 0 ) - davHeader += "Infinite"; - else - davHeader += TQString("Seconds-%1").arg(timeout); - } - davHeader += "\r\n"; - m_request.bCachedWrite = false; // Do not put any result in the cache - davData = true; - break; - case DAV_UNLOCK: - header = "UNLOCK "; - davHeader = "Lock-token: " + metaData("davLockToken") + "\r\n"; - m_request.bCachedWrite = false; // Do not put any result in the cache - break; - case DAV_SEARCH: - header = "SEARCH "; - davData = true; - m_request.bCachedWrite = false; - break; - case DAV_SUBSCRIBE: - header = "SUBSCRIBE "; - m_request.bCachedWrite = false; - break; - case DAV_UNSUBSCRIBE: - header = "UNSUBSCRIBE "; - m_request.bCachedWrite = false; - break; - case DAV_POLL: - header = "POLL "; - m_request.bCachedWrite = false; - break; - default: - error (ERR_UNSUPPORTED_ACTION, TQString::null); - return false; - } - // DAV_POLL; DAV_NOTIFY - - // format the URI - if (m_state.doProxy && !m_bIsTunneled) - { - KURL u; - - if (m_protocol == "webdav") - u.setProtocol( "http" ); - else if (m_protocol == "webdavs" ) - u.setProtocol( "https" ); - else - u.setProtocol( m_protocol ); - - // For all protocols other than the once handled by this io-slave - // append the username. This fixes a long standing bug of ftp io-slave - // logging in anonymously in proxied connections even when the username - // is explicitly specified. - if (m_protocol != "http" && m_protocol != "https" && - !m_state.user.isEmpty()) - u.setUser (m_state.user); - - u.setHost( m_state.hostname ); - if (m_state.port != m_iDefaultPort) - u.setPort( m_state.port ); - u.setEncodedPathAndQuery( m_request.url.encodedPathAndQuery(0,true) ); - header += u.url(); - } - else - { - header += m_request.url.encodedPathAndQuery(0, true); - } - - header += " HTTP/1.1\r\n"; /* start header */ - - if (!m_request.userAgent.isEmpty()) - { - header += "User-Agent: "; - header += m_request.userAgent; - header += "\r\n"; - } - - if (!m_request.referrer.isEmpty()) - { - header += "Referer: "; //Don't try to correct spelling! - header += m_request.referrer; - header += "\r\n"; - } - - if ( m_request.offset > 0 ) - { - header += TQString("Range: bytes=%1-\r\n").arg(TDEIO::number(m_request.offset)); - kdDebug(7103) << "tdeio_http : Range = " << TDEIO::number(m_request.offset) << endl; - } - - if ( m_request.cache == CC_Reload ) - { - /* No caching for reload */ - header += "Pragma: no-cache\r\n"; /* for HTTP/1.0 caches */ - header += "Cache-control: no-cache\r\n"; /* for HTTP >=1.1 caches */ - } - - if (m_request.bMustRevalidate) - { - /* conditional get */ - if (!m_request.etag.isEmpty()) - header += "If-None-Match: "+m_request.etag+"\r\n"; - if (!m_request.lastModified.isEmpty()) - header += "If-Modified-Since: "+m_request.lastModified+"\r\n"; - } - - header += "Accept: "; - TQString acceptHeader = metaData("accept"); - if (!acceptHeader.isEmpty()) - header += acceptHeader; - else - header += DEFAULT_ACCEPT_HEADER; - header += "\r\n"; - -#ifdef DO_GZIP - if (m_request.allowCompressedPage) - header += "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n"; -#endif - - if (!m_request.charsets.isEmpty()) - header += "Accept-Charset: " + m_request.charsets + "\r\n"; - - if (!m_request.languages.isEmpty()) - header += "Accept-Language: " + m_request.languages + "\r\n"; - - - /* support for virtual hosts and required by HTTP 1.1 */ - header += "Host: " + m_state.encoded_hostname; - - if (m_state.port != m_iDefaultPort) - header += TQString(":%1").arg(m_state.port); - header += "\r\n"; - - TQString cookieStr; - TQString cookieMode = metaData("cookies").lower(); - if (cookieMode == "none") - { - m_request.cookieMode = HTTPRequest::CookiesNone; - } - else if (cookieMode == "manual") - { - m_request.cookieMode = HTTPRequest::CookiesManual; - cookieStr = metaData("setcookies"); - } - else - { - m_request.cookieMode = HTTPRequest::CookiesAuto; - if (m_request.bUseCookiejar) - cookieStr = findCookies( m_request.url.url()); - } - - if (!cookieStr.isEmpty()) - header += cookieStr + "\r\n"; - - TQString customHeader = metaData( "customHTTPHeader" ); - if (!customHeader.isEmpty()) - { - header += sanitizeCustomHTTPHeader(customHeader); - header += "\r\n"; - } - - if (m_request.method == HTTP_POST) - { - header += metaData("content-type"); - header += "\r\n"; - } - - // Only check for a cached copy if the previous - // response was NOT a 401 or 407. - // no caching for Negotiate auth. - if ( !m_request.bNoAuth && m_responseCode != 401 && m_responseCode != 407 && Authentication != AUTH_Negotiate ) - { - kdDebug(7113) << "(" << m_pid << ") Calling checkCachedAuthentication " << endl; - AuthInfo info; - info.url = m_request.url; - info.verifyPath = true; - if ( !m_request.user.isEmpty() ) - info.username = m_request.user; - if ( checkCachedAuthentication( info ) && !info.digestInfo.isEmpty() ) - { - Authentication = info.digestInfo.startsWith("Basic") ? AUTH_Basic : info.digestInfo.startsWith("NTLM") ? AUTH_NTLM : info.digestInfo.startsWith("Negotiate") ? AUTH_Negotiate : AUTH_Digest ; - m_state.user = info.username; - m_state.passwd = info.password; - m_strRealm = info.realmValue; - if ( Authentication != AUTH_NTLM && Authentication != AUTH_Negotiate ) // don't use the cached challenge - m_strAuthorization = info.digestInfo; - } - } - else - { - kdDebug(7113) << "(" << m_pid << ") Not calling checkCachedAuthentication " << endl; - } - - switch ( Authentication ) - { - case AUTH_Basic: - header += createBasicAuth(); - break; - case AUTH_Digest: - header += createDigestAuth(); - break; -#ifdef HAVE_LIBGSSAPI - case AUTH_Negotiate: - header += createNegotiateAuth(); - break; -#endif - case AUTH_NTLM: - header += createNTLMAuth(); - break; - case AUTH_None: - default: - break; - } - - /********* Only for debugging purpose *********/ - if ( Authentication != AUTH_None ) - { - kdDebug(7113) << "(" << m_pid << ") Using Authentication: " << endl; - kdDebug(7113) << "(" << m_pid << ") HOST= " << m_state.hostname << endl; - kdDebug(7113) << "(" << m_pid << ") PORT= " << m_state.port << endl; - kdDebug(7113) << "(" << m_pid << ") USER= " << m_state.user << endl; - kdDebug(7113) << "(" << m_pid << ") PASSWORD= [protected]" << endl; - kdDebug(7113) << "(" << m_pid << ") REALM= " << m_strRealm << endl; - kdDebug(7113) << "(" << m_pid << ") EXTRA= " << m_strAuthorization << endl; - } - - // Do we need to authorize to the proxy server ? - if ( m_state.doProxy && !m_bIsTunneled ) - header += proxyAuthenticationHeader(); - - // Support old HTTP/1.0 style keep-alive header for compatability - // purposes as well as performance improvements while giving end - // users the ability to disable this feature proxy servers that - // don't not support such feature, e.g. junkbuster proxy server. - if (!m_bUseProxy || m_bPersistentProxyConnection || m_bIsTunneled) - header += "Connection: Keep-Alive\r\n"; - else - header += "Connection: close\r\n"; - - if ( m_protocol == "webdav" || m_protocol == "webdavs" ) - { - header += davProcessLocks(); - - // add extra webdav headers, if supplied - TQString davExtraHeader = metaData("davHeader"); - if ( !davExtraHeader.isEmpty() ) - davHeader += davExtraHeader; - - // Set content type of webdav data - if (davData) - davHeader += "Content-Type: text/xml; charset=utf-8\r\n"; - - // add extra header elements for WebDAV - if ( !davHeader.isNull() ) - header += davHeader; - } - } - - kdDebug(7103) << "(" << m_pid << ") ============ Sending Header:" << endl; - - TQStringList headerOutput = TQStringList::split("\r\n", header); - TQStringList::Iterator it = headerOutput.begin(); - - for (; it != headerOutput.end(); it++) - kdDebug(7103) << "(" << m_pid << ") " << (*it) << endl; - - if ( !moreData && !davData) - header += "\r\n"; /* end header */ - - // Now that we have our formatted header, let's send it! - // Create a new connection to the remote machine if we do - // not already have one... - if ( m_iSock == -1) - { - if (!httpOpenConnection()) - return false; - } - - // Send the data to the remote machine... - bool sendOk = (write(header.latin1(), header.length()) == (ssize_t) header.length()); - if (!sendOk) - { - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: " - "Connection broken! (" << m_state.hostname << ")" << endl; - - // With a Keep-Alive connection this can happen. - // Just reestablish the connection. - if (m_bKeepAlive) - { - httpCloseConnection(); - return true; // Try again - } - - if (!sendOk) - { - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: sendOk==false." - " Connnection broken !" << endl; - error( ERR_CONNECTION_BROKEN, m_state.hostname ); - return false; - } - } - - bool res = true; - - if ( moreData || davData ) - res = sendBody(); - - infoMessage(i18n("%1 contacted. Waiting for reply...").arg(m_request.hostname)); - - return res; -} - -void HTTPProtocol::forwardHttpResponseHeader() -{ - // Send the response header if it was requested - if ( config()->readBoolEntry("PropagateHttpHeader", false) ) - { - setMetaData("HTTP-Headers", m_responseHeader.join("\n")); - sendMetaData(); - } - m_responseHeader.clear(); -} - -/** - * This function will read in the return header from the server. It will - * not read in the body of the return message. It will also not transmit - * the header to our client as the client doesn't need to know the gory - * details of HTTP headers. - */ -bool HTTPProtocol::readHeader() -{ -try_again: - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader" << endl; - - // Check - if (m_request.bCachedRead) - { - m_responseHeader << "HTTP-CACHE"; - // Read header from cache... - char buffer[4097]; - if (!fgets(buffer, 4096, m_request.fcache) ) - { - // Error, delete cache entry - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: " - << "Could not access cache to obtain mimetype!" << endl; - error( ERR_CONNECTION_BROKEN, m_state.hostname ); - return false; - } - - m_strMimeType = TQString(TQString::fromUtf8( buffer)).stripWhiteSpace(); - - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: cached " - << "data mimetype: " << m_strMimeType << endl; - - if (!fgets(buffer, 4096, m_request.fcache) ) - { - // Error, delete cache entry - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: " - << "Could not access cached data! " << endl; - error( ERR_CONNECTION_BROKEN, m_state.hostname ); - return false; - } - - m_request.strCharset = TQString(TQString::fromUtf8( buffer)).stripWhiteSpace().lower(); - setMetaData("charset", m_request.strCharset); - if (!m_request.lastModified.isEmpty()) - setMetaData("modified", m_request.lastModified); - TQString tmp; - tmp.setNum(m_request.expireDate); - setMetaData("expire-date", tmp); - tmp.setNum(m_request.creationDate); - setMetaData("cache-creation-date", tmp); - mimeType(m_strMimeType); - forwardHttpResponseHeader(); - return true; - } - - TQCString locationStr; // In case we get a redirect. - TQCString cookieStr; // In case we get a cookie. - - TQString dispositionType; // In case we get a Content-Disposition type - TQString dispositionFilename; // In case we get a Content-Disposition filename - - TQString mediaValue; - TQString mediaAttribute; - - TQStringList upgradeOffers; - - bool upgradeRequired = false; // Server demands that we upgrade to something - // This is also true if we ask to upgrade and - // the server accepts, since we are now - // committed to doing so - bool canUpgrade = false; // The server offered an upgrade - - - m_request.etag = TQString::null; - m_request.lastModified = TQString::null; - m_request.strCharset = TQString::null; - - time_t dateHeader = 0; - time_t expireDate = 0; // 0 = no info, 1 = already expired, > 1 = actual date - int currentAge = 0; - int maxAge = -1; // -1 = no max age, 0 already expired, > 0 = actual time - int maxHeaderSize = 64*1024; // 64Kb to catch DOS-attacks - - // read in 8192 bytes at a time (HTTP cookies can be quite large.) - int len = 0; - char buffer[8193]; - bool cont = false; - bool cacheValidated = false; // Revalidation was successful - bool mayCache = true; - bool hasCacheDirective = false; - bool bCanResume = false; - - if (m_iSock == -1) - { - kdDebug(7113) << "HTTPProtocol::readHeader: No connection." << endl; - return false; // Restablish connection and try again - } - - if (!waitForResponse(m_remoteRespTimeout)) - { - // No response error - error( ERR_SERVER_TIMEOUT , m_state.hostname ); - return false; - } - - setRewindMarker(); - - gets(buffer, sizeof(buffer)-1); - - if (m_bEOF || *buffer == '\0') - { - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: " - << "EOF while waiting for header start." << endl; - if (m_bKeepAlive) // Try to reestablish connection. - { - httpCloseConnection(); - return false; // Reestablish connection and try again. - } - - if (m_request.method == HTTP_HEAD) - { - // HACK - // Some web-servers fail to respond properly to a HEAD request. - // We compensate for their failure to properly implement the HTTP standard - // by assuming that they will be sending html. - kdDebug(7113) << "(" << m_pid << ") HTTPPreadHeader: HEAD -> returned " - << "mimetype: " << DEFAULT_MIME_TYPE << endl; - mimeType(TQString::fromLatin1(DEFAULT_MIME_TYPE)); - return true; - } - - kdDebug(7113) << "HTTPProtocol::readHeader: Connection broken !" << endl; - error( ERR_CONNECTION_BROKEN, m_state.hostname ); - return false; - } - - kdDebug(7103) << "(" << m_pid << ") ============ Received Response:"<< endl; - - bool noHeader = true; - HTTP_REV httpRev = HTTP_None; - int headerSize = 0; - - do - { - // strip off \r and \n if we have them - len = strlen(buffer); - - while(len && (buffer[len-1] == '\n' || buffer[len-1] == '\r')) - buffer[--len] = 0; - - // if there was only a newline then continue - if (!len) - { - kdDebug(7103) << "(" << m_pid << ") --empty--" << endl; - continue; - } - - headerSize += len; - - // We have a response header. This flag is a work around for - // servers that append a "\r\n" before the beginning of the HEADER - // response!!! It only catches x number of \r\n being placed at the - // top of the reponse... - noHeader = false; - - kdDebug(7103) << "(" << m_pid << ") \"" << buffer << "\"" << endl; - - // Save broken servers from damnation!! - char* buf = buffer; - while( *buf == ' ' ) - buf++; - - - if (buf[0] == '<') - { - // We get XML / HTTP without a proper header - // put string back - kdDebug(7103) << "tdeio_http: No valid HTTP header found! Document starts with XML/HTML tag" << endl; - - // Document starts with a tag, assume html instead of text/plain - m_strMimeType = "text/html"; - - rewind(); - break; - } - - // Store the the headers so they can be passed to the - // calling application later - m_responseHeader << TQString::fromLatin1(buf); - - if ((strncasecmp(buf, "HTTP/", 5) == 0) || - (strncasecmp(buf, "ICY ", 4) == 0)) // Shoutcast support - { - if (strncasecmp(buf, "ICY ", 4) == 0) - { - // Shoutcast support - httpRev = SHOUTCAST; - m_bKeepAlive = false; - } - else if (strncmp((buf + 5), "1.0",3) == 0) - { - httpRev = HTTP_10; - // For 1.0 servers, the server itself has to explicitly - // tell us whether it supports persistent connection or - // not. By default, we assume it does not, but we do - // send the old style header "Connection: Keep-Alive" to - // inform it that we support persistence. - m_bKeepAlive = false; - } - else if (strncmp((buf + 5), "1.1",3) == 0) - { - httpRev = HTTP_11; - } - else - { - httpRev = HTTP_Unknown; - } - - if (m_responseCode) - m_prevResponseCode = m_responseCode; - - const char* rptr = buf; - while ( *rptr && *rptr > ' ' ) - ++rptr; - m_responseCode = atoi(rptr); - - // server side errors - if (m_responseCode >= 500 && m_responseCode <= 599) - { - if (m_request.method == HTTP_HEAD) - { - ; // Ignore error - } - else - { - if (m_request.bErrorPage) - errorPage(); - else - { - error(ERR_INTERNAL_SERVER, m_request.url.prettyURL()); - return false; - } - } - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - } - // Unauthorized access - else if (m_responseCode == 401 || m_responseCode == 407) - { - // Double authorization requests, i.e. a proxy auth - // request followed immediately by a regular auth request. - if ( m_prevResponseCode != m_responseCode && - (m_prevResponseCode == 401 || m_prevResponseCode == 407) ) - saveAuthorization(); - - m_bUnauthorized = true; - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - } - // - else if (m_responseCode == 416) // Range not supported - { - m_request.offset = 0; - httpCloseConnection(); - return false; // Try again. - } - // Upgrade Required - else if (m_responseCode == 426) - { - upgradeRequired = true; - } - // Any other client errors - else if (m_responseCode >= 400 && m_responseCode <= 499) - { - // Tell that we will only get an error page here. - if (m_request.bErrorPage) - errorPage(); - else - { - error(ERR_DOES_NOT_EXIST, m_request.url.prettyURL()); - return false; - } - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - } - else if (m_responseCode == 307) - { - // 307 Temporary Redirect - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - } - else if (m_responseCode == 304) - { - // 304 Not Modified - // The value in our cache is still valid. - cacheValidated = true; - } - else if (m_responseCode >= 301 && m_responseCode<= 303) - { - // 301 Moved permanently - if (m_responseCode == 301) - setMetaData("permanent-redirect", "true"); - - // 302 Found (temporary location) - // 303 See Other - if (m_request.method != HTTP_HEAD && m_request.method != HTTP_GET) - { -#if 0 - // Reset the POST buffer to avoid a double submit - // on redirection - if (m_request.method == HTTP_POST) - m_bufPOST.resize(0); -#endif - - // NOTE: This is wrong according to RFC 2616. However, - // because most other existing user agent implementations - // treat a 301/302 response as a 303 response and preform - // a GET action regardless of what the previous method was, - // many servers have simply adapted to this way of doing - // things!! Thus, we are forced to do the same thing or we - // won't be able to retrieve these pages correctly!! See RFC - // 2616 sections 10.3.[2/3/4/8] - m_request.method = HTTP_GET; // Force a GET - } - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - } - else if ( m_responseCode == 207 ) // Multi-status (for WebDav) - { - - } - else if ( m_responseCode == 204 ) // No content - { - // error(ERR_NO_CONTENT, i18n("Data have been successfully sent.")); - // Short circuit and do nothing! - - // The original handling here was wrong, this is not an error: eg. in the - // example of a 204 No Content response to a PUT completing. - // m_bError = true; - // return false; - } - else if ( m_responseCode == 206 ) - { - if ( m_request.offset ) - bCanResume = true; - } - else if (m_responseCode == 102) // Processing (for WebDAV) - { - /*** - * This status code is given when the server expects the - * command to take significant time to complete. So, inform - * the user. - */ - infoMessage( i18n( "Server processing request, please wait..." ) ); - cont = true; - } - else if (m_responseCode == 100) - { - // We got 'Continue' - ignore it - cont = true; - } - } - - // are we allowd to resume? this will tell us - else if (strncasecmp(buf, "Accept-Ranges:", 14) == 0) { - if (strncasecmp(trimLead(buf + 14), "none", 4) == 0) - bCanResume = false; - } - // Keep Alive - else if (strncasecmp(buf, "Keep-Alive:", 11) == 0) { - TQStringList options = TQStringList::split(',', - TQString::fromLatin1(trimLead(buf+11))); - for(TQStringList::ConstIterator it = options.begin(); - it != options.end(); - it++) - { - TQString option = (*it).stripWhiteSpace().lower(); - if (option.startsWith("timeout=")) - { - m_keepAliveTimeout = option.mid(8).toInt(); - } - } - } - - // Cache control - else if (strncasecmp(buf, "Cache-Control:", 14) == 0) { - TQStringList cacheControls = TQStringList::split(',', - TQString::fromLatin1(trimLead(buf+14))); - for(TQStringList::ConstIterator it = cacheControls.begin(); - it != cacheControls.end(); - it++) - { - TQString cacheControl = (*it).stripWhiteSpace(); - if (strncasecmp(cacheControl.latin1(), "no-cache", 8) == 0) - { - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - } - else if (strncasecmp(cacheControl.latin1(), "no-store", 8) == 0) - { - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - } - else if (strncasecmp(cacheControl.latin1(), "max-age=", 8) == 0) - { - TQString age = cacheControl.mid(8).stripWhiteSpace(); - if (!age.isNull()) - maxAge = STRTOLL(age.latin1(), 0, 10); - } - } - hasCacheDirective = true; - } - - // get the size of our data - else if (strncasecmp(buf, "Content-length:", 15) == 0) { - char* len = trimLead(buf + 15); - if (len) - m_iSize = STRTOLL(len, 0, 10); - } - - else if (strncasecmp(buf, "Content-location:", 17) == 0) { - setMetaData ("content-location", - TQString::fromLatin1(trimLead(buf+17)).stripWhiteSpace()); - } - - // what type of data do we have? - else if (strncasecmp(buf, "Content-type:", 13) == 0) { - char *start = trimLead(buf + 13); - char *pos = start; - - // Increment until we encounter ";" or the end of the buffer - while ( *pos && *pos != ';' ) pos++; - - // Assign the mime-type. - m_strMimeType = TQString::fromLatin1(start, pos-start).stripWhiteSpace().lower(); - kdDebug(7113) << "(" << m_pid << ") Content-type: " << m_strMimeType << endl; - - // If we still have text, then it means we have a mime-type with a - // parameter (eg: charset=iso-8851) ; so let's get that... - while (*pos) - { - start = ++pos; - while ( *pos && *pos != '=' ) pos++; - - char *end = pos; - while ( *end && *end != ';' ) end++; - - if (*pos) - { - mediaAttribute = TQString::fromLatin1(start, pos-start).stripWhiteSpace().lower(); - mediaValue = TQString::fromLatin1(pos+1, end-pos-1).stripWhiteSpace(); - pos = end; - if (mediaValue.length() && - (mediaValue[0] == '"') && - (mediaValue[mediaValue.length()-1] == '"')) - mediaValue = mediaValue.mid(1, mediaValue.length()-2); - - kdDebug (7113) << "(" << m_pid << ") Media-Parameter Attribute: " - << mediaAttribute << endl; - kdDebug (7113) << "(" << m_pid << ") Media-Parameter Value: " - << mediaValue << endl; - - if ( mediaAttribute == "charset") - { - mediaValue = mediaValue.lower(); - m_request.strCharset = mediaValue; - } - else - { - setMetaData("media-"+mediaAttribute, mediaValue); - } - } - } - } - - // Date - else if (strncasecmp(buf, "Date:", 5) == 0) { - dateHeader = KRFCDate::parseDate(trimLead(buf+5)); - } - - // Cache management - else if (strncasecmp(buf, "ETag:", 5) == 0) { - m_request.etag = trimLead(buf+5); - } - - // Cache management - else if (strncasecmp(buf, "Expires:", 8) == 0) { - expireDate = KRFCDate::parseDate(trimLead(buf+8)); - if (!expireDate) - expireDate = 1; // Already expired - } - - // Cache management - else if (strncasecmp(buf, "Last-Modified:", 14) == 0) { - m_request.lastModified = (TQString::fromLatin1(trimLead(buf+14))).stripWhiteSpace(); - } - - // whoops.. we received a warning - else if (strncasecmp(buf, "Warning:", 8) == 0) { - //Don't use warning() here, no need to bother the user. - //Those warnings are mostly about caches. - infoMessage(trimLead(buf + 8)); - } - - // Cache management (HTTP 1.0) - else if (strncasecmp(buf, "Pragma:", 7) == 0) { - TQCString pragma = TQCString(trimLead(buf+7)).stripWhiteSpace().lower(); - if (pragma == "no-cache") - { - m_request.bCachedWrite = false; // Don't put in cache - mayCache = false; - hasCacheDirective = true; - } - } - - // The deprecated Refresh Response - else if (strncasecmp(buf,"Refresh:", 8) == 0) { - mayCache = false; // Do not cache page as it defeats purpose of Refresh tag! - setMetaData( "http-refresh", TQString::fromLatin1(trimLead(buf+8)).stripWhiteSpace() ); - } - - // In fact we should do redirection only if we got redirection code - else if (strncasecmp(buf, "Location:", 9) == 0) { - // Redirect only for 3xx status code, will ya! Thanks, pal! - if ( m_responseCode > 299 && m_responseCode < 400 ) - locationStr = TQCString(trimLead(buf+9)).stripWhiteSpace(); - } - - // Check for cookies - else if (strncasecmp(buf, "Set-Cookie", 10) == 0) { - cookieStr += buf; - cookieStr += '\n'; - } - - // check for direct authentication - else if (strncasecmp(buf, "WWW-Authenticate:", 17) == 0) { - configAuth(trimLead(buf + 17), false); - } - - // check for proxy-based authentication - else if (strncasecmp(buf, "Proxy-Authenticate:", 19) == 0) { - configAuth(trimLead(buf + 19), true); - } - - else if (strncasecmp(buf, "Upgrade:", 8) == 0) { - // Now we have to check to see what is offered for the upgrade - TQString offered = &(buf[8]); - upgradeOffers = TQStringList::split(TQRegExp("[ \n,\r\t]"), offered); - } - - // content? - else if (strncasecmp(buf, "Content-Encoding:", 17) == 0) { - // This is so wrong !! No wonder tdeio_http is stripping the - // gzip encoding from downloaded files. This solves multiple - // bug reports and caitoo's problem with downloads when such a - // header is encountered... - - // A quote from RFC 2616: - // " When present, its (Content-Encoding) value indicates what additional - // content have been applied to the entity body, and thus what decoding - // mechanism must be applied to obtain the media-type referenced by the - // Content-Type header field. Content-Encoding is primarily used to allow - // a document to be compressed without loosing the identity of its underlying - // media type. Simply put if it is specified, this is the actual mime-type - // we should use when we pull the resource !!! - addEncoding(trimLead(buf + 17), m_qContentEncodings); - } - // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183 - else if(strncasecmp(buf, "Content-Disposition:", 20) == 0) { - char* dispositionBuf = trimLead(buf + 20); - while ( *dispositionBuf ) - { - if ( strncasecmp( dispositionBuf, "filename", 8 ) == 0 ) - { - dispositionBuf += 8; - - while ( *dispositionBuf == ' ' || *dispositionBuf == '=' ) - dispositionBuf++; - - char* bufStart = dispositionBuf; - - while ( *dispositionBuf && *dispositionBuf != ';' ) - dispositionBuf++; - - if ( dispositionBuf > bufStart ) - { - // Skip any leading quotes... - while ( *bufStart == '"' ) - bufStart++; - - // Skip any trailing quotes as well as white spaces... - while ( *(dispositionBuf-1) == ' ' || *(dispositionBuf-1) == '"') - dispositionBuf--; - - if ( dispositionBuf > bufStart ) - dispositionFilename = TQString::fromLatin1( bufStart, dispositionBuf-bufStart ); - - break; - } - } - else - { - char *bufStart = dispositionBuf; - - while ( *dispositionBuf && *dispositionBuf != ';' ) - dispositionBuf++; - - if ( dispositionBuf > bufStart ) - dispositionType = TQString::fromLatin1( bufStart, dispositionBuf-bufStart ).stripWhiteSpace(); - - while ( *dispositionBuf == ';' || *dispositionBuf == ' ' ) - dispositionBuf++; - } - } - - // Content-Dispostion is not allowed to dictate directory - // path, thus we extract the filename only. - if ( !dispositionFilename.isEmpty() ) - { - int pos = dispositionFilename.findRev( '/' ); - - if( pos > -1 ) - dispositionFilename = dispositionFilename.mid(pos+1); - - kdDebug(7113) << "(" << m_pid << ") Content-Disposition: filename=" - << dispositionFilename<< endl; - } - } - else if(strncasecmp(buf, "Content-Language:", 17) == 0) { - TQString language = TQString::fromLatin1(trimLead(buf+17)).stripWhiteSpace(); - if (!language.isEmpty()) - setMetaData("content-language", language); - } - else if (strncasecmp(buf, "Proxy-Connection:", 17) == 0) - { - if (strncasecmp(trimLead(buf + 17), "Close", 5) == 0) - m_bKeepAlive = false; - else if (strncasecmp(trimLead(buf + 17), "Keep-Alive", 10)==0) - m_bKeepAlive = true; - } - else if (strncasecmp(buf, "Link:", 5) == 0) { - // We only support Link: ; rel="type" so far - TQStringList link = TQStringList::split(";", TQString(buf) - .replace(TQRegExp("^Link:[ ]*"), - "")); - if (link.count() == 2) { - TQString rel = link[1].stripWhiteSpace(); - if (rel.startsWith("rel=\"")) { - rel = rel.mid(5, rel.length() - 6); - if (rel.lower() == "pageservices") { - TQString url = TQString(link[0].replace(TQRegExp("[<>]"),"")).stripWhiteSpace(); - setMetaData("PageServices", url); - } - } - } - } - else if (strncasecmp(buf, "P3P:", 4) == 0) { - TQString p3pstr = buf; - p3pstr = p3pstr.mid(4).simplifyWhiteSpace(); - TQStringList policyrefs, compact; - TQStringList policyfields = TQStringList::split(TQRegExp(",[ ]*"), p3pstr); - for (TQStringList::Iterator it = policyfields.begin(); - it != policyfields.end(); - ++it) { - TQStringList policy = TQStringList::split("=", *it); - - if (policy.count() == 2) { - if (policy[0].lower() == "policyref") { - policyrefs << TQString(policy[1].replace(TQRegExp("[\"\']"), "")) - .stripWhiteSpace(); - } else if (policy[0].lower() == "cp") { - // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with - // other metadata sent in strings. This could be a bit more - // efficient but I'm going for correctness right now. - TQStringList cps = TQStringList::split(" ", - TQString(policy[1].replace(TQRegExp("[\"\']"), "")) - .simplifyWhiteSpace()); - - for (TQStringList::Iterator j = cps.begin(); j != cps.end(); ++j) - compact << *j; - } - } - } - - if (!policyrefs.isEmpty()) - setMetaData("PrivacyPolicy", policyrefs.join("\n")); - - if (!compact.isEmpty()) - setMetaData("PrivacyCompactPolicy", compact.join("\n")); - } - // let them tell us if we should stay alive or not - else if (strncasecmp(buf, "Connection:", 11) == 0) - { - if (strncasecmp(trimLead(buf + 11), "Close", 5) == 0) - m_bKeepAlive = false; - else if (strncasecmp(trimLead(buf + 11), "Keep-Alive", 10)==0) - m_bKeepAlive = true; - else if (strncasecmp(trimLead(buf + 11), "Upgrade", 7)==0) - { - if (m_responseCode == 101) { - // Ok, an upgrade was accepted, now we must do it - upgradeRequired = true; - } else if (upgradeRequired) { // 426 - // Nothing to do since we did it above already - } else { - // Just an offer to upgrade - no need to take it - canUpgrade = true; - } - } - } - // continue only if we know that we're HTTP/1.1 - else if ( httpRev == HTTP_11) { - // what kind of encoding do we have? transfer? - if (strncasecmp(buf, "Transfer-Encoding:", 18) == 0) { - // If multiple encodings have been applied to an entity, the - // transfer-codings MUST be listed in the order in which they - // were applied. - addEncoding(trimLead(buf + 18), m_qTransferEncodings); - } - - // md5 signature - else if (strncasecmp(buf, "Content-MD5:", 12) == 0) { - m_sContentMD5 = TQString::fromLatin1(trimLead(buf + 12)); - } - - // *** Responses to the HTTP OPTIONS method follow - // WebDAV capabilities - else if (strncasecmp(buf, "DAV:", 4) == 0) { - if (m_davCapabilities.isEmpty()) { - m_davCapabilities << TQString::fromLatin1(trimLead(buf + 4)); - } - else { - m_davCapabilities << TQString::fromLatin1(trimLead(buf + 4)); - } - } - // *** Responses to the HTTP OPTIONS method finished - } - else if ((httpRev == HTTP_None) && (strlen(buf) != 0)) - { - // Remote server does not seem to speak HTTP at all - // Put the crap back into the buffer and hope for the best - rewind(); - if (m_responseCode) - m_prevResponseCode = m_responseCode; - - m_responseCode = 200; // Fake it - httpRev = HTTP_Unknown; - m_bKeepAlive = false; - break; - } - setRewindMarker(); - - // Clear out our buffer for further use. - memset(buffer, 0, sizeof(buffer)); - - } while (!m_bEOF && (len || noHeader) && (headerSize < maxHeaderSize) && (gets(buffer, sizeof(buffer)-1))); - - // Now process the HTTP/1.1 upgrade - TQStringList::Iterator opt = upgradeOffers.begin(); - for( ; opt != upgradeOffers.end(); ++opt) { - if (*opt == "TLS/1.0") { - if(upgradeRequired) { - if (!startTLS() && !usingTLS()) { - error(ERR_UPGRADE_REQUIRED, *opt); - return false; - } - } - } else if (*opt == "HTTP/1.1") { - httpRev = HTTP_11; - } else { - // unknown - if (upgradeRequired) { - error(ERR_UPGRADE_REQUIRED, *opt); - return false; - } - } - } - - setMetaData("charset", m_request.strCharset); - - // If we do not support the requested authentication method... - if ( (m_responseCode == 401 && Authentication == AUTH_None) || - (m_responseCode == 407 && ProxyAuthentication == AUTH_None) ) - { - m_bUnauthorized = false; - if (m_request.bErrorPage) - errorPage(); - else - { - error( ERR_UNSUPPORTED_ACTION, "Unknown Authorization method!" ); - return false; - } - } - - // Fixup expire date for clock drift. - if (expireDate && (expireDate <= dateHeader)) - expireDate = 1; // Already expired. - - // Convert max-age into expireDate (overriding previous set expireDate) - if (maxAge == 0) - expireDate = 1; // Already expired. - else if (maxAge > 0) - { - if (currentAge) - maxAge -= currentAge; - if (maxAge <=0) - maxAge = 0; - expireDate = time(0) + maxAge; - } - - if (!expireDate) - { - time_t lastModifiedDate = 0; - if (!m_request.lastModified.isEmpty()) - lastModifiedDate = KRFCDate::parseDate(m_request.lastModified); - - if (lastModifiedDate) - { - long diff = static_cast(difftime(dateHeader, lastModifiedDate)); - if (diff < 0) - expireDate = time(0) + 1; - else - expireDate = time(0) + (diff / 10); - } - else - { - expireDate = time(0) + DEFAULT_CACHE_EXPIRE; - } - } - - // DONE receiving the header! - if (!cookieStr.isEmpty()) - { - if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.bUseCookiejar) - { - // Give cookies to the cookiejar. - TQString domain = config()->readEntry("cross-domain"); - if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain)) - cookieStr = "Cross-Domain\n" + cookieStr; - addCookies( m_request.url.url(), cookieStr ); - } - else if (m_request.cookieMode == HTTPRequest::CookiesManual) - { - // Pass cookie to application - setMetaData("setcookies", cookieStr); - } - } - - if (m_request.bMustRevalidate) - { - m_request.bMustRevalidate = false; // Reset just in case. - if (cacheValidated) - { - // Yippie, we can use the cached version. - // Update the cache with new "Expire" headers. - fclose(m_request.fcache); - m_request.fcache = 0; - updateExpireDate( expireDate, true ); - m_request.fcache = checkCacheEntry( ); // Re-read cache entry - - if (m_request.fcache) - { - m_request.bCachedRead = true; - goto try_again; // Read header again, but now from cache. - } - else - { - // Where did our cache entry go??? - } - } - else - { - // Validation failed. Close cache. - fclose(m_request.fcache); - m_request.fcache = 0; - } - } - - // We need to reread the header if we got a '100 Continue' or '102 Processing' - if ( cont ) - { - goto try_again; - } - - // Do not do a keep-alive connection if the size of the - // response is not known and the response is not Chunked. - if (!m_bChunked && (m_iSize == NO_SIZE)) - m_bKeepAlive = false; - - if ( m_responseCode == 204 ) - { - return true; - } - - // We need to try to login again if we failed earlier - if ( m_bUnauthorized ) - { - if ( (m_responseCode == 401) || - (m_bUseProxy && (m_responseCode == 407)) - ) - { - if ( getAuthorization() ) - { - // for NTLM Authentication we have to keep the connection open! - if ( Authentication == AUTH_NTLM && m_strAuthorization.length() > 4 ) - { - m_bKeepAlive = true; - readBody( true ); - } - else if (ProxyAuthentication == AUTH_NTLM && m_strProxyAuthorization.length() > 4) - { - readBody( true ); - } - else - httpCloseConnection(); - return false; // Try again. - } - - if (m_bError) - return false; // Error out - - // Show error page... - } - m_bUnauthorized = false; - } - - // We need to do a redirect - if (!locationStr.isEmpty()) - { - KURL u(m_request.url, locationStr); - if(!u.isValid()) - { - error(ERR_MALFORMED_URL, u.prettyURL()); - return false; - } - if ((u.protocol() != "http") && (u.protocol() != "https") && - (u.protocol() != "ftp") && (u.protocol() != "webdav") && - (u.protocol() != "webdavs")) - { - redirection(u); - error(ERR_ACCESS_DENIED, u.prettyURL()); - return false; - } - - // preserve #ref: (bug 124654) - // if we were at http://host/resource1#ref, we sent a GET for "/resource1" - // if we got redirected to http://host/resource2, then we have to re-add - // the fragment: - if (m_request.url.hasRef() && !u.hasRef() && - (m_request.url.host() == u.host()) && - (m_request.url.protocol() == u.protocol())) - u.setRef(m_request.url.ref()); - - m_bRedirect = true; - m_redirectLocation = u; - - if (!m_request.id.isEmpty()) - { - sendMetaData(); - } - - kdDebug(7113) << "(" << m_pid << ") request.url: " << m_request.url.prettyURL() - << endl << "LocationStr: " << locationStr.data() << endl; - - kdDebug(7113) << "(" << m_pid << ") Requesting redirection to: " << u.prettyURL() - << endl; - - // If we're redirected to a http:// url, remember that we're doing webdav... - if (m_protocol == "webdav" || m_protocol == "webdavs") - u.setProtocol(m_protocol); - - redirection(u); - m_request.bCachedWrite = false; // Turn off caching on re-direction (DA) - mayCache = false; - } - - // Inform the job that we can indeed resume... - if ( bCanResume && m_request.offset ) - canResume(); - else - m_request.offset = 0; - - // We don't cache certain text objects - if (m_strMimeType.startsWith("text/") && - (m_strMimeType != "text/css") && - (m_strMimeType != "text/x-javascript") && - !hasCacheDirective) - { - // Do not cache secure pages or pages - // originating from password protected sites - // unless the webserver explicitly allows it. - if ( m_bIsSSL || (Authentication != AUTH_None) ) - { - m_request.bCachedWrite = false; - mayCache = false; - } - } - - // WABA: Correct for tgz files with a gzip-encoding. - // They really shouldn't put gzip in the Content-Encoding field! - // Web-servers really shouldn't do this: They let Content-Size refer - // to the size of the tgz file, not to the size of the tar file, - // while the Content-Type refers to "tar" instead of "tgz". - if (m_qContentEncodings.last() == "gzip") - { - if (m_strMimeType == "application/x-tar") - { - m_qContentEncodings.remove(m_qContentEncodings.fromLast()); - m_strMimeType = TQString::fromLatin1("application/x-tgz"); - } - else if (m_strMimeType == "application/postscript") - { - // LEONB: Adding another exception for psgz files. - // Could we use the mimelnk files instead of hardcoding all this? - m_qContentEncodings.remove(m_qContentEncodings.fromLast()); - m_strMimeType = TQString::fromLatin1("application/x-gzpostscript"); - } - else if ( m_request.allowCompressedPage && - m_strMimeType != "application/x-tgz" && - m_strMimeType != "application/x-targz" && - m_strMimeType != "application/x-gzip" && - m_request.url.path().right(6) == ".ps.gz" ) - { - m_qContentEncodings.remove(m_qContentEncodings.fromLast()); - m_strMimeType = TQString::fromLatin1("application/x-gzpostscript"); - } - else if ( (m_request.allowCompressedPage && - m_strMimeType == "text/html") - || - (m_request.allowCompressedPage && - m_strMimeType != "application/x-tgz" && - m_strMimeType != "application/x-targz" && - m_strMimeType != "application/x-gzip" && - m_request.url.path().right(3) != ".gz") - ) - { - // Unzip! - } - else - { - m_qContentEncodings.remove(m_qContentEncodings.fromLast()); - m_strMimeType = TQString::fromLatin1("application/x-gzip"); - } - } - - // We can't handle "bzip2" encoding (yet). So if we get something with - // bzip2 encoding, we change the mimetype to "application/x-bzip2". - // Note for future changes: some web-servers send both "bzip2" as - // encoding and "application/x-bzip2" as mimetype. That is wrong. - // currently that doesn't bother us, because we remove the encoding - // and set the mimetype to x-bzip2 anyway. - if (m_qContentEncodings.last() == "bzip2") - { - m_qContentEncodings.remove(m_qContentEncodings.fromLast()); - m_strMimeType = TQString::fromLatin1("application/x-bzip2"); - } - - // Convert some common mimetypes to standard KDE mimetypes - if (m_strMimeType == "application/x-targz") - m_strMimeType = TQString::fromLatin1("application/x-tgz"); - else if (m_strMimeType == "application/zip") - m_strMimeType = TQString::fromLatin1("application/x-zip"); - else if (m_strMimeType == "image/x-png") - m_strMimeType = TQString::fromLatin1("image/png"); - else if (m_strMimeType == "image/bmp") - m_strMimeType = TQString::fromLatin1("image/x-bmp"); - else if (m_strMimeType == "audio/mpeg" || m_strMimeType == "audio/x-mpeg" || m_strMimeType == "audio/mp3") - m_strMimeType = TQString::fromLatin1("audio/x-mp3"); - else if (m_strMimeType == "audio/microsoft-wave") - m_strMimeType = TQString::fromLatin1("audio/x-wav"); - else if (m_strMimeType == "audio/midi") - m_strMimeType = TQString::fromLatin1("audio/x-midi"); - else if (m_strMimeType == "image/x-xpixmap") - m_strMimeType = TQString::fromLatin1("image/x-xpm"); - else if (m_strMimeType == "application/rtf") - m_strMimeType = TQString::fromLatin1("text/rtf"); - - // Crypto ones.... - else if (m_strMimeType == "application/pkix-cert" || - m_strMimeType == "application/binary-certificate") - { - m_strMimeType = TQString::fromLatin1("application/x-x509-ca-cert"); - } - - // Prefer application/x-tgz or x-gzpostscript over application/x-gzip. - else if (m_strMimeType == "application/x-gzip") - { - if ((m_request.url.path().right(7) == ".tar.gz") || - (m_request.url.path().right(4) == ".tar")) - m_strMimeType = TQString::fromLatin1("application/x-tgz"); - if ((m_request.url.path().right(6) == ".ps.gz")) - m_strMimeType = TQString::fromLatin1("application/x-gzpostscript"); - } - - // Some webservers say "text/plain" when they mean "application/x-bzip2" - else if ((m_strMimeType == "text/plain") || (m_strMimeType == "application/octet-stream")) - { - TQString ext = m_request.url.path().right(4).upper(); - if (ext == ".BZ2") - m_strMimeType = TQString::fromLatin1("application/x-bzip2"); - else if (ext == ".PEM") - m_strMimeType = TQString::fromLatin1("application/x-x509-ca-cert"); - else if (ext == ".SWF") - m_strMimeType = TQString::fromLatin1("application/x-shockwave-flash"); - else if (ext == ".PLS") - m_strMimeType = TQString::fromLatin1("audio/x-scpls"); - else if (ext == ".WMV") - m_strMimeType = TQString::fromLatin1("video/x-ms-wmv"); - } - -#if 0 - // Even if we can't rely on content-length, it seems that we should - // never get more data than content-length. Maybe less, if the - // content-length refers to the unzipped data. - if (!m_qContentEncodings.isEmpty()) - { - // If we still have content encoding we can't rely on the Content-Length. - m_iSize = NO_SIZE; - } -#endif - - if( !dispositionType.isEmpty() ) - { - kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition type to: " - << dispositionType << endl; - setMetaData("content-disposition-type", dispositionType); - } - if( !dispositionFilename.isEmpty() ) - { - kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition filename to: " - << dispositionFilename << endl; - // ### KDE4: setting content-disposition to filename for pre 3.5.2 compatability - setMetaData("content-disposition", dispositionFilename); - setMetaData("content-disposition-filename", dispositionFilename); - } - - if (!m_request.lastModified.isEmpty()) - setMetaData("modified", m_request.lastModified); - - if (!mayCache) - { - setMetaData("no-cache", "true"); - setMetaData("expire-date", "1"); // Expired - } - else - { - TQString tmp; - tmp.setNum(expireDate); - setMetaData("expire-date", tmp); - tmp.setNum(time(0)); // Cache entry will be created shortly. - setMetaData("cache-creation-date", tmp); - } - - // Let the app know about the mime-type iff this is not - // a redirection and the mime-type string is not empty. - if (locationStr.isEmpty() && (!m_strMimeType.isEmpty() || - m_request.method == HTTP_HEAD)) - { - kdDebug(7113) << "(" << m_pid << ") Emitting mimetype " << m_strMimeType << endl; - mimeType( m_strMimeType ); - } - - // Do not move send response header before any redirection as it seems - // to screw up some sites. See BR# 150904. - forwardHttpResponseHeader(); - - if (m_request.method == HTTP_HEAD) - return true; - - // Do we want to cache this request? - if (m_request.bUseCache) - { - ::unlink( TQFile::encodeName(m_request.cef)); - if ( m_request.bCachedWrite && !m_strMimeType.isEmpty() ) - { - // Check... - createCacheEntry(m_strMimeType, expireDate); // Create a cache entry - if (!m_request.fcache) - { - m_request.bCachedWrite = false; // Error creating cache entry. - kdDebug(7113) << "(" << m_pid << ") Error creating cache entry for " << m_request.url.prettyURL()<<"!\n"; - } - m_request.expireDate = expireDate; - m_maxCacheSize = config()->readNumEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE) / 2; - } - } - - if (m_request.bCachedWrite && !m_strMimeType.isEmpty()) - kdDebug(7113) << "(" << m_pid << ") Cache, adding \"" << m_request.url.prettyURL() << "\"" << endl; - else if (m_request.bCachedWrite && m_strMimeType.isEmpty()) - kdDebug(7113) << "(" << m_pid << ") Cache, pending \"" << m_request.url.prettyURL() << "\"" << endl; - else - kdDebug(7113) << "(" << m_pid << ") Cache, not adding \"" << m_request.url.prettyURL() << "\"" << endl; - return true; -} - - -void HTTPProtocol::addEncoding(TQString encoding, TQStringList &encs) -{ - encoding = encoding.stripWhiteSpace().lower(); - // Identity is the same as no encoding - if (encoding == "identity") { - return; - } else if (encoding == "8bit") { - // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de - return; - } else if (encoding == "chunked") { - m_bChunked = true; - // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints? - //if ( m_cmd != CMD_COPY ) - m_iSize = NO_SIZE; - } else if ((encoding == "x-gzip") || (encoding == "gzip")) { - encs.append(TQString::fromLatin1("gzip")); - } else if ((encoding == "x-bzip2") || (encoding == "bzip2")) { - encs.append(TQString::fromLatin1("bzip2")); // Not yet supported! - } else if ((encoding == "x-deflate") || (encoding == "deflate")) { - encs.append(TQString::fromLatin1("deflate")); - } else { - kdDebug(7113) << "(" << m_pid << ") Unknown encoding encountered. " - << "Please write code. Encoding = \"" << encoding - << "\"" << endl; - } -} - -bool HTTPProtocol::sendBody() -{ - int result=-1; - int length=0; - - infoMessage( i18n( "Requesting data to send" ) ); - - // m_bufPOST will NOT be empty iff authentication was required before posting - // the data OR a re-connect is requested from ::readHeader because the - // connection was lost for some reason. - if ( !m_bufPOST.isNull() ) - { - kdDebug(7113) << "(" << m_pid << ") POST'ing saved data..." << endl; - - result = 0; - length = m_bufPOST.size(); - } - else - { - kdDebug(7113) << "(" << m_pid << ") POST'ing live data..." << endl; - - TQByteArray buffer; - int old_size; - - m_bufPOST.resize(0); - do - { - dataReq(); // Request for data - result = readData( buffer ); - if ( result > 0 ) - { - length += result; - old_size = m_bufPOST.size(); - m_bufPOST.resize( old_size+result ); - memcpy( m_bufPOST.data()+ old_size, buffer.data(), buffer.size() ); - buffer.resize(0); - } - } while ( result > 0 ); - } - - if ( result < 0 ) - { - error( ERR_ABORTED, m_request.hostname ); - return false; - } - - infoMessage( i18n( "Sending data to %1" ).arg( m_request.hostname ) ); - - TQString size = TQString ("Content-Length: %1\r\n\r\n").arg(length); - kdDebug( 7113 ) << "(" << m_pid << ")" << size << endl; - - // Send the content length... - bool sendOk = (write(size.latin1(), size.length()) == (ssize_t) size.length()); - if (!sendOk) - { - kdDebug( 7113 ) << "(" << m_pid << ") Connection broken when sending " - << "content length: (" << m_state.hostname << ")" << endl; - error( ERR_CONNECTION_BROKEN, m_state.hostname ); - return false; - } - - // Send the data... - // kdDebug( 7113 ) << "(" << m_pid << ") POST DATA: " << TQCString(m_bufPOST) << endl; - sendOk = (write(m_bufPOST.data(), m_bufPOST.size()) == (ssize_t) m_bufPOST.size()); - if (!sendOk) - { - kdDebug(7113) << "(" << m_pid << ") Connection broken when sending message body: (" - << m_state.hostname << ")" << endl; - error( ERR_CONNECTION_BROKEN, m_state.hostname ); - return false; - } - - return true; -} - -void HTTPProtocol::httpClose( bool keepAlive ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose" << endl; - - if (m_request.fcache) - { - fclose(m_request.fcache); - m_request.fcache = 0; - if (m_request.bCachedWrite) - { - TQString filename = m_request.cef + ".new"; - ::unlink( TQFile::encodeName(filename) ); - } - } - - // Only allow persistent connections for GET requests. - // NOTE: we might even want to narrow this down to non-form - // based submit requests which will require a meta-data from - // tdehtml. - if (keepAlive && (!m_bUseProxy || - m_bPersistentProxyConnection || m_bIsTunneled)) - { - if (!m_keepAliveTimeout) - m_keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT; - else if (m_keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT) - m_keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT; - - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose: keep alive (" << m_keepAliveTimeout << ")" << endl; - TQByteArray data; - TQDataStream stream( data, IO_WriteOnly ); - stream << int(99); // special: Close connection - setTimeoutSpecialCommand(m_keepAliveTimeout, data); - return; - } - - httpCloseConnection(); -} - -void HTTPProtocol::closeConnection() -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::closeConnection" << endl; - httpCloseConnection (); -} - -void HTTPProtocol::httpCloseConnection () -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCloseConnection" << endl; - m_bIsTunneled = false; - m_bKeepAlive = false; - closeDescriptor(); - setTimeoutSpecialCommand(-1); // Cancel any connection timeout -} - -void HTTPProtocol::slave_status() -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::slave_status" << endl; - - if ( m_iSock != -1 && !isConnectionValid() ) - httpCloseConnection(); - - slaveStatus( m_state.hostname, (m_iSock != -1) ); -} - -void HTTPProtocol::mimetype( const KURL& url ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mimetype: " - << url.prettyURL() << endl; - - if ( !checkRequestURL( url ) ) - return; - - m_request.method = HTTP_HEAD; - m_request.path = url.path(); - m_request.query = url.query(); - m_request.cache = CC_Cache; - m_request.doProxy = m_bUseProxy; - - retrieveHeader(); - - kdDebug(7113) << "(" << m_pid << ") http: mimetype = " << m_strMimeType - << endl; -} - -void HTTPProtocol::special( const TQByteArray &data ) -{ - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::special" << endl; - - int tmp; - TQDataStream stream(data, IO_ReadOnly); - - stream >> tmp; - switch (tmp) { - case 1: // HTTP POST - { - KURL url; - stream >> url; - post( url ); - break; - } - case 2: // cache_update - { - KURL url; - bool no_cache; - time_t expireDate; - stream >> url >> no_cache >> expireDate; - cacheUpdate( url, no_cache, expireDate ); - break; - } - case 5: // WebDAV lock - { - KURL url; - TQString scope, type, owner; - stream >> url >> scope >> type >> owner; - davLock( url, scope, type, owner ); - break; - } - case 6: // WebDAV unlock - { - KURL url; - stream >> url; - davUnlock( url ); - break; - } - case 7: // Generic WebDAV - { - KURL url; - int method; - stream >> url >> method; - davGeneric( url, (TDEIO::HTTP_METHOD) method ); - break; - } - case 99: // Close Connection - { - httpCloseConnection(); - break; - } - default: - // Some command we don't understand. - // Just ignore it, it may come from some future version of KDE. - break; - } -} - -/** - * Read a chunk from the data stream. - */ -int HTTPProtocol::readChunked() -{ - if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE)) - { - setRewindMarker(); - - m_bufReceive.resize(4096); - - if (!gets(m_bufReceive.data(), m_bufReceive.size()-1)) - { - kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl; - return -1; - } - // We could have got the CRLF of the previous chunk. - // If so, try again. - if (m_bufReceive[0] == '\0') - { - if (!gets(m_bufReceive.data(), m_bufReceive.size()-1)) - { - kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl; - return -1; - } - } - - // m_bEOF is set to true when read called from gets returns 0. For chunked reading 0 - // means end of chunked transfer and not error. See RFC 2615 section 3.6.1 - #if 0 - if (m_bEOF) - { - kdDebug(7113) << "(" << m_pid << ") EOF on Chunk header" << endl; - return -1; - } - #endif - - long long trunkSize = STRTOLL(m_bufReceive.data(), 0, 16); - if (trunkSize < 0) - { - kdDebug(7113) << "(" << m_pid << ") Negative chunk size" << endl; - return -1; - } - m_iBytesLeft = trunkSize; - - // kdDebug(7113) << "(" << m_pid << ") Chunk size = " << m_iBytesLeft << " bytes" << endl; - - if (m_iBytesLeft == 0) - { - // Last chunk. - // Skip trailers. - do { - // Skip trailer of last chunk. - if (!gets(m_bufReceive.data(), m_bufReceive.size()-1)) - { - kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk trailer" << endl; - return -1; - } - // kdDebug(7113) << "(" << m_pid << ") Chunk trailer = \"" << m_bufReceive.data() << "\"" << endl; - } - while (strlen(m_bufReceive.data()) != 0); - - return 0; - } - } - - int bytesReceived = readLimited(); - if (!m_iBytesLeft) - m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk - - // kdDebug(7113) << "(" << m_pid << ") readChunked: BytesReceived=" << bytesReceived << endl; - return bytesReceived; -} - -int HTTPProtocol::readLimited() -{ - if (!m_iBytesLeft) - return 0; - - m_bufReceive.resize(4096); - - int bytesReceived; - int bytesToReceive; - - if (m_iBytesLeft > m_bufReceive.size()) - bytesToReceive = m_bufReceive.size(); - else - bytesToReceive = m_iBytesLeft; - - bytesReceived = read(m_bufReceive.data(), bytesToReceive); - - if (bytesReceived <= 0) - return -1; // Error: connection lost - - m_iBytesLeft -= bytesReceived; - return bytesReceived; -} - -int HTTPProtocol::readUnlimited() -{ - if (m_bKeepAlive) - { - kdDebug(7113) << "(" << m_pid << ") Unbounded datastream on a Keep " - << "alive connection!" << endl; - m_bKeepAlive = false; - } - - m_bufReceive.resize(4096); - - int result = read(m_bufReceive.data(), m_bufReceive.size()); - if (result > 0) - return result; - - m_bEOF = true; - m_iBytesLeft = 0; - return 0; -} - -void HTTPProtocol::slotData(const TQByteArray &_d) -{ - if (!_d.size()) - { - m_bEOD = true; - return; - } - - if (m_iContentLeft != NO_SIZE) - { - if (m_iContentLeft >= _d.size()) - m_iContentLeft -= _d.size(); - else - m_iContentLeft = NO_SIZE; - } - - TQByteArray d = _d; - if ( !m_dataInternal ) - { - // If a broken server does not send the mime-type, - // we try to id it from the content before dealing - // with the content itself. - if ( m_strMimeType.isEmpty() && !m_bRedirect && - !( m_responseCode >= 300 && m_responseCode <=399) ) - { - kdDebug(7113) << "(" << m_pid << ") Determining mime-type from content..." << endl; - int old_size = m_mimeTypeBuffer.size(); - m_mimeTypeBuffer.resize( old_size + d.size() ); - memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() ); - if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0) - && (m_mimeTypeBuffer.size() < 1024) ) - { - m_cpMimeBuffer = true; - return; // Do not send up the data since we do not yet know its mimetype! - } - - kdDebug(7113) << "(" << m_pid << ") Mimetype buffer size: " << m_mimeTypeBuffer.size() - << endl; - - KMimeMagicResult *result; - result = KMimeMagic::self()->findBufferFileType( m_mimeTypeBuffer, - m_request.url.fileName() ); - if( result ) - { - m_strMimeType = result->mimeType(); - kdDebug(7113) << "(" << m_pid << ") Mimetype from content: " - << m_strMimeType << endl; - } - - if ( m_strMimeType.isEmpty() ) - { - m_strMimeType = TQString::fromLatin1( DEFAULT_MIME_TYPE ); - kdDebug(7113) << "(" << m_pid << ") Using default mimetype: " - << m_strMimeType << endl; - } - - if ( m_request.bCachedWrite ) - { - createCacheEntry( m_strMimeType, m_request.expireDate ); - if (!m_request.fcache) - m_request.bCachedWrite = false; - } - - if ( m_cpMimeBuffer ) - { - // Do not make any assumption about the state of the TQByteArray we received. - // Fix the crash described by BR# 130104. - d.detach(); - d.resize(0); - d.resize(m_mimeTypeBuffer.size()); - memcpy( d.data(), m_mimeTypeBuffer.data(), - d.size() ); - } - mimeType(m_strMimeType); - m_mimeTypeBuffer.resize(0); - } - - data( d ); - if (m_request.bCachedWrite && m_request.fcache) - writeCacheEntry(d.data(), d.size()); - } - else - { - uint old_size = m_bufWebDavData.size(); - m_bufWebDavData.resize (old_size + d.size()); - memcpy (m_bufWebDavData.data() + old_size, d.data(), d.size()); - } -} - -/** - * This function is our "receive" function. It is responsible for - * downloading the message (not the header) from the HTTP server. It - * is called either as a response to a client's TDEIOJob::dataEnd() - * (meaning that the client is done sending data) or by 'httpOpen()' - * (if we are in the process of a PUT/POST request). It can also be - * called by a webDAV function, to receive stat/list/property/etc. - * data; in this case the data is stored in m_bufWebDavData. - */ -bool HTTPProtocol::readBody( bool dataInternal /* = false */ ) -{ - if (m_responseCode == 204) - return true; - - m_bEOD = false; - // Note that when dataInternal is true, we are going to: - // 1) save the body data to a member variable, m_bufWebDavData - // 2) _not_ advertise the data, speed, size, etc., through the - // corresponding functions. - // This is used for returning data to WebDAV. - m_dataInternal = dataInternal; - if ( dataInternal ) - m_bufWebDavData.resize (0); - - // Check if we need to decode the data. - // If we are in copy mode, then use only transfer decoding. - bool useMD5 = !m_sContentMD5.isEmpty(); - - // Deal with the size of the file. - TDEIO::filesize_t sz = m_request.offset; - if ( sz ) - m_iSize += sz; - - // Update the application with total size except when - // it is compressed, or when the data is to be handled - // internally (webDAV). If compressed we have to wait - // until we uncompress to find out the actual data size - if ( !dataInternal ) { - if ( (m_iSize > 0) && (m_iSize != NO_SIZE)) { - totalSize(m_iSize); - infoMessage( i18n( "Retrieving %1 from %2...").arg(TDEIO::convertSize(m_iSize)) - .arg( m_request.hostname ) ); - } - else - { - totalSize ( 0 ); - } - } - else - infoMessage( i18n( "Retrieving from %1..." ).arg( m_request.hostname ) ); - - if (m_request.bCachedRead) - { - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: read data from cache!" << endl; - m_request.bCachedWrite = false; - - char buffer[ MAX_IPC_SIZE ]; - - m_iContentLeft = NO_SIZE; - - // Jippie! It's already in the cache :-) - while (!feof(m_request.fcache) && !ferror(m_request.fcache)) - { - int nbytes = fread( buffer, 1, MAX_IPC_SIZE, m_request.fcache); - - if (nbytes > 0) - { - m_bufReceive.setRawData( buffer, nbytes); - slotData( m_bufReceive ); - m_bufReceive.resetRawData( buffer, nbytes ); - sz += nbytes; - } - } - - m_bufReceive.resize( 0 ); - - if ( !dataInternal ) - { - processedSize( sz ); - data( TQByteArray() ); - } - - return true; - } - - - if (m_iSize != NO_SIZE) - m_iBytesLeft = m_iSize - sz; - else - m_iBytesLeft = NO_SIZE; - - m_iContentLeft = m_iBytesLeft; - - if (m_bChunked) - m_iBytesLeft = NO_SIZE; - - kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: retrieve data. " - << TDEIO::number(m_iBytesLeft) << " left." << endl; - - // Main incoming loop... Gather everything while we can... - m_cpMimeBuffer = false; - m_mimeTypeBuffer.resize(0); - struct timeval last_tv; - gettimeofday( &last_tv, 0L ); - - HTTPFilterChain chain; - - TQObject::connect(&chain, TQT_SIGNAL(output(const TQByteArray &)), - this, TQT_SLOT(slotData(const TQByteArray &))); - TQObject::connect(&chain, TQT_SIGNAL(error(int, const TQString &)), - this, TQT_SLOT(error(int, const TQString &))); - - // decode all of the transfer encodings - while (!m_qTransferEncodings.isEmpty()) - { - TQString enc = m_qTransferEncodings.last(); - m_qTransferEncodings.remove(m_qTransferEncodings.fromLast()); - if ( enc == "gzip" ) - chain.addFilter(new HTTPFilterGZip); - else if ( enc == "deflate" ) - chain.addFilter(new HTTPFilterDeflate); - } - - // From HTTP 1.1 Draft 6: - // The MD5 digest is computed based on the content of the entity-body, - // including any content-coding that has been applied, but not including - // any transfer-encoding applied to the message-body. If the message is - // received with a transfer-encoding, that encoding MUST be removed - // prior to checking the Content-MD5 value against the received entity. - HTTPFilterMD5 *md5Filter = 0; - if ( useMD5 ) - { - md5Filter = new HTTPFilterMD5; - chain.addFilter(md5Filter); - } - - // now decode all of the content encodings - // -- Why ?? We are not - // -- a proxy server, be a client side implementation!! The applications - // -- are capable of determinig how to extract the encoded implementation. - // WB: That's a misunderstanding. We are free to remove the encoding. - // WB: Some braindead www-servers however, give .tgz files an encoding - // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar" - // WB: They shouldn't do that. We can work around that though... - while (!m_qContentEncodings.isEmpty()) - { - TQString enc = m_qContentEncodings.last(); - m_qContentEncodings.remove(m_qContentEncodings.fromLast()); - if ( enc == "gzip" ) - chain.addFilter(new HTTPFilterGZip); - else if ( enc == "deflate" ) - chain.addFilter(new HTTPFilterDeflate); - } - - while (!m_bEOF) - { - int bytesReceived; - - if (m_bChunked) - bytesReceived = readChunked(); - else if (m_iSize != NO_SIZE) - bytesReceived = readLimited(); - else - bytesReceived = readUnlimited(); - - // make sure that this wasn't an error, first - // kdDebug(7113) << "(" << (int) m_pid << ") readBody: bytesReceived: " - // << (int) bytesReceived << " m_iSize: " << (int) m_iSize << " Chunked: " - // << (int) m_bChunked << " BytesLeft: "<< (int) m_iBytesLeft << endl; - if (bytesReceived == -1) - { - if (m_iContentLeft == 0) - { - // gzip'ed data sometimes reports a too long content-length. - // (The length of the unzipped data) - m_iBytesLeft = 0; - break; - } - // Oh well... log an error and bug out - kdDebug(7113) << "(" << m_pid << ") readBody: bytesReceived==-1 sz=" << (int)sz - << " Connnection broken !" << endl; - error(ERR_CONNECTION_BROKEN, m_state.hostname); - return false; - } - - // I guess that nbytes == 0 isn't an error.. but we certainly - // won't work with it! - if (bytesReceived > 0) - { - // Important: truncate the buffer to the actual size received! - // Otherwise garbage will be passed to the app - m_bufReceive.truncate( bytesReceived ); - - chain.slotInput(m_bufReceive); - - if (m_bError) - return false; - - sz += bytesReceived; - if (!dataInternal) - processedSize( sz ); - } - m_bufReceive.resize(0); // res - - if (m_iBytesLeft && m_bEOD && !m_bChunked) - { - // gzip'ed data sometimes reports a too long content-length. - // (The length of the unzipped data) - m_iBytesLeft = 0; - } - - if (m_iBytesLeft == 0) - { - kdDebug(7113) << "("<call( "kded", "kcookiejar", "findCookies(TQString,long int)", - params, replyType, reply ) ) - { - kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl; - return result; - } - if ( replyType == "TQString" ) - { - TQDataStream stream2( reply, IO_ReadOnly ); - stream2 >> result; - } - else - { - kdError(7113) << "(" << m_pid << ") DCOP function findCookies(...) returns " - << replyType << ", expected TQString" << endl; - } - return result; -} - -/******************************* CACHING CODE ****************************/ - - -void HTTPProtocol::cacheUpdate( const KURL& url, bool no_cache, time_t expireDate) -{ - if ( !checkRequestURL( url ) ) - return; - - m_request.path = url.path(); - m_request.query = url.query(); - m_request.cache = CC_Reload; - m_request.doProxy = m_bUseProxy; - - if (no_cache) - { - m_request.fcache = checkCacheEntry( ); - if (m_request.fcache) - { - fclose(m_request.fcache); - m_request.fcache = 0; - ::unlink( TQFile::encodeName(m_request.cef) ); - } - } - else - { - updateExpireDate( expireDate ); - } - finished(); -} - -// !START SYNC! -// The following code should be kept in sync -// with the code in http_cache_cleaner.cpp - -FILE* HTTPProtocol::checkCacheEntry( bool readWrite) -{ - const TQChar separator = '_'; - - TQString CEF = m_request.path; - - int p = CEF.find('/'); - - while(p != -1) - { - CEF[p] = separator; - p = CEF.find('/', p); - } - - TQString host = m_request.hostname.lower(); - CEF = host + CEF + '_'; - - TQString dir = m_strCacheDir; - if (dir[dir.length()-1] != '/') - dir += "/"; - - int l = host.length(); - for(int i = 0; i < l; i++) - { - if (host[i].isLetter() && (host[i] != 'w')) - { - dir += host[i]; - break; - } - } - if (dir[dir.length()-1] == '/') - dir += "0"; - - unsigned long hash = 0x00000000; - TQCString u = m_request.url.url().latin1(); - for(int i = u.length(); i--;) - { - hash = (hash * 12211 + static_cast(u.at(i))) % 2147483563; - } - - TQString hashString; - hashString.sprintf("%08lx", hash); - - CEF = CEF + hashString; - - CEF = dir + "/" + CEF; - - m_request.cef = CEF; - - const char *mode = (readWrite ? "r+" : "r"); - - FILE *fs = fopen( TQFile::encodeName(CEF), mode); // Open for reading and writing - if (!fs) - return 0; - - char buffer[401]; - bool ok = true; - - // CacheRevision - if (ok && (!fgets(buffer, 400, fs))) - ok = false; - if (ok && (strcmp(buffer, CACHE_REVISION) != 0)) - ok = false; - - time_t date; - time_t currentDate = time(0); - - // URL - if (ok && (!fgets(buffer, 400, fs))) - ok = false; - if (ok) - { - int l = strlen(buffer); - if (l>0) - buffer[l-1] = 0; // Strip newline - if (m_request.url.url() != buffer) - { - ok = false; // Hash collision - } - } - - // Creation Date - if (ok && (!fgets(buffer, 400, fs))) - ok = false; - if (ok) - { - date = (time_t) strtoul(buffer, 0, 10); - m_request.creationDate = date; - if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge)) - { - m_request.bMustRevalidate = true; - m_request.expireDate = currentDate; - } - } - - // Expiration Date - m_request.cacheExpireDateOffset = ftell(fs); - if (ok && (!fgets(buffer, 400, fs))) - ok = false; - if (ok) - { - if (m_request.cache == CC_Verify) - { - date = (time_t) strtoul(buffer, 0, 10); - // After the expire date we need to revalidate. - if (!date || difftime(currentDate, date) >= 0) - m_request.bMustRevalidate = true; - m_request.expireDate = date; - } - else if (m_request.cache == CC_Refresh) - { - m_request.bMustRevalidate = true; - m_request.expireDate = currentDate; - } - } - - // ETag - if (ok && (!fgets(buffer, 400, fs))) - ok = false; - if (ok) - { - m_request.etag = TQString(buffer).stripWhiteSpace(); - } - - // Last-Modified - if (ok && (!fgets(buffer, 400, fs))) - ok = false; - if (ok) - { - m_request.lastModified = TQString(buffer).stripWhiteSpace(); - } - - if (ok) - return fs; - - fclose(fs); - unlink( TQFile::encodeName(CEF)); - return 0; -} - -void HTTPProtocol::updateExpireDate(time_t expireDate, bool updateCreationDate) -{ - bool ok = true; - - FILE *fs = checkCacheEntry(true); - if (fs) - { - TQString date; - char buffer[401]; - time_t creationDate; - - fseek(fs, 0, SEEK_SET); - if (ok && !fgets(buffer, 400, fs)) - ok = false; - if (ok && !fgets(buffer, 400, fs)) - ok = false; - long cacheCreationDateOffset = ftell(fs); - if (ok && !fgets(buffer, 400, fs)) - ok = false; - creationDate = strtoul(buffer, 0, 10); - if (!creationDate) - ok = false; - - if (updateCreationDate) - { - if (!ok || fseek(fs, cacheCreationDateOffset, SEEK_SET)) - return; - TQString date; - date.setNum( time(0) ); - date = date.leftJustify(16); - fputs(date.latin1(), fs); // Creation date - fputc('\n', fs); - } - - if (expireDate>(30*365*24*60*60)) - { - // expire date is a really a big number, it can't be - // a relative date. - date.setNum( expireDate ); - } - else - { - // expireDate before 2000. those values must be - // interpreted as relative expiration dates from - // tags. - // so we have to scan the creation time and add - // it to the expiryDate - date.setNum( creationDate + expireDate ); - } - date = date.leftJustify(16); - if (!ok || fseek(fs, m_request.cacheExpireDateOffset, SEEK_SET)) - return; - fputs(date.latin1(), fs); // Expire date - fseek(fs, 0, SEEK_END); - fclose(fs); - } -} - -void HTTPProtocol::createCacheEntry( const TQString &mimetype, time_t expireDate) -{ - TQString dir = m_request.cef; - int p = dir.findRev('/'); - if (p == -1) return; // Error. - dir.truncate(p); - - // Create file - (void) ::mkdir( TQFile::encodeName(dir), 0700 ); - - TQString filename = m_request.cef + ".new"; // Create a new cache entryexpireDate - -// kdDebug( 7103 ) << "creating new cache entry: " << filename << endl; - - m_request.fcache = fopen( TQFile::encodeName(filename), "w"); - if (!m_request.fcache) - { - kdWarning(7113) << "(" << m_pid << ")createCacheEntry: opening " << filename << " failed." << endl; - return; // Error. - } - - fputs(CACHE_REVISION, m_request.fcache); // Revision - - fputs(m_request.url.url().latin1(), m_request.fcache); // Url - fputc('\n', m_request.fcache); - - TQString date; - m_request.creationDate = time(0); - date.setNum( m_request.creationDate ); - date = date.leftJustify(16); - fputs(date.latin1(), m_request.fcache); // Creation date - fputc('\n', m_request.fcache); - - date.setNum( expireDate ); - date = date.leftJustify(16); - fputs(date.latin1(), m_request.fcache); // Expire date - fputc('\n', m_request.fcache); - - if (!m_request.etag.isEmpty()) - fputs(m_request.etag.latin1(), m_request.fcache); //ETag - fputc('\n', m_request.fcache); - - if (!m_request.lastModified.isEmpty()) - fputs(m_request.lastModified.latin1(), m_request.fcache); // Last modified - fputc('\n', m_request.fcache); - - fputs(mimetype.latin1(), m_request.fcache); // Mimetype - fputc('\n', m_request.fcache); - - if (!m_request.strCharset.isEmpty()) - fputs(m_request.strCharset.latin1(), m_request.fcache); // Charset - fputc('\n', m_request.fcache); - - return; -} -// The above code should be kept in sync -// with the code in http_cache_cleaner.cpp -// !END SYNC! - -void HTTPProtocol::writeCacheEntry( const char *buffer, int nbytes) -{ - if (fwrite( buffer, nbytes, 1, m_request.fcache) != 1) - { - kdWarning(7113) << "(" << m_pid << ") writeCacheEntry: writing " << nbytes << " bytes failed." << endl; - fclose(m_request.fcache); - m_request.fcache = 0; - TQString filename = m_request.cef + ".new"; - ::unlink( TQFile::encodeName(filename) ); - return; - } - long file_pos = ftell( m_request.fcache ) / 1024; - if ( file_pos > m_maxCacheSize ) - { - kdDebug(7113) << "writeCacheEntry: File size reaches " << file_pos - << "Kb, exceeds cache limits. (" << m_maxCacheSize << "Kb)" << endl; - fclose(m_request.fcache); - m_request.fcache = 0; - TQString filename = m_request.cef + ".new"; - ::unlink( TQFile::encodeName(filename) ); - return; - } -} - -void HTTPProtocol::closeCacheEntry() -{ - TQString filename = m_request.cef + ".new"; - int result = fclose( m_request.fcache); - m_request.fcache = 0; - if (result == 0) - { - if (::rename( TQFile::encodeName(filename), TQFile::encodeName(m_request.cef)) == 0) - return; // Success - - kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error renaming " - << "cache entry. (" << filename << " -> " << m_request.cef - << ")" << endl; - } - - kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error closing cache " - << "entry. (" << filename<< ")" << endl; -} - -void HTTPProtocol::cleanCache() -{ - const time_t maxAge = DEFAULT_CLEAN_CACHE_INTERVAL; // 30 Minutes. - bool doClean = false; - TQString cleanFile = m_strCacheDir; - if (cleanFile[cleanFile.length()-1] != '/') - cleanFile += "/"; - cleanFile += "cleaned"; - - struct stat stat_buf; - - int result = ::stat(TQFile::encodeName(cleanFile), &stat_buf); - if (result == -1) - { - int fd = creat( TQFile::encodeName(cleanFile), 0600); - if (fd != -1) - { - doClean = true; - ::close(fd); - } - } - else - { - time_t age = (time_t) difftime( time(0), stat_buf.st_mtime ); - if (age > maxAge) // - doClean = true; - } - if (doClean) - { - // Touch file. - utime(TQFile::encodeName(cleanFile), 0); - TDEApplication::startServiceByDesktopPath("http_cache_cleaner.desktop"); - } -} - - - -//************************** AUTHENTICATION CODE ********************/ - - -void HTTPProtocol::configAuth( char *p, bool isForProxy ) -{ - HTTP_AUTH f = AUTH_None; - const char *strAuth = p; - - if ( strncasecmp( p, "Basic", 5 ) == 0 ) - { - f = AUTH_Basic; - p += 5; - strAuth = "Basic"; // Correct for upper-case variations. - } - else if ( strncasecmp (p, "Digest", 6) == 0 ) - { - f = AUTH_Digest; - memcpy((void *)p, "Digest", 6); // Correct for upper-case variations. - p += 6; - } - else if (strncasecmp( p, "MBS_PWD_COOKIE", 14 ) == 0) - { - // Found on http://www.webscription.net/baen/default.asp - f = AUTH_Basic; - p += 14; - strAuth = "Basic"; - } -#ifdef HAVE_LIBGSSAPI - else if ( strncasecmp( p, "Negotiate", 9 ) == 0 ) - { - // if we get two 401 in a row let's assume for now that - // Negotiate isn't working and ignore it - if ( !isForProxy && !(m_responseCode == 401 && m_prevResponseCode == 401) ) - { - f = AUTH_Negotiate; - memcpy((void *)p, "Negotiate", 9); // Correct for upper-case variations. - p += 9; - }; - } -#endif - else if ( strncasecmp( p, "NTLM", 4 ) == 0 ) - { - f = AUTH_NTLM; - memcpy((void *)p, "NTLM", 4); // Correct for upper-case variations. - p += 4; - m_strRealm = "NTLM"; // set a dummy realm - } - else - { - kdWarning(7113) << "(" << m_pid << ") Unsupported or invalid authorization " - << "type requested" << endl; - if (isForProxy) - kdWarning(7113) << "(" << m_pid << ") Proxy URL: " << m_proxyURL << endl; - else - kdWarning(7113) << "(" << m_pid << ") URL: " << m_request.url << endl; - kdWarning(7113) << "(" << m_pid << ") Request Authorization: " << p << endl; - } - - /* - This check ensures the following: - 1.) Rejection of any unknown/unsupported authentication schemes - 2.) Usage of the strongest possible authentication schemes if - and when multiple Proxy-Authenticate or WWW-Authenticate - header field is sent. - */ - if (isForProxy) - { - if ((f == AUTH_None) || - ((m_iProxyAuthCount > 0) && (f < ProxyAuthentication))) - { - // Since I purposefully made the Proxy-Authentication settings - // persistent to reduce the number of round-trips to tdesud we - // have to take special care when an unknown/unsupported auth- - // scheme is received. This check accomplishes just that... - if ( m_iProxyAuthCount == 0) - ProxyAuthentication = f; - kdDebug(7113) << "(" << m_pid << ") Rejected proxy auth method: " << f << endl; - return; - } - m_iProxyAuthCount++; - kdDebug(7113) << "(" << m_pid << ") Accepted proxy auth method: " << f << endl; - } - else - { - if ((f == AUTH_None) || - ((m_iWWWAuthCount > 0) && (f < Authentication))) - { - kdDebug(7113) << "(" << m_pid << ") Rejected auth method: " << f << endl; - return; - } - m_iWWWAuthCount++; - kdDebug(7113) << "(" << m_pid << ") Accepted auth method: " << f << endl; - } - - - while (*p) - { - int i = 0; - while( (*p == ' ') || (*p == ',') || (*p == '\t') ) { p++; } - if ( strncasecmp( p, "realm=", 6 ) == 0 ) - { - //for sites like lib.homelinux.org - TQTextCodec* oldCodec=TQTextCodec::codecForCStrings(); - if (TDEGlobal::locale()->language().contains("ru")) - TQTextCodec::setCodecForCStrings(TQTextCodec::codecForName("CP1251")); - - p += 6; - if (*p == '"') p++; - while( p[i] && p[i] != '"' ) i++; - if( isForProxy ) - m_strProxyRealm = TQString::fromAscii( p, i ); - else - m_strRealm = TQString::fromAscii( p, i ); - - TQTextCodec::setCodecForCStrings(oldCodec); - - if (!p[i]) break; - } - p+=(i+1); - } - - if( isForProxy ) - { - ProxyAuthentication = f; - m_strProxyAuthorization = TQString::fromLatin1( strAuth ); - } - else - { - Authentication = f; - m_strAuthorization = TQString::fromLatin1( strAuth ); - } -} - - -bool HTTPProtocol::retryPrompt() -{ - TQString prompt; - switch ( m_responseCode ) - { - case 401: - prompt = i18n("Authentication Failed."); - break; - case 407: - prompt = i18n("Proxy Authentication Failed."); - break; - default: - break; - } - prompt += i18n(" Do you want to retry?"); - return (messageBox(QuestionYesNo, prompt, i18n("Authentication")) == 3); -} - -void HTTPProtocol::promptInfo( AuthInfo& info ) -{ - if ( m_responseCode == 401 ) - { - info.url = m_request.url; - if ( !m_state.user.isEmpty() ) - info.username = m_state.user; - info.readOnly = !m_request.url.user().isEmpty(); - info.prompt = i18n( "You need to supply a username and a " - "password to access this site." ); - info.keepPassword = true; // Prompt the user for persistence as well. - if ( !m_strRealm.isEmpty() ) - { - info.realmValue = m_strRealm; - info.verifyPath = false; - info.digestInfo = m_strAuthorization; - info.commentLabel = i18n( "Site:" ); - info.comment = i18n("%1 at %2").arg( htmlEscape(m_strRealm) ).arg( m_request.hostname ); - } - } - else if ( m_responseCode == 407 ) - { - info.url = m_proxyURL; - info.username = m_proxyURL.user(); - info.prompt = i18n( "You need to supply a username and a password for " - "the proxy server listed below before you are allowed " - "to access any sites." ); - info.keepPassword = true; - if ( !m_strProxyRealm.isEmpty() ) - { - info.realmValue = m_strProxyRealm; - info.verifyPath = false; - info.digestInfo = m_strProxyAuthorization; - info.commentLabel = i18n( "Proxy:" ); - info.comment = i18n("%1 at %2").arg( htmlEscape(m_strProxyRealm) ).arg( m_proxyURL.host() ); - } - } -} - -bool HTTPProtocol::getAuthorization() -{ - AuthInfo info; - bool result = false; - - kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::getAuthorization: " - << "Current Response: " << m_responseCode << ", " - << "Previous Response: " << m_prevResponseCode << ", " - << "Authentication: " << Authentication << ", " - << "ProxyAuthentication: " << ProxyAuthentication << endl; - - if (m_request.bNoAuth) - { - if (m_request.bErrorPage) - errorPage(); - else - error( ERR_COULD_NOT_LOGIN, i18n("Authentication needed for %1 but authentication is disabled.").arg(m_request.hostname)); - return false; - } - - bool repeatFailure = (m_prevResponseCode == m_responseCode); - - TQString errorMsg; - - if (repeatFailure) - { - bool prompt = true; - if ( Authentication == AUTH_Digest || ProxyAuthentication == AUTH_Digest ) - { - bool isStaleNonce = false; - TQString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization; - int pos = auth.find("stale", 0, false); - if ( pos != -1 ) - { - pos += 5; - int len = auth.length(); - while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++; - if ( pos < len && auth.find("true", pos, false) != -1 ) - { - isStaleNonce = true; - kdDebug(7113) << "(" << m_pid << ") Stale nonce value. " - << "Will retry using same info..." << endl; - } - } - if ( isStaleNonce ) - { - prompt = false; - result = true; - if ( m_responseCode == 401 ) - { - info.username = m_request.user; - info.password = m_request.passwd; - info.realmValue = m_strRealm; - info.digestInfo = m_strAuthorization; - } - else if ( m_responseCode == 407 ) - { - info.username = m_proxyURL.user(); - info.password = m_proxyURL.pass(); - info.realmValue = m_strProxyRealm; - info.digestInfo = m_strProxyAuthorization; - } - } - } - - if ( Authentication == AUTH_NTLM || ProxyAuthentication == AUTH_NTLM ) - { - TQString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization; - kdDebug(7113) << "auth: " << auth << endl; - if ( auth.length() > 4 ) - { - prompt = false; - result = true; - kdDebug(7113) << "(" << m_pid << ") NTLM auth second phase, " - << "sending response..." << endl; - if ( m_responseCode == 401 ) - { - info.username = m_request.user; - info.password = m_request.passwd; - info.realmValue = m_strRealm; - info.digestInfo = m_strAuthorization; - } - else if ( m_responseCode == 407 ) - { - info.username = m_proxyURL.user(); - info.password = m_proxyURL.pass(); - info.realmValue = m_strProxyRealm; - info.digestInfo = m_strProxyAuthorization; - } - } - } - - if ( prompt ) - { - switch ( m_responseCode ) - { - case 401: - errorMsg = i18n("Authentication Failed."); - break; - case 407: - errorMsg = i18n("Proxy Authentication Failed."); - break; - default: - break; - } - } - } - else - { - // At this point we know more details, so use it to find - // out if we have a cached version and avoid a re-prompt! - // We also do not use verify path unlike the pre-emptive - // requests because we already know the realm value... - - if (m_bProxyAuthValid) - { - // Reset cached proxy auth - m_bProxyAuthValid = false; - KURL proxy ( config()->readEntry("UseProxy") ); - m_proxyURL.setUser(proxy.user()); - m_proxyURL.setPass(proxy.pass()); - } - - info.verifyPath = false; - if ( m_responseCode == 407 ) - { - info.url = m_proxyURL; - info.username = m_proxyURL.user(); - info.password = m_proxyURL.pass(); - info.realmValue = m_strProxyRealm; - info.digestInfo = m_strProxyAuthorization; - } - else - { - info.url = m_request.url; - info.username = m_request.user; - info.password = m_request.passwd; - info.realmValue = m_strRealm; - info.digestInfo = m_strAuthorization; - } - - // If either username or password is not supplied - // with the request, check the password cache. - if ( info.username.isNull() || - info.password.isNull() ) - result = checkCachedAuthentication( info ); - - if ( Authentication == AUTH_Digest ) - { - TQString auth; - - if (m_responseCode == 401) - auth = m_strAuthorization; - else - auth = m_strProxyAuthorization; - - int pos = auth.find("stale", 0, false); - if ( pos != -1 ) - { - pos += 5; - int len = auth.length(); - while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++; - if ( pos < len && auth.find("true", pos, false) != -1 ) - { - info.digestInfo = (m_responseCode == 401) ? m_strAuthorization : m_strProxyAuthorization; - kdDebug(7113) << "(" << m_pid << ") Just a stale nonce value! " - << "Retrying using the new nonce sent..." << endl; - } - } - } - } - - if (!result ) - { - // Do not prompt if the username & password - // is already supplied and the login attempt - // did not fail before. - if ( !repeatFailure && - !info.username.isNull() && - !info.password.isNull() ) - result = true; - else - { - if (Authentication == AUTH_Negotiate) - { - if (!repeatFailure) - result = true; - } - else if ( m_request.disablePassDlg == false ) - { - kdDebug( 7113 ) << "(" << m_pid << ") Prompting the user for authorization..." << endl; - promptInfo( info ); - result = openPassDlg( info, errorMsg ); - } - } - } - - if ( result ) - { - switch (m_responseCode) - { - case 401: // Request-Authentication - m_request.user = info.username; - m_request.passwd = info.password; - m_strRealm = info.realmValue; - m_strAuthorization = info.digestInfo; - break; - case 407: // Proxy-Authentication - m_proxyURL.setUser( info.username ); - m_proxyURL.setPass( info.password ); - m_strProxyRealm = info.realmValue; - m_strProxyAuthorization = info.digestInfo; - break; - default: - break; - } - return true; - } - - if (m_request.bErrorPage) - errorPage(); - else - error( ERR_USER_CANCELED, TQString::null ); - return false; -} - -void HTTPProtocol::saveAuthorization() -{ - AuthInfo info; - if ( m_prevResponseCode == 407 ) - { - if (!m_bUseProxy) - return; - m_bProxyAuthValid = true; - info.url = m_proxyURL; - info.username = m_proxyURL.user(); - info.password = m_proxyURL.pass(); - info.realmValue = m_strProxyRealm; - info.digestInfo = m_strProxyAuthorization; - cacheAuthentication( info ); - } - else - { - info.url = m_request.url; - info.username = m_request.user; - info.password = m_request.passwd; - info.realmValue = m_strRealm; - info.digestInfo = m_strAuthorization; - cacheAuthentication( info ); - } -} - -#ifdef HAVE_LIBGSSAPI -TQCString HTTPProtocol::gssError( int major_status, int minor_status ) -{ - OM_uint32 new_status; - OM_uint32 msg_ctx = 0; - gss_buffer_desc major_string; - gss_buffer_desc minor_string; - OM_uint32 ret; - TQCString errorstr; - - errorstr = ""; - - do { - ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string); - errorstr += (const char *)major_string.value; - errorstr += " "; - ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string); - errorstr += (const char *)minor_string.value; - errorstr += " "; - } while (!GSS_ERROR(ret) && msg_ctx != 0); - - return errorstr; -} - -TQString HTTPProtocol::createNegotiateAuth() -{ - TQString auth; - TQCString servicename; - TQByteArray input; - OM_uint32 major_status, minor_status; - OM_uint32 req_flags = 0; - gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; - gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; - gss_name_t server; - gss_ctx_id_t ctx; - gss_OID mech_oid; - static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; - static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"}; - int found = 0; - unsigned int i; - gss_OID_set mech_set; - gss_OID tmp_oid; - - ctx = GSS_C_NO_CONTEXT; - mech_oid = &krb5_oid_desc; - - // see whether we can use the SPNEGO mechanism - major_status = gss_indicate_mechs(&minor_status, &mech_set); - if (GSS_ERROR(major_status)) { - kdDebug(7113) << "(" << m_pid << ") gss_indicate_mechs failed: " << gssError(major_status, minor_status) << endl; - } else { - for (i=0; icount && !found; i++) { - tmp_oid = &mech_set->elements[i]; - if (tmp_oid->length == spnego_oid_desc.length && - !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) { - kdDebug(7113) << "(" << m_pid << ") createNegotiateAuth: found SPNEGO mech" << endl; - found = 1; - mech_oid = &spnego_oid_desc; - break; - } - } - gss_release_oid_set(&minor_status, &mech_set); - } - - // the service name is "HTTP/f.q.d.n" - servicename = "HTTP@"; - servicename += m_state.hostname.ascii(); - - input_token.value = (void *)servicename.data(); - input_token.length = servicename.length() + 1; - - major_status = gss_import_name(&minor_status, &input_token, - GSS_C_NT_HOSTBASED_SERVICE, &server); - - input_token.value = NULL; - input_token.length = 0; - - if (GSS_ERROR(major_status)) { - kdDebug(7113) << "(" << m_pid << ") gss_import_name failed: " << gssError(major_status, minor_status) << endl; - // reset the auth string so that subsequent methods aren't confused - m_strAuthorization = TQString::null; - return TQString::null; - } - - major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, - &ctx, server, mech_oid, - req_flags, GSS_C_INDEFINITE, - GSS_C_NO_CHANNEL_BINDINGS, - GSS_C_NO_BUFFER, NULL, &output_token, - NULL, NULL); - - - if (GSS_ERROR(major_status) || (output_token.length == 0)) { - kdDebug(7113) << "(" << m_pid << ") gss_init_sec_context failed: " << gssError(major_status, minor_status) << endl; - gss_release_name(&minor_status, &server); - if (ctx != GSS_C_NO_CONTEXT) { - gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); - ctx = GSS_C_NO_CONTEXT; - } - // reset the auth string so that subsequent methods aren't confused - m_strAuthorization = TQString::null; - return TQString::null; - } - - input.duplicate((const char *)output_token.value, output_token.length); - auth = "Authorization: Negotiate "; - auth += KCodecs::base64Encode( input ); - auth += "\r\n"; - - // free everything - gss_release_name(&minor_status, &server); - if (ctx != GSS_C_NO_CONTEXT) { - gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); - ctx = GSS_C_NO_CONTEXT; - } - gss_release_buffer(&minor_status, &output_token); - - return auth; -} -#else - -// Dummy -TQCString HTTPProtocol::gssError( int, int ) -{ - return ""; -} - -// Dummy -TQString HTTPProtocol::createNegotiateAuth() -{ - return TQString::null; -} -#endif - -TQString HTTPProtocol::createNTLMAuth( bool isForProxy ) -{ - uint len; - TQString auth, user, domain, passwd; - TQCString strauth; - TQByteArray buf; - - if ( isForProxy ) - { - auth = "Proxy-Connection: Keep-Alive\r\n"; - auth += "Proxy-Authorization: NTLM "; - user = m_proxyURL.user(); - passwd = m_proxyURL.pass(); - strauth = m_strProxyAuthorization.latin1(); - len = m_strProxyAuthorization.length(); - } - else - { - auth = "Authorization: NTLM "; - user = m_state.user; - passwd = m_state.passwd; - strauth = m_strAuthorization.latin1(); - len = m_strAuthorization.length(); - } - if ( user.contains('\\') ) { - domain = user.section( '\\', 0, 0); - user = user.section( '\\', 1 ); - } - - kdDebug(7113) << "(" << m_pid << ") NTLM length: " << len << endl; - if ( user.isEmpty() || passwd.isEmpty() || len < 4 ) - return TQString::null; - - if ( len > 4 ) - { - // create a response - TQByteArray challenge; - KCodecs::base64Decode( strauth.right( len - 5 ), challenge ); - KNTLM::getAuth( buf, challenge, user, passwd, domain, - KNetwork::KResolver::localHostName(), false, false ); - } - else - { - KNTLM::getNegotiate( buf ); - } - - // remove the challenge to prevent reuse - if ( isForProxy ) - m_strProxyAuthorization = "NTLM"; - else - m_strAuthorization = "NTLM"; - - auth += KCodecs::base64Encode( buf ); - auth += "\r\n"; - - return auth; -} - -TQString HTTPProtocol::createBasicAuth( bool isForProxy ) -{ - TQString auth; - TQCString user, passwd; - if ( isForProxy ) - { - auth = "Proxy-Authorization: Basic "; - user = m_proxyURL.user().latin1(); - passwd = m_proxyURL.pass().latin1(); - } - else - { - auth = "Authorization: Basic "; - user = m_state.user.latin1(); - passwd = m_state.passwd.latin1(); - } - - if ( user.isEmpty() ) - user = ""; - if ( passwd.isEmpty() ) - passwd = ""; - - user += ':'; - user += passwd; - auth += KCodecs::base64Encode( user ); - auth += "\r\n"; - - return auth; -} - -void HTTPProtocol::calculateResponse( DigestAuthInfo& info, TQCString& Response ) -{ - KMD5 md; - TQCString HA1; - TQCString HA2; - - // Calculate H(A1) - TQCString authStr = info.username; - authStr += ':'; - authStr += info.realm; - authStr += ':'; - authStr += info.password; - md.update( authStr ); - - if ( info.algorithm.lower() == "md5-sess" ) - { - authStr = md.hexDigest(); - authStr += ':'; - authStr += info.nonce; - authStr += ':'; - authStr += info.cnonce; - md.reset(); - md.update( authStr ); - } - HA1 = md.hexDigest(); - - kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A1 => " << HA1 << endl; - - // Calcualte H(A2) - authStr = info.method; - authStr += ':'; - authStr += m_request.url.encodedPathAndQuery(0, true).latin1(); - if ( info.qop == "auth-int" ) - { - authStr += ':'; - authStr += info.entityBody; - } - md.reset(); - md.update( authStr ); - HA2 = md.hexDigest(); - - kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A2 => " - << HA2 << endl; - - // Calcualte the response. - authStr = HA1; - authStr += ':'; - authStr += info.nonce; - authStr += ':'; - if ( !info.qop.isEmpty() ) - { - authStr += info.nc; - authStr += ':'; - authStr += info.cnonce; - authStr += ':'; - authStr += info.qop; - authStr += ':'; - } - authStr += HA2; - md.reset(); - md.update( authStr ); - Response = md.hexDigest(); - - kdDebug(7113) << "(" << m_pid << ") calculateResponse(): Response => " - << Response << endl; -} - -TQString HTTPProtocol::createDigestAuth ( bool isForProxy ) -{ - const char *p; - - TQString auth; - TQCString opaque; - TQCString Response; - - DigestAuthInfo info; - - opaque = ""; - if ( isForProxy ) - { - auth = "Proxy-Authorization: Digest "; - info.username = m_proxyURL.user().latin1(); - info.password = m_proxyURL.pass().latin1(); - p = m_strProxyAuthorization.latin1(); - } - else - { - auth = "Authorization: Digest "; - info.username = m_state.user.latin1(); - info.password = m_state.passwd.latin1(); - p = m_strAuthorization.latin1(); - } - if (!p || !*p) - return TQString::null; - - p += 6; // Skip "Digest" - - if ( info.username.isEmpty() || info.password.isEmpty() || !p ) - return TQString::null; - - // info.entityBody = p; // FIXME: send digest of data for POST action ?? - info.realm = ""; - info.algorithm = "MD5"; - info.nonce = ""; - info.qop = ""; - - // cnonce is recommended to contain about 64 bits of entropy - info.cnonce = TDEApplication::randomString(16).latin1(); - - // HACK: Should be fixed according to RFC 2617 section 3.2.2 - info.nc = "00000001"; - - // Set the method used... - switch ( m_request.method ) - { - case HTTP_GET: - info.method = "GET"; - break; - case HTTP_PUT: - info.method = "PUT"; - break; - case HTTP_POST: - info.method = "POST"; - break; - case HTTP_HEAD: - info.method = "HEAD"; - break; - case HTTP_DELETE: - info.method = "DELETE"; - break; - case DAV_PROPFIND: - info.method = "PROPFIND"; - break; - case DAV_PROPPATCH: - info.method = "PROPPATCH"; - break; - case DAV_MKCOL: - info.method = "MKCOL"; - break; - case DAV_COPY: - info.method = "COPY"; - break; - case DAV_MOVE: - info.method = "MOVE"; - break; - case DAV_LOCK: - info.method = "LOCK"; - break; - case DAV_UNLOCK: - info.method = "UNLOCK"; - break; - case DAV_SEARCH: - info.method = "SEARCH"; - break; - case DAV_SUBSCRIBE: - info.method = "SUBSCRIBE"; - break; - case DAV_UNSUBSCRIBE: - info.method = "UNSUBSCRIBE"; - break; - case DAV_POLL: - info.method = "POLL"; - break; - default: - error( ERR_UNSUPPORTED_ACTION, i18n("Unsupported method: authentication will fail. Please submit a bug report.")); - break; - } - - // Parse the Digest response.... - while (*p) - { - int i = 0; - while ( (*p == ' ') || (*p == ',') || (*p == '\t')) { p++; } - if (strncasecmp(p, "realm=", 6 )==0) - { - p+=6; - while ( *p == '"' ) p++; // Go past any number of " mark(s) first - while ( p[i] != '"' ) i++; // Read everything until the last " mark - info.realm = TQCString( p, i+1 ); - } - else if (strncasecmp(p, "algorith=", 9)==0) - { - p+=9; - while ( *p == '"' ) p++; // Go past any number of " mark(s) first - while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++; - info.algorithm = TQCString(p, i+1); - } - else if (strncasecmp(p, "algorithm=", 10)==0) - { - p+=10; - while ( *p == '"' ) p++; // Go past any " mark(s) first - while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++; - info.algorithm = TQCString(p,i+1); - } - else if (strncasecmp(p, "domain=", 7)==0) - { - p+=7; - while ( *p == '"' ) p++; // Go past any " mark(s) first - while ( p[i] != '"' ) i++; // Read everything until the last " mark - int pos; - int idx = 0; - TQCString uri = TQCString(p,i+1); - do - { - pos = uri.find( ' ', idx ); - if ( pos != -1 ) - { - KURL u (m_request.url, uri.mid(idx, pos-idx)); - if (u.isValid ()) - info.digestURI.append( u.url().latin1() ); - } - else - { - KURL u (m_request.url, uri.mid(idx, uri.length()-idx)); - if (u.isValid ()) - info.digestURI.append( u.url().latin1() ); - } - idx = pos+1; - } while ( pos != -1 ); - } - else if (strncasecmp(p, "nonce=", 6)==0) - { - p+=6; - while ( *p == '"' ) p++; // Go past any " mark(s) first - while ( p[i] != '"' ) i++; // Read everything until the last " mark - info.nonce = TQCString(p,i+1); - } - else if (strncasecmp(p, "opaque=", 7)==0) - { - p+=7; - while ( *p == '"' ) p++; // Go past any " mark(s) first - while ( p[i] != '"' ) i++; // Read everything until the last " mark - opaque = TQCString(p,i+1); - } - else if (strncasecmp(p, "qop=", 4)==0) - { - p+=4; - while ( *p == '"' ) p++; // Go past any " mark(s) first - while ( p[i] != '"' ) i++; // Read everything until the last " mark - info.qop = TQCString(p,i+1); - } - p+=(i+1); - } - - if (info.realm.isEmpty() || info.nonce.isEmpty()) - return TQString::null; - - // If the "domain" attribute was not specified and the current response code - // is authentication needed, add the current request url to the list over which - // this credential can be automatically applied. - if (info.digestURI.isEmpty() && (m_responseCode == 401 || m_responseCode == 407)) - info.digestURI.append (m_request.url.url().latin1()); - else - { - // Verify whether or not we should send a cached credential to the - // server based on the stored "domain" attribute... - bool send = true; - - // Determine the path of the request url... - TQString requestPath = m_request.url.directory(false, false); - if (requestPath.isEmpty()) - requestPath = "/"; - - int count = info.digestURI.count(); - - for (int i = 0; i < count; i++ ) - { - KURL u ( info.digestURI.at(i) ); - - send &= (m_request.url.protocol().lower() == u.protocol().lower()); - send &= (m_request.hostname.lower() == u.host().lower()); - - if (m_request.port > 0 && u.port() > 0) - send &= (m_request.port == u.port()); - - TQString digestPath = u.directory (false, false); - if (digestPath.isEmpty()) - digestPath = "/"; - - send &= (requestPath.startsWith(digestPath)); - - if (send) - break; - } - - kdDebug(7113) << "(" << m_pid << ") createDigestAuth(): passed digest " - "authentication credential test: " << send << endl; - - if (!send) - return TQString::null; - } - - kdDebug(7113) << "(" << m_pid << ") RESULT OF PARSING:" << endl; - kdDebug(7113) << "(" << m_pid << ") algorithm: " << info.algorithm << endl; - kdDebug(7113) << "(" << m_pid << ") realm: " << info.realm << endl; - kdDebug(7113) << "(" << m_pid << ") nonce: " << info.nonce << endl; - kdDebug(7113) << "(" << m_pid << ") opaque: " << opaque << endl; - kdDebug(7113) << "(" << m_pid << ") qop: " << info.qop << endl; - - // Calculate the response... - calculateResponse( info, Response ); - - auth += "username=\""; - auth += info.username; - - auth += "\", realm=\""; - auth += info.realm; - auth += "\""; - - auth += ", nonce=\""; - auth += info.nonce; - - auth += "\", uri=\""; - auth += m_request.url.encodedPathAndQuery(0, true); - - auth += "\", algorithm=\""; - auth += info.algorithm; - auth +="\""; - - if ( !info.qop.isEmpty() ) - { - auth += ", qop=\""; - auth += info.qop; - auth += "\", cnonce=\""; - auth += info.cnonce; - auth += "\", nc="; - auth += info.nc; - } - - auth += ", response=\""; - auth += Response; - if ( !opaque.isEmpty() ) - { - auth += "\", opaque=\""; - auth += opaque; - } - auth += "\"\r\n"; - - return auth; -} - -TQString HTTPProtocol::proxyAuthenticationHeader() -{ - TQString header; - - // We keep proxy authentication locally until they are changed. - // Thus, no need to check with the password manager for every - // connection. - if ( m_strProxyRealm.isEmpty() ) - { - AuthInfo info; - info.url = m_proxyURL; - info.username = m_proxyURL.user(); - info.password = m_proxyURL.pass(); - info.verifyPath = true; - - // If the proxy URL already contains username - // and password simply attempt to retrieve it - // without prompting the user... - if ( !info.username.isNull() && !info.password.isNull() ) - { - if( m_strProxyAuthorization.isEmpty() ) - ProxyAuthentication = AUTH_None; - else if( m_strProxyAuthorization.startsWith("Basic") ) - ProxyAuthentication = AUTH_Basic; - else if( m_strProxyAuthorization.startsWith("NTLM") ) - ProxyAuthentication = AUTH_NTLM; - else - ProxyAuthentication = AUTH_Digest; - } - else - { - if ( checkCachedAuthentication(info) && !info.digestInfo.isEmpty() ) - { - m_proxyURL.setUser( info.username ); - m_proxyURL.setPass( info.password ); - m_strProxyRealm = info.realmValue; - m_strProxyAuthorization = info.digestInfo; - if( m_strProxyAuthorization.startsWith("Basic") ) - ProxyAuthentication = AUTH_Basic; - else if( m_strProxyAuthorization.startsWith("NTLM") ) - ProxyAuthentication = AUTH_NTLM; - else - ProxyAuthentication = AUTH_Digest; - } - else - { - ProxyAuthentication = AUTH_None; - } - } - } - - /********* Only for debugging purpose... *********/ - if ( ProxyAuthentication != AUTH_None ) - { - kdDebug(7113) << "(" << m_pid << ") Using Proxy Authentication: " << endl; - kdDebug(7113) << "(" << m_pid << ") HOST= " << m_proxyURL.host() << endl; - kdDebug(7113) << "(" << m_pid << ") PORT= " << m_proxyURL.port() << endl; - kdDebug(7113) << "(" << m_pid << ") USER= " << m_proxyURL.user() << endl; - kdDebug(7113) << "(" << m_pid << ") PASSWORD= [protected]" << endl; - kdDebug(7113) << "(" << m_pid << ") REALM= " << m_strProxyRealm << endl; - kdDebug(7113) << "(" << m_pid << ") EXTRA= " << m_strProxyAuthorization << endl; - } - - switch ( ProxyAuthentication ) - { - case AUTH_Basic: - header += createBasicAuth( true ); - break; - case AUTH_Digest: - header += createDigestAuth( true ); - break; - case AUTH_NTLM: - if ( m_bFirstRequest ) header += createNTLMAuth( true ); - break; - case AUTH_None: - default: - break; - } - - return header; -} - -#include "http.moc" diff --git a/tdeioslave/http/http.cpp b/tdeioslave/http/http.cpp new file mode 100644 index 000000000..8cd0f7a64 --- /dev/null +++ b/tdeioslave/http/http.cpp @@ -0,0 +1,6131 @@ +/* + Copyright (C) 2000-2003 Waldo Bastian + Copyright (C) 2000-2002 George Staikos + Copyright (C) 2000-2002 Dawit Alemayehu + Copyright (C) 2001,2002 Hamish Rodda + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) 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 + +#include +#include +#include +#include +#include +#include +#include +#include // Required for AIX +#include +#include // must be explicitly included for MacOSX + +/* +#include +#include +#include +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tdeio/ioslave_defaults.h" +#include "tdeio/http_slave_defaults.h" + +#include "httpfilter.h" +#include "http.h" + +#ifdef HAVE_LIBGSSAPI +#ifdef GSSAPI_MIT +#include +#else +#include +#endif /* GSSAPI_MIT */ + +// Catch uncompatible crap (BR86019) +#if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0) +#include +#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name +#endif + +#endif /* HAVE_LIBGSSAPI */ + +#include + +using namespace TDEIO; + +extern "C" { + KDE_EXPORT int kdemain(int argc, char **argv); +} + +int kdemain( int argc, char **argv ) +{ + TDELocale::setMainCatalogue("tdelibs"); + TDEInstance instance( "tdeio_http" ); + ( void ) TDEGlobal::locale(); + + if (argc != 4) + { + fprintf(stderr, "Usage: tdeio_http protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + HTTPProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + return 0; +} + +/*********************************** Generic utility functions ********************/ + +static char * trimLead (char *orig_string) +{ + while (*orig_string == ' ') + orig_string++; + return orig_string; +} + +static bool isCrossDomainRequest( const TQString& fqdn, const TQString& originURL ) +{ + if (originURL == "true") // Backwards compatibility + return true; + + KURL url ( originURL ); + + // Document Origin domain + TQString a = url.host(); + + // Current request domain + TQString b = fqdn; + + if (a == b) + return false; + + TQStringList l1 = TQStringList::split('.', a); + TQStringList l2 = TQStringList::split('.', b); + + while(l1.count() > l2.count()) + l1.pop_front(); + + while(l2.count() > l1.count()) + l2.pop_front(); + + while(l2.count() >= 2) + { + if (l1 == l2) + return false; + + l1.pop_front(); + l2.pop_front(); + } + + return true; +} + +/* + Eliminates any custom header that could potentically alter the request +*/ +static TQString sanitizeCustomHTTPHeader(const TQString& _header) +{ + TQString sanitizedHeaders; + TQStringList headers = TQStringList::split(TQRegExp("[\r\n]"), _header); + + for(TQStringList::Iterator it = headers.begin(); it != headers.end(); ++it) + { + TQString header = (*it).lower(); + // Do not allow Request line to be specified and ignore + // the other HTTP headers. + if (header.find(':') == -1 || + header.startsWith("host") || + header.startsWith("via")) + continue; + + sanitizedHeaders += (*it); + sanitizedHeaders += "\r\n"; + } + + return sanitizedHeaders.stripWhiteSpace(); +} + +static TQString htmlEscape(const TQString &plain) +{ + TQString rich; + rich.reserve(uint(plain.length() * 1.1)); + for (uint i = 0; i < plain.length(); ++i) { + if (plain.at(i) == '<') { + rich += "<"; + } else if (plain.at(i) == '>') { + rich += ">"; + } else if (plain.at(i) == '&') { + rich += "&"; + } else if (plain.at(i) == '"') { + rich += """; + } else { + rich += plain.at(i); + } + } + rich.squeeze(); + return rich; +} + + +#define NO_SIZE ((TDEIO::filesize_t) -1) + +#ifdef HAVE_STRTOLL +#define STRTOLL strtoll +#else +#define STRTOLL strtol +#endif + + +/************************************** HTTPProtocol **********************************************/ + +HTTPProtocol::HTTPProtocol( const TQCString &protocol, const TQCString &pool, + const TQCString &app ) + :TCPSlaveBase( 0, protocol , pool, app, + (protocol == "https" || protocol == "webdavs") ) +{ + m_requestQueue.setAutoDelete(true); + + m_bBusy = false; + m_bFirstRequest = false; + m_bProxyAuthValid = false; + + m_iSize = NO_SIZE; + m_lineBufUnget = 0; + + m_protocol = protocol; + + m_maxCacheAge = DEFAULT_MAX_CACHE_AGE; + m_maxCacheSize = DEFAULT_MAX_CACHE_SIZE / 2; + m_remoteConnTimeout = DEFAULT_CONNECT_TIMEOUT; + m_remoteRespTimeout = DEFAULT_RESPONSE_TIMEOUT; + m_proxyConnTimeout = DEFAULT_PROXY_CONNECT_TIMEOUT; + + m_pid = getpid(); + + setMultipleAuthCaching( true ); + reparseConfiguration(); +} + +HTTPProtocol::~HTTPProtocol() +{ + httpClose(false); +} + +void HTTPProtocol::reparseConfiguration() +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::reparseConfiguration" << endl; + + m_strProxyRealm = TQString::null; + m_strProxyAuthorization = TQString::null; + ProxyAuthentication = AUTH_None; + m_bUseProxy = false; + + if (m_protocol == "https" || m_protocol == "webdavs") + m_iDefaultPort = DEFAULT_HTTPS_PORT; + else if (m_protocol == "ftp") + m_iDefaultPort = DEFAULT_FTP_PORT; + else + m_iDefaultPort = DEFAULT_HTTP_PORT; +} + +void HTTPProtocol::resetConnectionSettings() +{ + m_bEOF = false; + m_bError = false; + m_lineCount = 0; + m_iWWWAuthCount = 0; + m_lineCountUnget = 0; + m_iProxyAuthCount = 0; + +} + +void HTTPProtocol::resetResponseSettings() +{ + m_bRedirect = false; + m_redirectLocation = KURL(); + m_bChunked = false; + m_iSize = NO_SIZE; + + m_responseHeader.clear(); + m_qContentEncodings.clear(); + m_qTransferEncodings.clear(); + m_sContentMD5 = TQString::null; + m_strMimeType = TQString::null; + + setMetaData("request-id", m_request.id); +} + +void HTTPProtocol::resetSessionSettings() +{ + // Do not reset the URL on redirection if the proxy + // URL, username or password has not changed! + KURL proxy ( config()->readEntry("UseProxy") ); + + if ( m_strProxyRealm.isEmpty() || !proxy.isValid() || + m_proxyURL.host() != proxy.host() || + (!proxy.user().isNull() && proxy.user() != m_proxyURL.user()) || + (!proxy.pass().isNull() && proxy.pass() != m_proxyURL.pass()) ) + { + m_bProxyAuthValid = false; + m_proxyURL = proxy; + m_bUseProxy = m_proxyURL.isValid(); + + kdDebug(7113) << "(" << m_pid << ") Using proxy: " << m_bUseProxy << + " URL: " << m_proxyURL.prettyURL() << + " Realm: " << m_strProxyRealm << endl; + } + + m_bPersistentProxyConnection = config()->readBoolEntry("PersistentProxyConnection", false); + kdDebug(7113) << "(" << m_pid << ") Enable Persistent Proxy Connection: " + << m_bPersistentProxyConnection << endl; + + m_request.bUseCookiejar = config()->readBoolEntry("Cookies"); + m_request.bUseCache = config()->readBoolEntry("UseCache", true); + m_request.bErrorPage = config()->readBoolEntry("errorPage", true); + m_request.bNoAuth = config()->readBoolEntry("no-auth"); + m_strCacheDir = config()->readPathEntry("CacheDir"); + m_maxCacheAge = config()->readNumEntry("MaxCacheAge", DEFAULT_MAX_CACHE_AGE); + m_request.window = config()->readEntry("window-id"); + + kdDebug(7113) << "(" << m_pid << ") Window Id = " << m_request.window << endl; + kdDebug(7113) << "(" << m_pid << ") ssl_was_in_use = " + << metaData ("ssl_was_in_use") << endl; + + m_request.referrer = TQString::null; + if ( config()->readBoolEntry("SendReferrer", true) && + (m_protocol == "https" || m_protocol == "webdavs" || + metaData ("ssl_was_in_use") != "TRUE" ) ) + { + KURL referrerURL ( metaData("referrer") ); + if (referrerURL.isValid()) + { + // Sanitize + TQString protocol = referrerURL.protocol(); + if (protocol.startsWith("webdav")) + { + protocol.replace(0, 6, "http"); + referrerURL.setProtocol(protocol); + } + + if (protocol.startsWith("http")) + { + referrerURL.setRef(TQString::null); + referrerURL.setUser(TQString::null); + referrerURL.setPass(TQString::null); + m_request.referrer = referrerURL.url(); + } + } + } + + if ( config()->readBoolEntry("SendLanguageSettings", true) ) + { + m_request.charsets = config()->readEntry( "Charsets", "iso-8859-1" ); + + if ( !m_request.charsets.isEmpty() ) + m_request.charsets += DEFAULT_PARTIAL_CHARSET_HEADER; + + m_request.languages = config()->readEntry( "Languages", DEFAULT_LANGUAGE_HEADER ); + } + else + { + m_request.charsets = TQString::null; + m_request.languages = TQString::null; + } + + // Adjust the offset value based on the "resume" meta-data. + TQString resumeOffset = metaData("resume"); + if ( !resumeOffset.isEmpty() ) + m_request.offset = resumeOffset.toInt(); // TODO: Convert to 64 bit + else + m_request.offset = 0; + + m_request.disablePassDlg = config()->readBoolEntry("DisablePassDlg", false); + m_request.allowCompressedPage = config()->readBoolEntry("AllowCompressedPage", true); + m_request.id = metaData("request-id"); + + // Store user agent for this host. + if ( config()->readBoolEntry("SendUserAgent", true) ) + m_request.userAgent = metaData("UserAgent"); + else + m_request.userAgent = TQString::null; + + // Deal with cache cleaning. + // TODO: Find a smarter way to deal with cleaning the + // cache ? + if ( m_request.bUseCache ) + cleanCache(); + + // Deal with HTTP tunneling + if ( m_bIsSSL && m_bUseProxy && m_proxyURL.protocol() != "https" && + m_proxyURL.protocol() != "webdavs") + { + m_bNeedTunnel = true; + setRealHost( m_request.hostname ); + kdDebug(7113) << "(" << m_pid << ") SSL tunnel: Setting real hostname to: " + << m_request.hostname << endl; + } + else + { + m_bNeedTunnel = false; + setRealHost( TQString::null); + } + + m_responseCode = 0; + m_prevResponseCode = 0; + + m_strRealm = TQString::null; + m_strAuthorization = TQString::null; + Authentication = AUTH_None; + + // Obtain the proxy and remote server timeout values + m_proxyConnTimeout = proxyConnectTimeout(); + m_remoteConnTimeout = connectTimeout(); + m_remoteRespTimeout = responseTimeout(); + + // Set the SSL meta-data here... + setSSLMetaData(); + + // Bounce back the actual referrer sent + setMetaData("referrer", m_request.referrer); + + // Follow HTTP/1.1 spec and enable keep-alive by default + // unless the remote side tells us otherwise or we determine + // the persistent link has been terminated by the remote end. + m_bKeepAlive = true; + m_keepAliveTimeout = 0; + m_bUnauthorized = false; + + // A single request can require multiple exchanges with the remote + // server due to authentication challenges or SSL tunneling. + // m_bFirstRequest is a flag that indicates whether we are + // still processing the first request. This is important because we + // should not force a close of a keep-alive connection in the middle + // of the first request. + // m_bFirstRequest is set to "true" whenever a new connection is + // made in httpOpenConnection() + m_bFirstRequest = false; +} + +void HTTPProtocol::setHost( const TQString& host, int port, + const TQString& user, const TQString& pass ) +{ + // Reset the webdav-capable flags for this host + if ( m_request.hostname != host ) + m_davHostOk = m_davHostUnsupported = false; + + // is it an IPv6 address? + if (host.find(':') == -1) + { + m_request.hostname = host; + m_request.encoded_hostname = KIDNA::toAscii(host); + } + else + { + m_request.hostname = host; + int pos = host.find('%'); + if (pos == -1) + m_request.encoded_hostname = '[' + host + ']'; + else + // don't send the scope-id in IPv6 addresses to the server + m_request.encoded_hostname = '[' + host.left(pos) + ']'; + } + m_request.port = (port == 0) ? m_iDefaultPort : port; + m_request.user = user; + m_request.passwd = pass; + + m_bIsTunneled = false; + + kdDebug(7113) << "(" << m_pid << ") Hostname is now: " << m_request.hostname << + " (" << m_request.encoded_hostname << ")" <\r\n"; + request.append( "\r\n" ); + request.append( query.utf8() ); + request.append( "\r\n" ); + + davSetRequest( request ); + } else { + // We are only after certain features... + TQCString request; + request = "" + ""; + + // insert additional XML request from the davRequestResponse metadata + if ( hasMetaData( "davRequestResponse" ) ) + request += metaData( "davRequestResponse" ).utf8(); + else { + // No special request, ask for default properties + request += "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + } + request += ""; + + davSetRequest( request ); + } + + // WebDAV Stat or List... + m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH; + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + m_request.davData.depth = stat ? 0 : 1; + if (!stat) + m_request.url.adjustPath(+1); + + retrieveContent( true ); + + // Has a redirection already been called? If so, we're done. + if (m_bRedirect) { + finished(); + return; + } + + TQDomDocument multiResponse; + multiResponse.setContent( m_bufWebDavData, true ); + + bool hasResponse = false; + + for ( TQDomNode n = multiResponse.documentElement().firstChild(); + !n.isNull(); n = n.nextSibling()) + { + TQDomElement thisResponse = n.toElement(); + if (thisResponse.isNull()) + continue; + + hasResponse = true; + + TQDomElement href = thisResponse.namedItem( "href" ).toElement(); + if ( !href.isNull() ) + { + entry.clear(); + + TQString urlStr = href.text(); +#if 0 + int encoding = remoteEncoding()->encodingMib(); + if ((encoding == 106) && (!KStringHandler::isUtf8(KURL::decode_string(urlStr, 4).latin1()))) + encoding = 4; // Use latin1 if the file is not actually utf-8 +#else + TQUrl::decode(urlStr); + int encoding = 106; +#endif + + KURL thisURL ( urlStr, encoding ); + + atom.m_uds = TDEIO::UDS_NAME; + + if ( thisURL.isValid() ) { + // don't list the base dir of a listDir() + if ( !stat && thisURL.path(+1).length() == url.path(+1).length() ) + continue; + + atom.m_str = thisURL.fileName(); + } else { + // This is a relative URL. + atom.m_str = href.text(); + } + + entry.append( atom ); + + TQDomNodeList propstats = thisResponse.elementsByTagName( "propstat" ); + + davParsePropstats( propstats, entry ); + + if ( stat ) + { + // return an item + statEntry( entry ); + finished(); + return; + } + else + { + listEntry( entry, false ); + } + } + else + { + kdDebug(7113) << "Error: no URL contained in response to PROPFIND on " + << url.prettyURL() << endl; + } + } + + if ( stat || !hasResponse ) + { + error( ERR_DOES_NOT_EXIST, url.prettyURL() ); + } + else + { + listEntry( entry, true ); + finished(); + } +} + +void HTTPProtocol::davGeneric( const KURL& url, TDEIO::HTTP_METHOD method ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davGeneric " << url.prettyURL() + << endl; + + if ( !checkRequestURL( url ) ) + return; + + // check to make sure this host supports WebDAV + if ( !davHostOk() ) + return; + + // WebDAV method + m_request.method = method; + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveContent( false ); +} + +int HTTPProtocol::codeFromResponse( const TQString& response ) +{ + int firstSpace = response.find( ' ' ); + int secondSpace = response.find( ' ', firstSpace + 1 ); + return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt(); +} + +void HTTPProtocol::davParsePropstats( const TQDomNodeList& propstats, UDSEntry& entry ) +{ + TQString mimeType; + UDSAtom atom; + bool foundExecutable = false; + bool isDirectory = false; + uint lockCount = 0; + uint supportedLockCount = 0; + + for ( uint i = 0; i < propstats.count(); i++) + { + TQDomElement propstat = propstats.item(i).toElement(); + + TQDomElement status = propstat.namedItem( "status" ).toElement(); + if ( status.isNull() ) + { + // error, no status code in this propstat + kdDebug(7113) << "Error, no status code in this propstat" << endl; + return; + } + + int code = codeFromResponse( status.text() ); + + if ( code != 200 ) + { + kdDebug(7113) << "Warning: status code " << code << " (this may mean that some properties are unavailable" << endl; + continue; + } + + TQDomElement prop = propstat.namedItem( "prop" ).toElement(); + if ( prop.isNull() ) + { + kdDebug(7113) << "Error: no prop segment in this propstat." << endl; + return; + } + + if ( hasMetaData( "davRequestResponse" ) ) + { + atom.m_uds = TDEIO::UDS_XML_PROPERTIES; + TQDomDocument doc; + doc.appendChild(prop); + atom.m_str = doc.toString(); + entry.append( atom ); + } + + for ( TQDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() ) + { + TQDomElement property = n.toElement(); + if (property.isNull()) + continue; + + if ( property.namespaceURI() != "DAV:" ) + { + // break out - we're only interested in properties from the DAV namespace + continue; + } + + if ( property.tagName() == "creationdate" ) + { + // Resource creation date. Should be is ISO 8601 format. + atom.m_uds = TDEIO::UDS_CREATION_TIME; + atom.m_long = parseDateTime( property.text(), property.attribute("dt") ); + entry.append( atom ); + } + else if ( property.tagName() == "getcontentlength" ) + { + // Content length (file size) + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = property.text().toULong(); + entry.append( atom ); + } + else if ( property.tagName() == "displayname" ) + { + // Name suitable for presentation to the user + setMetaData( "davDisplayName", property.text() ); + } + else if ( property.tagName() == "source" ) + { + // Source template location + TQDomElement source = property.namedItem( "link" ).toElement() + .namedItem( "dst" ).toElement(); + if ( !source.isNull() ) + setMetaData( "davSource", source.text() ); + } + else if ( property.tagName() == "getcontentlanguage" ) + { + // equiv. to Content-Language header on a GET + setMetaData( "davContentLanguage", property.text() ); + } + else if ( property.tagName() == "getcontenttype" ) + { + // Content type (mime type) + // This may require adjustments for other server-side webdav implementations + // (tested with Apache + mod_dav 1.0.3) + if ( property.text() == "httpd/unix-directory" ) + { + isDirectory = true; + } + else + { + mimeType = property.text(); + } + } + else if ( property.tagName() == "executable" ) + { + // File executable status + if ( property.text() == "T" ) + foundExecutable = true; + + } + else if ( property.tagName() == "getlastmodified" ) + { + // Last modification date + atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; + atom.m_long = parseDateTime( property.text(), property.attribute("dt") ); + entry.append( atom ); + + } + else if ( property.tagName() == "getetag" ) + { + // Entity tag + setMetaData( "davEntityTag", property.text() ); + } + else if ( property.tagName() == "supportedlock" ) + { + // Supported locking specifications + for ( TQDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() ) + { + TQDomElement lockEntry = n2.toElement(); + if ( lockEntry.tagName() == "lockentry" ) + { + TQDomElement lockScope = lockEntry.namedItem( "lockscope" ).toElement(); + TQDomElement lockType = lockEntry.namedItem( "locktype" ).toElement(); + if ( !lockScope.isNull() && !lockType.isNull() ) + { + // Lock type was properly specified + supportedLockCount++; + TQString scope = lockScope.firstChild().toElement().tagName(); + TQString type = lockType.firstChild().toElement().tagName(); + + setMetaData( TQString("davSupportedLockScope%1").arg(supportedLockCount), scope ); + setMetaData( TQString("davSupportedLockType%1").arg(supportedLockCount), type ); + } + } + } + } + else if ( property.tagName() == "lockdiscovery" ) + { + // Lists the available locks + davParseActiveLocks( property.elementsByTagName( "activelock" ), lockCount ); + } + else if ( property.tagName() == "resourcetype" ) + { + // Resource type. "Specifies the nature of the resource." + if ( !property.namedItem( "collection" ).toElement().isNull() ) + { + // This is a collection (directory) + isDirectory = true; + } + } + else + { + kdDebug(7113) << "Found unknown webdav property: " << property.tagName() << endl; + } + } + } + + setMetaData( "davLockCount", TQString("%1").arg(lockCount) ); + setMetaData( "davSupportedLockCount", TQString("%1").arg(supportedLockCount) ); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = isDirectory ? S_IFDIR : S_IFREG; + entry.append( atom ); + + if ( foundExecutable || isDirectory ) + { + // File was executable, or is a directory. + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = 0700; + entry.append(atom); + } + else + { + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = 0600; + entry.append(atom); + } + + if ( !isDirectory && !mimeType.isEmpty() ) + { + atom.m_uds = TDEIO::UDS_MIME_TYPE; + atom.m_str = mimeType; + entry.append( atom ); + } +} + +void HTTPProtocol::davParseActiveLocks( const TQDomNodeList& activeLocks, + uint& lockCount ) +{ + for ( uint i = 0; i < activeLocks.count(); i++ ) + { + TQDomElement activeLock = activeLocks.item(i).toElement(); + + lockCount++; + // required + TQDomElement lockScope = activeLock.namedItem( "lockscope" ).toElement(); + TQDomElement lockType = activeLock.namedItem( "locktype" ).toElement(); + TQDomElement lockDepth = activeLock.namedItem( "depth" ).toElement(); + // optional + TQDomElement lockOwner = activeLock.namedItem( "owner" ).toElement(); + TQDomElement lockTimeout = activeLock.namedItem( "timeout" ).toElement(); + TQDomElement lockToken = activeLock.namedItem( "locktoken" ).toElement(); + + if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() ) + { + // lock was properly specified + lockCount++; + TQString scope = lockScope.firstChild().toElement().tagName(); + TQString type = lockType.firstChild().toElement().tagName(); + TQString depth = lockDepth.text(); + + setMetaData( TQString("davLockScope%1").arg( lockCount ), scope ); + setMetaData( TQString("davLockType%1").arg( lockCount ), type ); + setMetaData( TQString("davLockDepth%1").arg( lockCount ), depth ); + + if ( !lockOwner.isNull() ) + setMetaData( TQString("davLockOwner%1").arg( lockCount ), lockOwner.text() ); + + if ( !lockTimeout.isNull() ) + setMetaData( TQString("davLockTimeout%1").arg( lockCount ), lockTimeout.text() ); + + if ( !lockToken.isNull() ) + { + TQDomElement tokenVal = lockScope.namedItem( "href" ).toElement(); + if ( !tokenVal.isNull() ) + setMetaData( TQString("davLockToken%1").arg( lockCount ), tokenVal.text() ); + } + } + } +} + +long HTTPProtocol::parseDateTime( const TQString& input, const TQString& type ) +{ + if ( type == "dateTime.tz" ) + { + return KRFCDate::parseDateISO8601( input ); + } + else if ( type == "dateTime.rfc1123" ) + { + return KRFCDate::parseDate( input ); + } + + // format not advertised... try to parse anyway + time_t time = KRFCDate::parseDate( input ); + if ( time != 0 ) + return time; + + return KRFCDate::parseDateISO8601( input ); +} + +TQString HTTPProtocol::davProcessLocks() +{ + if ( hasMetaData( "davLockCount" ) ) + { + TQString response("If:"); + int numLocks; + numLocks = metaData( "davLockCount" ).toInt(); + bool bracketsOpen = false; + for ( int i = 0; i < numLocks; i++ ) + { + if ( hasMetaData( TQString("davLockToken%1").arg(i) ) ) + { + if ( hasMetaData( TQString("davLockURL%1").arg(i) ) ) + { + if ( bracketsOpen ) + { + response += ")"; + bracketsOpen = false; + } + response += " <" + metaData( TQString("davLockURL%1").arg(i) ) + ">"; + } + + if ( !bracketsOpen ) + { + response += " ("; + bracketsOpen = true; + } + else + { + response += " "; + } + + if ( hasMetaData( TQString("davLockNot%1").arg(i) ) ) + response += "Not "; + + response += "<" + metaData( TQString("davLockToken%1").arg(i) ) + ">"; + } + } + + if ( bracketsOpen ) + response += ")"; + + response += "\r\n"; + return response; + } + + return TQString::null; +} + +bool HTTPProtocol::davHostOk() +{ + // FIXME needs to be reworked. Switched off for now. + return true; + + // cached? + if ( m_davHostOk ) + { + kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " true" << endl; + return true; + } + else if ( m_davHostUnsupported ) + { + kdDebug(7113) << "(" << m_pid << ") " << k_funcinfo << " false" << endl; + davError( -2 ); + return false; + } + + m_request.method = HTTP_OPTIONS; + + // query the server's capabilities generally, not for a specific URL + m_request.path = "*"; + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + // clear davVersions variable, which holds the response to the DAV: header + m_davCapabilities.clear(); + + retrieveHeader(false); + + if (m_davCapabilities.count()) + { + for (uint i = 0; i < m_davCapabilities.count(); i++) + { + bool ok; + uint verNo = m_davCapabilities[i].toUInt(&ok); + if (ok && verNo > 0 && verNo < 3) + { + m_davHostOk = true; + kdDebug(7113) << "Server supports DAV version " << verNo << "." << endl; + } + } + + if ( m_davHostOk ) + return true; + } + + m_davHostUnsupported = true; + davError( -2 ); + return false; +} + +// This function is for closing retrieveHeader( false ); requests +// Required because there may or may not be further info expected +void HTTPProtocol::davFinished() +{ + // TODO: Check with the DAV extension developers + httpClose(m_bKeepAlive); + finished(); +} + +void HTTPProtocol::mkdir( const KURL& url, int ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mkdir " << url.prettyURL() + << endl; + + if ( !checkRequestURL( url ) ) + return; + + m_request.method = DAV_MKCOL; + m_request.path = url.path(); + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveHeader( false ); + + if ( m_responseCode == 201 ) + davFinished(); + else + davError(); +} + +void HTTPProtocol::get( const KURL& url ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::get " << url.prettyURL() + << endl; + + if ( !checkRequestURL( url ) ) + return; + + m_request.method = HTTP_GET; + m_request.path = url.path(); + m_request.query = url.query(); + + TQString tmp = metaData("cache"); + if (!tmp.isEmpty()) + m_request.cache = parseCacheControl(tmp); + else + m_request.cache = DEFAULT_CACHE_CONTROL; + + m_request.passwd = url.pass(); + m_request.user = url.user(); + m_request.doProxy = m_bUseProxy; + + retrieveContent(); +} + +void HTTPProtocol::put( const KURL &url, int, bool overwrite, bool) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put " << url.prettyURL() + << endl; + + if ( !checkRequestURL( url ) ) + return; + + // Webdav hosts are capable of observing overwrite == false + if (!overwrite && m_protocol.left(6) == "webdav") { + // check to make sure this host supports WebDAV + if ( !davHostOk() ) + return; + + TQCString request; + request = "" + "" + "" + "" + "" + "" + ""; + + davSetRequest( request ); + + // WebDAV Stat or List... + m_request.method = DAV_PROPFIND; + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + m_request.davData.depth = 0; + + retrieveContent(true); + + if (m_responseCode == 207) { + error(ERR_FILE_ALREADY_EXIST, TQString::null); + return; + } + + m_bError = false; + } + + m_request.method = HTTP_PUT; + m_request.path = url.path(); + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveHeader( false ); + + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put error = " << m_bError << endl; + if (m_bError) + return; + + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::put responseCode = " << m_responseCode << endl; + + httpClose(false); // Always close connection. + + if ( (m_responseCode >= 200) && (m_responseCode < 300) ) + finished(); + else + httpError(); +} + +void HTTPProtocol::copy( const KURL& src, const KURL& dest, int, bool overwrite ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::copy " << src.prettyURL() + << " -> " << dest.prettyURL() << endl; + + if ( !checkRequestURL( dest ) || !checkRequestURL( src ) ) + return; + + // destination has to be "http(s)://..." + KURL newDest = dest; + if (newDest.protocol() == "webdavs") + newDest.setProtocol("https"); + else + newDest.setProtocol("http"); + + m_request.method = DAV_COPY; + m_request.path = src.path(); + m_request.davData.desturl = newDest.url(); + m_request.davData.overwrite = overwrite; + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveHeader( false ); + + // The server returns a HTTP/1.1 201 Created or 204 No Content on successful completion + if ( m_responseCode == 201 || m_responseCode == 204 ) + davFinished(); + else + davError(); +} + +void HTTPProtocol::rename( const KURL& src, const KURL& dest, bool overwrite ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::rename " << src.prettyURL() + << " -> " << dest.prettyURL() << endl; + + if ( !checkRequestURL( dest ) || !checkRequestURL( src ) ) + return; + + // destination has to be "http://..." + KURL newDest = dest; + if (newDest.protocol() == "webdavs") + newDest.setProtocol("https"); + else + newDest.setProtocol("http"); + + m_request.method = DAV_MOVE; + m_request.path = src.path(); + m_request.davData.desturl = newDest.url(); + m_request.davData.overwrite = overwrite; + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveHeader( false ); + + if ( m_responseCode == 301 ) + { + // Work around strict Apache-2 WebDAV implementation which refuses to cooperate + // with webdav://host/directory, instead requiring webdav://host/directory/ + // (strangely enough it accepts Destination: without a trailing slash) + + if (m_redirectLocation.protocol() == "https") + m_redirectLocation.setProtocol("webdavs"); + else + m_redirectLocation.setProtocol("webdav"); + + if ( !checkRequestURL( m_redirectLocation ) ) + return; + + m_request.method = DAV_MOVE; + m_request.path = m_redirectLocation.path(); + m_request.davData.desturl = newDest.url(); + m_request.davData.overwrite = overwrite; + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveHeader( false ); + } + + if ( m_responseCode == 201 ) + davFinished(); + else + davError(); +} + +void HTTPProtocol::del( const KURL& url, bool ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::del " << url.prettyURL() + << endl; + + if ( !checkRequestURL( url ) ) + return; + + m_request.method = HTTP_DELETE; + m_request.path = url.path(); + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveHeader( false ); + + // The server returns a HTTP/1.1 200 Ok or HTTP/1.1 204 No Content + // on successful completion + if ( m_responseCode == 200 || m_responseCode == 204 ) + davFinished(); + else + davError(); +} + +void HTTPProtocol::post( const KURL& url ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::post " + << url.prettyURL() << endl; + + if ( !checkRequestURL( url ) ) + return; + + m_request.method = HTTP_POST; + m_request.path = url.path(); + m_request.query = url.query(); + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveContent(); +} + +void HTTPProtocol::davLock( const KURL& url, const TQString& scope, + const TQString& type, const TQString& owner ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davLock " + << url.prettyURL() << endl; + + if ( !checkRequestURL( url ) ) + return; + + m_request.method = DAV_LOCK; + m_request.path = url.path(); + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + /* Create appropriate lock XML request. */ + TQDomDocument lockReq; + + TQDomElement lockInfo = lockReq.createElementNS( "DAV:", "lockinfo" ); + lockReq.appendChild( lockInfo ); + + TQDomElement lockScope = lockReq.createElement( "lockscope" ); + lockInfo.appendChild( lockScope ); + + lockScope.appendChild( lockReq.createElement( scope ) ); + + TQDomElement lockType = lockReq.createElement( "locktype" ); + lockInfo.appendChild( lockType ); + + lockType.appendChild( lockReq.createElement( type ) ); + + if ( !owner.isNull() ) { + TQDomElement ownerElement = lockReq.createElement( "owner" ); + lockReq.appendChild( ownerElement ); + + TQDomElement ownerHref = lockReq.createElement( "href" ); + ownerElement.appendChild( ownerHref ); + + ownerHref.appendChild( lockReq.createTextNode( owner ) ); + } + + // insert the document into the POST buffer + m_bufPOST = lockReq.toCString(); + + retrieveContent( true ); + + if ( m_responseCode == 200 ) { + // success + TQDomDocument multiResponse; + multiResponse.setContent( m_bufWebDavData, true ); + + TQDomElement prop = multiResponse.documentElement().namedItem( "prop" ).toElement(); + + TQDomElement lockdiscovery = prop.namedItem( "lockdiscovery" ).toElement(); + + uint lockCount = 0; + davParseActiveLocks( lockdiscovery.elementsByTagName( "activelock" ), lockCount ); + + setMetaData( "davLockCount", TQString("%1").arg( lockCount ) ); + + finished(); + + } else + davError(); +} + +void HTTPProtocol::davUnlock( const KURL& url ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davUnlock " + << url.prettyURL() << endl; + + if ( !checkRequestURL( url ) ) + return; + + m_request.method = DAV_UNLOCK; + m_request.path = url.path(); + m_request.query = TQString::null; + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + retrieveContent( true ); + + if ( m_responseCode == 200 ) + finished(); + else + davError(); +} + +TQString HTTPProtocol::davError( int code /* = -1 */, TQString url ) +{ + bool callError = false; + if ( code == -1 ) { + code = m_responseCode; + callError = true; + } + if ( code == -2 ) { + callError = true; + } + + // Huh? This looks like inverted logic to me (it doesn't make sense to me as + // written), but I'm only fixing the CVE now. -- Kevin Kofler + if ( !url.isNull() ) + url = m_request.url.prettyURL(); + + TQString action, errorString; + TDEIO::Error kError; + + // for 412 Precondition Failed + TQString ow = i18n( "Otherwise, the request would have succeeded." ); + + switch ( m_request.method ) { + case DAV_PROPFIND: + action = i18n( "retrieve property values" ); + break; + case DAV_PROPPATCH: + action = i18n( "set property values" ); + break; + case DAV_MKCOL: + action = i18n( "create the requested folder" ); + break; + case DAV_COPY: + action = i18n( "copy the specified file or folder" ); + break; + case DAV_MOVE: + action = i18n( "move the specified file or folder" ); + break; + case DAV_SEARCH: + action = i18n( "search in the specified folder" ); + break; + case DAV_LOCK: + action = i18n( "lock the specified file or folder" ); + break; + case DAV_UNLOCK: + action = i18n( "unlock the specified file or folder" ); + break; + case HTTP_DELETE: + action = i18n( "delete the specified file or folder" ); + break; + case HTTP_OPTIONS: + action = i18n( "query the server's capabilities" ); + break; + case HTTP_GET: + action = i18n( "retrieve the contents of the specified file or folder" ); + break; + case HTTP_PUT: + case HTTP_POST: + case HTTP_HEAD: + default: + // this should not happen, this function is for webdav errors only + Q_ASSERT(0); + } + + // default error message if the following code fails + kError = ERR_INTERNAL; + errorString = i18n("An unexpected error (%1) occurred while attempting to %2.") + .arg( code ).arg( action ); + + switch ( code ) + { + case -2: + // internal error: OPTIONS request did not specify DAV compliance + kError = ERR_UNSUPPORTED_PROTOCOL; + errorString = i18n("The server does not support the WebDAV protocol."); + break; + case 207: + // 207 Multi-status + { + // our error info is in the returned XML document. + // retrieve the XML document + + // there was an error retrieving the XML document. + // ironic, eh? + if ( !readBody( true ) && m_bError ) + return TQString::null; + + TQStringList errors; + TQDomDocument multiResponse; + + multiResponse.setContent( m_bufWebDavData, true ); + + TQDomElement multistatus = multiResponse.documentElement().namedItem( "multistatus" ).toElement(); + + TQDomNodeList responses = multistatus.elementsByTagName( "response" ); + + for (uint i = 0; i < responses.count(); i++) + { + int errCode; + TQString errUrl; + + TQDomElement response = responses.item(i).toElement(); + TQDomElement code = response.namedItem( "status" ).toElement(); + + if ( !code.isNull() ) + { + errCode = codeFromResponse( code.text() ); + TQDomElement href = response.namedItem( "href" ).toElement(); + if ( !href.isNull() ) + errUrl = href.text(); + errors << davError( errCode, errUrl ); + } + } + + //kError = ERR_SLAVE_DEFINED; + errorString = i18n("An error occurred while attempting to %1, %2. A " + "summary of the reasons is below.
    ").arg( action ).arg( url ); + + for ( TQStringList::Iterator it = errors.begin(); it != errors.end(); ++it ) + errorString += "
  • " + *it + "
  • "; + + errorString += "
"; + } + case 403: + case 500: // hack: Apache mod_dav returns this instead of 403 (!) + // 403 Forbidden + kError = ERR_ACCESS_DENIED; + errorString = i18n("Access was denied while attempting to %1.").arg( action ); + break; + case 405: + // 405 Method Not Allowed + if ( m_request.method == DAV_MKCOL ) + { + kError = ERR_DIR_ALREADY_EXIST; + errorString = i18n("The specified folder already exists."); + } + break; + case 409: + // 409 Conflict + kError = ERR_ACCESS_DENIED; + errorString = i18n("A resource cannot be created at the destination " + "until one or more intermediate collections (folders) " + "have been created."); + break; + case 412: + // 412 Precondition failed + if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) + { + kError = ERR_ACCESS_DENIED; + errorString = i18n("The server was unable to maintain the liveness of " + "the properties listed in the propertybehavior XML " + "element or you attempted to overwrite a file while " + "requesting that files are not overwritten. %1") + .arg( ow ); + + } + else if ( m_request.method == DAV_LOCK ) + { + kError = ERR_ACCESS_DENIED; + errorString = i18n("The requested lock could not be granted. %1").arg( ow ); + } + break; + case 415: + // 415 Unsupported Media Type + kError = ERR_ACCESS_DENIED; + errorString = i18n("The server does not support the request type of the body."); + break; + case 423: + // 423 Locked + kError = ERR_ACCESS_DENIED; + errorString = i18n("Unable to %1 because the resource is locked.").arg( action ); + break; + case 425: + // 424 Failed Dependency + errorString = i18n("This action was prevented by another error."); + break; + case 502: + // 502 Bad Gateway + if ( m_request.method == DAV_COPY || m_request.method == DAV_MOVE ) + { + kError = ERR_WRITE_ACCESS_DENIED; + errorString = i18n("Unable to %1 because the destination server refuses " + "to accept the file or folder.").arg( action ); + } + break; + case 507: + // 507 Insufficient Storage + kError = ERR_DISK_FULL; + errorString = i18n("The destination resource does not have sufficient space " + "to record the state of the resource after the execution " + "of this method."); + break; + } + + // if ( kError != ERR_SLAVE_DEFINED ) + //errorString += " (" + url + ")"; + + if ( callError ) + error( ERR_SLAVE_DEFINED, errorString ); + + return errorString; +} + +void HTTPProtocol::httpError() +{ + TQString action, errorString; + TDEIO::Error kError; + + switch ( m_request.method ) { + case HTTP_PUT: + action = i18n( "upload %1" ).arg(m_request.url.prettyURL()); + break; + default: + // this should not happen, this function is for http errors only + Q_ASSERT(0); + } + + // default error message if the following code fails + kError = ERR_INTERNAL; + errorString = i18n("An unexpected error (%1) occurred while attempting to %2.") + .arg( m_responseCode ).arg( action ); + + switch ( m_responseCode ) + { + case 403: + case 405: + case 500: // hack: Apache mod_dav returns this instead of 403 (!) + // 403 Forbidden + // 405 Method Not Allowed + kError = ERR_ACCESS_DENIED; + errorString = i18n("Access was denied while attempting to %1.").arg( action ); + break; + case 409: + // 409 Conflict + kError = ERR_ACCESS_DENIED; + errorString = i18n("A resource cannot be created at the destination " + "until one or more intermediate collections (folders) " + "have been created."); + break; + case 423: + // 423 Locked + kError = ERR_ACCESS_DENIED; + errorString = i18n("Unable to %1 because the resource is locked.").arg( action ); + break; + case 502: + // 502 Bad Gateway + kError = ERR_WRITE_ACCESS_DENIED; + errorString = i18n("Unable to %1 because the destination server refuses " + "to accept the file or folder.").arg( action ); + break; + case 507: + // 507 Insufficient Storage + kError = ERR_DISK_FULL; + errorString = i18n("The destination resource does not have sufficient space " + "to record the state of the resource after the execution " + "of this method."); + break; + } + + // if ( kError != ERR_SLAVE_DEFINED ) + //errorString += " (" + url + ")"; + + error( ERR_SLAVE_DEFINED, errorString ); +} + +bool HTTPProtocol::isOffline(const KURL &url) +{ + const int NetWorkStatusUnknown = 1; + const int NetWorkStatusOnline = 8; + TQCString replyType; + TQByteArray params; + TQByteArray reply; + + TQDataStream stream(params, IO_WriteOnly); + + if ( url.host() == TQString::fromLatin1("localhost") || url.host() == TQString::fromLatin1("127.0.0.1") || url.host() == TQString::fromLatin1("::") ) { + return false; + } + if ( dcopClient()->call( "kded", "networkstatus", "status()", + params, replyType, reply ) && (replyType == "int") ) + { + int result; + TQDataStream stream2( reply, IO_ReadOnly ); + stream2 >> result; + kdDebug(7113) << "(" << m_pid << ") networkstatus status = " << result << endl; + return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline); + } + kdDebug(7113) << "(" << m_pid << ") networkstatus " << endl; + return false; // On error, assume we are online +} + +void HTTPProtocol::multiGet(const TQByteArray &data) +{ + TQDataStream stream(data, IO_ReadOnly); + TQ_UINT32 n; + stream >> n; + + kdDebug(7113) << "(" << m_pid << ") HTTPProtcool::multiGet n = " << n << endl; + + HTTPRequest saveRequest; + if (m_bBusy) + saveRequest = m_request; + +// m_requestQueue.clear(); + for(unsigned i = 0; i < n; i++) + { + KURL url; + stream >> url >> mIncomingMetaData; + + if ( !checkRequestURL( url ) ) + continue; + + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::multi_get " << url.prettyURL() << endl; + + m_request.method = HTTP_GET; + m_request.path = url.path(); + m_request.query = url.query(); + TQString tmp = metaData("cache"); + if (!tmp.isEmpty()) + m_request.cache = parseCacheControl(tmp); + else + m_request.cache = DEFAULT_CACHE_CONTROL; + + m_request.passwd = url.pass(); + m_request.user = url.user(); + m_request.doProxy = m_bUseProxy; + + HTTPRequest *newRequest = new HTTPRequest(m_request); + m_requestQueue.append(newRequest); + } + + if (m_bBusy) + m_request = saveRequest; + + if (!m_bBusy) + { + m_bBusy = true; + while(!m_requestQueue.isEmpty()) + { + HTTPRequest *request = m_requestQueue.take(0); + m_request = *request; + delete request; + retrieveContent(); + } + m_bBusy = false; + } +} + +ssize_t HTTPProtocol::write (const void *_buf, size_t nbytes) +{ + int bytes_sent = 0; + const char* buf = static_cast(_buf); + while ( nbytes > 0 ) + { + int n = TCPSlaveBase::write(buf, nbytes); + + if ( n <= 0 ) + { + // remote side closed connection ? + if ( n == 0 ) + break; + // a valid exception(s) occurred, let's retry... + if (n < 0 && ((errno == EINTR) || (errno == EAGAIN))) + continue; + // some other error occurred ? + return -1; + } + + nbytes -= n; + buf += n; + bytes_sent += n; + } + + return bytes_sent; +} + +void HTTPProtocol::setRewindMarker() +{ + m_rewindCount = 0; +} + +void HTTPProtocol::rewind() +{ + m_linePtrUnget = m_rewindBuf, + m_lineCountUnget = m_rewindCount; + m_rewindCount = 0; +} + + +char *HTTPProtocol::gets (char *s, int size) +{ + int len=0; + char *buf=s; + char mybuf[2]={0,0}; + + while (len < size) + { + read(mybuf, 1); + if (m_bEOF) + break; + + if (m_rewindCount < sizeof(m_rewindBuf)) + m_rewindBuf[m_rewindCount++] = *mybuf; + + if (*mybuf == '\r') // Ignore! + continue; + + if ((*mybuf == '\n') || !*mybuf) + break; + + *buf++ = *mybuf; + len++; + } + + *buf=0; + return s; +} + +ssize_t HTTPProtocol::read (void *b, size_t nbytes) +{ + ssize_t ret = 0; + + if (m_lineCountUnget > 0) + { + ret = ( nbytes < m_lineCountUnget ? nbytes : m_lineCountUnget ); + m_lineCountUnget -= ret; + memcpy(b, m_linePtrUnget, ret); + m_linePtrUnget += ret; + + return ret; + } + + if (m_lineCount > 0) + { + ret = ( nbytes < m_lineCount ? nbytes : m_lineCount ); + m_lineCount -= ret; + memcpy(b, m_linePtr, ret); + m_linePtr += ret; + return ret; + } + + if (nbytes == 1) + { + ret = read(m_lineBuf, 1024); // Read into buffer + m_linePtr = m_lineBuf; + if (ret <= 0) + { + m_lineCount = 0; + return ret; + } + m_lineCount = ret; + return read(b, 1); // Read from buffer + } + + do + { + ret = TCPSlaveBase::read( b, nbytes); + if (ret == 0) + m_bEOF = true; + + } while ((ret == -1) && (errno == EAGAIN || errno == EINTR)); + + return ret; +} + +void HTTPProtocol::httpCheckConnection() +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCheckConnection: " << + " Socket status: " << m_iSock << + " Keep Alive: " << m_bKeepAlive << + " First: " << m_bFirstRequest << endl; + + if ( !m_bFirstRequest && (m_iSock != -1) ) + { + bool closeDown = false; + if ( !isConnectionValid()) + { + kdDebug(7113) << "(" << m_pid << ") Connection lost!" << endl; + closeDown = true; + } + else if ( m_request.method != HTTP_GET ) + { + closeDown = true; + } + else if ( !m_state.doProxy && !m_request.doProxy ) + { + if (m_state.hostname != m_request.hostname || + m_state.port != m_request.port || + m_state.user != m_request.user || + m_state.passwd != m_request.passwd) + closeDown = true; + } + else + { + // Keep the connection to the proxy. + if ( !(m_request.doProxy && m_state.doProxy) ) + closeDown = true; + } + + if (closeDown) + httpCloseConnection(); + } + + // Let's update our current state + m_state.hostname = m_request.hostname; + m_state.encoded_hostname = m_request.encoded_hostname; + m_state.port = m_request.port; + m_state.user = m_request.user; + m_state.passwd = m_request.passwd; + m_state.doProxy = m_request.doProxy; +} + +bool HTTPProtocol::httpOpenConnection() +{ + int errCode; + TQString errMsg; + + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpenConnection" << endl; + + setBlockConnection( true ); + // tdeio_http uses its own proxying: + KSocks::self()->disableSocks(); + + if ( m_state.doProxy ) + { + TQString proxy_host = m_proxyURL.host(); + int proxy_port = m_proxyURL.port(); + + kdDebug(7113) << "(" << m_pid << ") Connecting to proxy server: " + << proxy_host << ", port: " << proxy_port << endl; + + infoMessage( i18n("Connecting to %1...").arg(m_state.hostname) ); + + setConnectTimeout( m_proxyConnTimeout ); + + if ( !connectToHost(proxy_host, proxy_port, false) ) + { + if (userAborted()) { + error(ERR_NO_CONTENT, ""); + return false; + } + + switch ( connectResult() ) + { + case IO_LookupError: + errMsg = proxy_host; + errCode = ERR_UNKNOWN_PROXY_HOST; + break; + case IO_TimeOutError: + errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port); + errCode = ERR_SERVER_TIMEOUT; + break; + default: + errMsg = i18n("Proxy %1 at port %2").arg(proxy_host).arg(proxy_port); + errCode = ERR_COULD_NOT_CONNECT; + } + error( errCode, errMsg ); + return false; + } + } + else + { + // Apparently we don't want a proxy. let's just connect directly + setConnectTimeout(m_remoteConnTimeout); + + if ( !connectToHost(m_state.hostname, m_state.port, false ) ) + { + if (userAborted()) { + error(ERR_NO_CONTENT, ""); + return false; + } + + switch ( connectResult() ) + { + case IO_LookupError: + errMsg = m_state.hostname; + errCode = ERR_UNKNOWN_HOST; + break; + case IO_TimeOutError: + errMsg = i18n("Connection was to %1 at port %2").arg(m_state.hostname).arg(m_state.port); + errCode = ERR_SERVER_TIMEOUT; + break; + default: + errCode = ERR_COULD_NOT_CONNECT; + if (m_state.port != m_iDefaultPort) + errMsg = i18n("%1 (port %2)").arg(m_state.hostname).arg(m_state.port); + else + errMsg = m_state.hostname; + } + error( errCode, errMsg ); + return false; + } + } + + // Set our special socket option!! + int on = 1; + (void) setsockopt( m_iSock, IPPROTO_TCP, TCP_NODELAY, (char*)&on, sizeof(on) ); + + m_bFirstRequest = true; + + connected(); + return true; +} + + +/** + * This function is responsible for opening up the connection to the remote + * HTTP server and sending the header. If this requires special + * authentication or other such fun stuff, then it will handle it. This + * function will NOT receive anything from the server, however. This is in + * contrast to previous incarnations of 'httpOpen'. + * + * The reason for the change is due to one small fact: some requests require + * data to be sent in addition to the header (POST requests) and there is no + * way for this function to get that data. This function is called in the + * slotPut() or slotGet() functions which, in turn, are called (indirectly) as + * a result of a TDEIOJob::put() or TDEIOJob::get(). It is those latter functions + * which are responsible for starting up this ioslave in the first place. + * This means that 'httpOpen' is called (essentially) as soon as the ioslave + * is created -- BEFORE any data gets to this slave. + * + * The basic process now is this: + * + * 1) Open up the socket and port + * 2) Format our request/header + * 3) Send the header to the remote server + */ +bool HTTPProtocol::httpOpen() +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen" << endl; + + // Cannot have an https request without the m_bIsSSL being set! This can + // only happen if TCPSlaveBase::InitializeSSL() function failed in which it + // means the current installation does not support SSL... + if ( (m_protocol == "https" || m_protocol == "webdavs") && !m_bIsSSL ) + { + error( ERR_UNSUPPORTED_PROTOCOL, m_protocol ); + return false; + } + + m_request.fcache = 0; + m_request.bCachedRead = false; + m_request.bCachedWrite = false; + m_request.bMustRevalidate = false; + m_request.expireDate = 0; + m_request.creationDate = 0; + + if (m_request.bUseCache) + { + m_request.fcache = checkCacheEntry( ); + + bool bCacheOnly = (m_request.cache == TDEIO::CC_CacheOnly); + bool bOffline = isOffline(m_request.doProxy ? m_proxyURL : m_request.url); + if (bOffline && (m_request.cache != TDEIO::CC_Reload)) + m_request.cache = TDEIO::CC_CacheOnly; + + if (m_request.cache == CC_Reload && m_request.fcache) + { + if (m_request.fcache) + fclose(m_request.fcache); + m_request.fcache = 0; + } + if ((m_request.cache == TDEIO::CC_CacheOnly) || (m_request.cache == TDEIO::CC_Cache)) + m_request.bMustRevalidate = false; + + m_request.bCachedWrite = true; + + if (m_request.fcache && !m_request.bMustRevalidate) + { + // Cache entry is OK. + m_request.bCachedRead = true; // Cache hit. + return true; + } + else if (!m_request.fcache) + { + m_request.bMustRevalidate = false; // Cache miss + } + else + { + // Conditional cache hit. (Validate) + } + + if (bCacheOnly && bOffline) + { + error( ERR_OFFLINE_MODE, m_request.url.prettyURL() ); + return false; + } + if (bCacheOnly) + { + error( ERR_DOES_NOT_EXIST, m_request.url.prettyURL() ); + return false; + } + if (bOffline) + { + error( ERR_OFFLINE_MODE, m_request.url.prettyURL() ); + return false; + } + } + + TQString header; + TQString davHeader; + + bool moreData = false; + bool davData = false; + + // Clear out per-connection settings... + resetConnectionSettings (); + + // Check the validity of the current connection, if one exists. + httpCheckConnection(); + + if ( !m_bIsTunneled && m_bNeedTunnel ) + { + setEnableSSLTunnel( true ); + // We send a HTTP 1.0 header since some proxies refuse HTTP 1.1 and we don't + // need any HTTP 1.1 capabilities for CONNECT - Waba + header = TQString("CONNECT %1:%2 HTTP/1.0" + "\r\n").arg( m_request.encoded_hostname).arg(m_request.port); + + // Identify who you are to the proxy server! + if (!m_request.userAgent.isEmpty()) + header += "User-Agent: " + m_request.userAgent + "\r\n"; + + /* Add hostname information */ + header += "Host: " + m_state.encoded_hostname; + + if (m_state.port != m_iDefaultPort) + header += TQString(":%1").arg(m_state.port); + header += "\r\n"; + + header += proxyAuthenticationHeader(); + } + else + { + // Determine if this is a POST or GET method + switch (m_request.method) + { + case HTTP_GET: + header = "GET "; + break; + case HTTP_PUT: + header = "PUT "; + moreData = true; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case HTTP_POST: + header = "POST "; + moreData = true; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case HTTP_HEAD: + header = "HEAD "; + break; + case HTTP_DELETE: + header = "DELETE "; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case HTTP_OPTIONS: + header = "OPTIONS "; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case DAV_PROPFIND: + header = "PROPFIND "; + davData = true; + davHeader = "Depth: "; + if ( hasMetaData( "davDepth" ) ) + { + kdDebug(7113) << "Reading DAV depth from metadata: " << metaData( "davDepth" ) << endl; + davHeader += metaData( "davDepth" ); + } + else + { + if ( m_request.davData.depth == 2 ) + davHeader += "infinity"; + else + davHeader += TQString("%1").arg( m_request.davData.depth ); + } + davHeader += "\r\n"; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case DAV_PROPPATCH: + header = "PROPPATCH "; + davData = true; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case DAV_MKCOL: + header = "MKCOL "; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case DAV_COPY: + case DAV_MOVE: + header = ( m_request.method == DAV_COPY ) ? "COPY " : "MOVE "; + davHeader = "Destination: " + m_request.davData.desturl; + // infinity depth means copy recursively + // (optional for copy -> but is the desired action) + davHeader += "\r\nDepth: infinity\r\nOverwrite: "; + davHeader += m_request.davData.overwrite ? "T" : "F"; + davHeader += "\r\n"; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case DAV_LOCK: + header = "LOCK "; + davHeader = "Timeout: "; + { + uint timeout = 0; + if ( hasMetaData( "davTimeout" ) ) + timeout = metaData( "davTimeout" ).toUInt(); + if ( timeout == 0 ) + davHeader += "Infinite"; + else + davHeader += TQString("Seconds-%1").arg(timeout); + } + davHeader += "\r\n"; + m_request.bCachedWrite = false; // Do not put any result in the cache + davData = true; + break; + case DAV_UNLOCK: + header = "UNLOCK "; + davHeader = "Lock-token: " + metaData("davLockToken") + "\r\n"; + m_request.bCachedWrite = false; // Do not put any result in the cache + break; + case DAV_SEARCH: + header = "SEARCH "; + davData = true; + m_request.bCachedWrite = false; + break; + case DAV_SUBSCRIBE: + header = "SUBSCRIBE "; + m_request.bCachedWrite = false; + break; + case DAV_UNSUBSCRIBE: + header = "UNSUBSCRIBE "; + m_request.bCachedWrite = false; + break; + case DAV_POLL: + header = "POLL "; + m_request.bCachedWrite = false; + break; + default: + error (ERR_UNSUPPORTED_ACTION, TQString::null); + return false; + } + // DAV_POLL; DAV_NOTIFY + + // format the URI + if (m_state.doProxy && !m_bIsTunneled) + { + KURL u; + + if (m_protocol == "webdav") + u.setProtocol( "http" ); + else if (m_protocol == "webdavs" ) + u.setProtocol( "https" ); + else + u.setProtocol( m_protocol ); + + // For all protocols other than the once handled by this io-slave + // append the username. This fixes a long standing bug of ftp io-slave + // logging in anonymously in proxied connections even when the username + // is explicitly specified. + if (m_protocol != "http" && m_protocol != "https" && + !m_state.user.isEmpty()) + u.setUser (m_state.user); + + u.setHost( m_state.hostname ); + if (m_state.port != m_iDefaultPort) + u.setPort( m_state.port ); + u.setEncodedPathAndQuery( m_request.url.encodedPathAndQuery(0,true) ); + header += u.url(); + } + else + { + header += m_request.url.encodedPathAndQuery(0, true); + } + + header += " HTTP/1.1\r\n"; /* start header */ + + if (!m_request.userAgent.isEmpty()) + { + header += "User-Agent: "; + header += m_request.userAgent; + header += "\r\n"; + } + + if (!m_request.referrer.isEmpty()) + { + header += "Referer: "; //Don't try to correct spelling! + header += m_request.referrer; + header += "\r\n"; + } + + if ( m_request.offset > 0 ) + { + header += TQString("Range: bytes=%1-\r\n").arg(TDEIO::number(m_request.offset)); + kdDebug(7103) << "tdeio_http : Range = " << TDEIO::number(m_request.offset) << endl; + } + + if ( m_request.cache == CC_Reload ) + { + /* No caching for reload */ + header += "Pragma: no-cache\r\n"; /* for HTTP/1.0 caches */ + header += "Cache-control: no-cache\r\n"; /* for HTTP >=1.1 caches */ + } + + if (m_request.bMustRevalidate) + { + /* conditional get */ + if (!m_request.etag.isEmpty()) + header += "If-None-Match: "+m_request.etag+"\r\n"; + if (!m_request.lastModified.isEmpty()) + header += "If-Modified-Since: "+m_request.lastModified+"\r\n"; + } + + header += "Accept: "; + TQString acceptHeader = metaData("accept"); + if (!acceptHeader.isEmpty()) + header += acceptHeader; + else + header += DEFAULT_ACCEPT_HEADER; + header += "\r\n"; + +#ifdef DO_GZIP + if (m_request.allowCompressedPage) + header += "Accept-Encoding: x-gzip, x-deflate, gzip, deflate\r\n"; +#endif + + if (!m_request.charsets.isEmpty()) + header += "Accept-Charset: " + m_request.charsets + "\r\n"; + + if (!m_request.languages.isEmpty()) + header += "Accept-Language: " + m_request.languages + "\r\n"; + + + /* support for virtual hosts and required by HTTP 1.1 */ + header += "Host: " + m_state.encoded_hostname; + + if (m_state.port != m_iDefaultPort) + header += TQString(":%1").arg(m_state.port); + header += "\r\n"; + + TQString cookieStr; + TQString cookieMode = metaData("cookies").lower(); + if (cookieMode == "none") + { + m_request.cookieMode = HTTPRequest::CookiesNone; + } + else if (cookieMode == "manual") + { + m_request.cookieMode = HTTPRequest::CookiesManual; + cookieStr = metaData("setcookies"); + } + else + { + m_request.cookieMode = HTTPRequest::CookiesAuto; + if (m_request.bUseCookiejar) + cookieStr = findCookies( m_request.url.url()); + } + + if (!cookieStr.isEmpty()) + header += cookieStr + "\r\n"; + + TQString customHeader = metaData( "customHTTPHeader" ); + if (!customHeader.isEmpty()) + { + header += sanitizeCustomHTTPHeader(customHeader); + header += "\r\n"; + } + + if (m_request.method == HTTP_POST) + { + header += metaData("content-type"); + header += "\r\n"; + } + + // Only check for a cached copy if the previous + // response was NOT a 401 or 407. + // no caching for Negotiate auth. + if ( !m_request.bNoAuth && m_responseCode != 401 && m_responseCode != 407 && Authentication != AUTH_Negotiate ) + { + kdDebug(7113) << "(" << m_pid << ") Calling checkCachedAuthentication " << endl; + AuthInfo info; + info.url = m_request.url; + info.verifyPath = true; + if ( !m_request.user.isEmpty() ) + info.username = m_request.user; + if ( checkCachedAuthentication( info ) && !info.digestInfo.isEmpty() ) + { + Authentication = info.digestInfo.startsWith("Basic") ? AUTH_Basic : info.digestInfo.startsWith("NTLM") ? AUTH_NTLM : info.digestInfo.startsWith("Negotiate") ? AUTH_Negotiate : AUTH_Digest ; + m_state.user = info.username; + m_state.passwd = info.password; + m_strRealm = info.realmValue; + if ( Authentication != AUTH_NTLM && Authentication != AUTH_Negotiate ) // don't use the cached challenge + m_strAuthorization = info.digestInfo; + } + } + else + { + kdDebug(7113) << "(" << m_pid << ") Not calling checkCachedAuthentication " << endl; + } + + switch ( Authentication ) + { + case AUTH_Basic: + header += createBasicAuth(); + break; + case AUTH_Digest: + header += createDigestAuth(); + break; +#ifdef HAVE_LIBGSSAPI + case AUTH_Negotiate: + header += createNegotiateAuth(); + break; +#endif + case AUTH_NTLM: + header += createNTLMAuth(); + break; + case AUTH_None: + default: + break; + } + + /********* Only for debugging purpose *********/ + if ( Authentication != AUTH_None ) + { + kdDebug(7113) << "(" << m_pid << ") Using Authentication: " << endl; + kdDebug(7113) << "(" << m_pid << ") HOST= " << m_state.hostname << endl; + kdDebug(7113) << "(" << m_pid << ") PORT= " << m_state.port << endl; + kdDebug(7113) << "(" << m_pid << ") USER= " << m_state.user << endl; + kdDebug(7113) << "(" << m_pid << ") PASSWORD= [protected]" << endl; + kdDebug(7113) << "(" << m_pid << ") REALM= " << m_strRealm << endl; + kdDebug(7113) << "(" << m_pid << ") EXTRA= " << m_strAuthorization << endl; + } + + // Do we need to authorize to the proxy server ? + if ( m_state.doProxy && !m_bIsTunneled ) + header += proxyAuthenticationHeader(); + + // Support old HTTP/1.0 style keep-alive header for compatability + // purposes as well as performance improvements while giving end + // users the ability to disable this feature proxy servers that + // don't not support such feature, e.g. junkbuster proxy server. + if (!m_bUseProxy || m_bPersistentProxyConnection || m_bIsTunneled) + header += "Connection: Keep-Alive\r\n"; + else + header += "Connection: close\r\n"; + + if ( m_protocol == "webdav" || m_protocol == "webdavs" ) + { + header += davProcessLocks(); + + // add extra webdav headers, if supplied + TQString davExtraHeader = metaData("davHeader"); + if ( !davExtraHeader.isEmpty() ) + davHeader += davExtraHeader; + + // Set content type of webdav data + if (davData) + davHeader += "Content-Type: text/xml; charset=utf-8\r\n"; + + // add extra header elements for WebDAV + if ( !davHeader.isNull() ) + header += davHeader; + } + } + + kdDebug(7103) << "(" << m_pid << ") ============ Sending Header:" << endl; + + TQStringList headerOutput = TQStringList::split("\r\n", header); + TQStringList::Iterator it = headerOutput.begin(); + + for (; it != headerOutput.end(); it++) + kdDebug(7103) << "(" << m_pid << ") " << (*it) << endl; + + if ( !moreData && !davData) + header += "\r\n"; /* end header */ + + // Now that we have our formatted header, let's send it! + // Create a new connection to the remote machine if we do + // not already have one... + if ( m_iSock == -1) + { + if (!httpOpenConnection()) + return false; + } + + // Send the data to the remote machine... + bool sendOk = (write(header.latin1(), header.length()) == (ssize_t) header.length()); + if (!sendOk) + { + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: " + "Connection broken! (" << m_state.hostname << ")" << endl; + + // With a Keep-Alive connection this can happen. + // Just reestablish the connection. + if (m_bKeepAlive) + { + httpCloseConnection(); + return true; // Try again + } + + if (!sendOk) + { + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpen: sendOk==false." + " Connnection broken !" << endl; + error( ERR_CONNECTION_BROKEN, m_state.hostname ); + return false; + } + } + + bool res = true; + + if ( moreData || davData ) + res = sendBody(); + + infoMessage(i18n("%1 contacted. Waiting for reply...").arg(m_request.hostname)); + + return res; +} + +void HTTPProtocol::forwardHttpResponseHeader() +{ + // Send the response header if it was requested + if ( config()->readBoolEntry("PropagateHttpHeader", false) ) + { + setMetaData("HTTP-Headers", m_responseHeader.join("\n")); + sendMetaData(); + } + m_responseHeader.clear(); +} + +/** + * This function will read in the return header from the server. It will + * not read in the body of the return message. It will also not transmit + * the header to our client as the client doesn't need to know the gory + * details of HTTP headers. + */ +bool HTTPProtocol::readHeader() +{ +try_again: + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader" << endl; + + // Check + if (m_request.bCachedRead) + { + m_responseHeader << "HTTP-CACHE"; + // Read header from cache... + char buffer[4097]; + if (!fgets(buffer, 4096, m_request.fcache) ) + { + // Error, delete cache entry + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: " + << "Could not access cache to obtain mimetype!" << endl; + error( ERR_CONNECTION_BROKEN, m_state.hostname ); + return false; + } + + m_strMimeType = TQString(TQString::fromUtf8( buffer)).stripWhiteSpace(); + + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: cached " + << "data mimetype: " << m_strMimeType << endl; + + if (!fgets(buffer, 4096, m_request.fcache) ) + { + // Error, delete cache entry + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: " + << "Could not access cached data! " << endl; + error( ERR_CONNECTION_BROKEN, m_state.hostname ); + return false; + } + + m_request.strCharset = TQString(TQString::fromUtf8( buffer)).stripWhiteSpace().lower(); + setMetaData("charset", m_request.strCharset); + if (!m_request.lastModified.isEmpty()) + setMetaData("modified", m_request.lastModified); + TQString tmp; + tmp.setNum(m_request.expireDate); + setMetaData("expire-date", tmp); + tmp.setNum(m_request.creationDate); + setMetaData("cache-creation-date", tmp); + mimeType(m_strMimeType); + forwardHttpResponseHeader(); + return true; + } + + TQCString locationStr; // In case we get a redirect. + TQCString cookieStr; // In case we get a cookie. + + TQString dispositionType; // In case we get a Content-Disposition type + TQString dispositionFilename; // In case we get a Content-Disposition filename + + TQString mediaValue; + TQString mediaAttribute; + + TQStringList upgradeOffers; + + bool upgradeRequired = false; // Server demands that we upgrade to something + // This is also true if we ask to upgrade and + // the server accepts, since we are now + // committed to doing so + bool canUpgrade = false; // The server offered an upgrade + + + m_request.etag = TQString::null; + m_request.lastModified = TQString::null; + m_request.strCharset = TQString::null; + + time_t dateHeader = 0; + time_t expireDate = 0; // 0 = no info, 1 = already expired, > 1 = actual date + int currentAge = 0; + int maxAge = -1; // -1 = no max age, 0 already expired, > 0 = actual time + int maxHeaderSize = 64*1024; // 64Kb to catch DOS-attacks + + // read in 8192 bytes at a time (HTTP cookies can be quite large.) + int len = 0; + char buffer[8193]; + bool cont = false; + bool cacheValidated = false; // Revalidation was successful + bool mayCache = true; + bool hasCacheDirective = false; + bool bCanResume = false; + + if (m_iSock == -1) + { + kdDebug(7113) << "HTTPProtocol::readHeader: No connection." << endl; + return false; // Restablish connection and try again + } + + if (!waitForResponse(m_remoteRespTimeout)) + { + // No response error + error( ERR_SERVER_TIMEOUT , m_state.hostname ); + return false; + } + + setRewindMarker(); + + gets(buffer, sizeof(buffer)-1); + + if (m_bEOF || *buffer == '\0') + { + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readHeader: " + << "EOF while waiting for header start." << endl; + if (m_bKeepAlive) // Try to reestablish connection. + { + httpCloseConnection(); + return false; // Reestablish connection and try again. + } + + if (m_request.method == HTTP_HEAD) + { + // HACK + // Some web-servers fail to respond properly to a HEAD request. + // We compensate for their failure to properly implement the HTTP standard + // by assuming that they will be sending html. + kdDebug(7113) << "(" << m_pid << ") HTTPPreadHeader: HEAD -> returned " + << "mimetype: " << DEFAULT_MIME_TYPE << endl; + mimeType(TQString::fromLatin1(DEFAULT_MIME_TYPE)); + return true; + } + + kdDebug(7113) << "HTTPProtocol::readHeader: Connection broken !" << endl; + error( ERR_CONNECTION_BROKEN, m_state.hostname ); + return false; + } + + kdDebug(7103) << "(" << m_pid << ") ============ Received Response:"<< endl; + + bool noHeader = true; + HTTP_REV httpRev = HTTP_None; + int headerSize = 0; + + do + { + // strip off \r and \n if we have them + len = strlen(buffer); + + while(len && (buffer[len-1] == '\n' || buffer[len-1] == '\r')) + buffer[--len] = 0; + + // if there was only a newline then continue + if (!len) + { + kdDebug(7103) << "(" << m_pid << ") --empty--" << endl; + continue; + } + + headerSize += len; + + // We have a response header. This flag is a work around for + // servers that append a "\r\n" before the beginning of the HEADER + // response!!! It only catches x number of \r\n being placed at the + // top of the reponse... + noHeader = false; + + kdDebug(7103) << "(" << m_pid << ") \"" << buffer << "\"" << endl; + + // Save broken servers from damnation!! + char* buf = buffer; + while( *buf == ' ' ) + buf++; + + + if (buf[0] == '<') + { + // We get XML / HTTP without a proper header + // put string back + kdDebug(7103) << "tdeio_http: No valid HTTP header found! Document starts with XML/HTML tag" << endl; + + // Document starts with a tag, assume html instead of text/plain + m_strMimeType = "text/html"; + + rewind(); + break; + } + + // Store the the headers so they can be passed to the + // calling application later + m_responseHeader << TQString::fromLatin1(buf); + + if ((strncasecmp(buf, "HTTP/", 5) == 0) || + (strncasecmp(buf, "ICY ", 4) == 0)) // Shoutcast support + { + if (strncasecmp(buf, "ICY ", 4) == 0) + { + // Shoutcast support + httpRev = SHOUTCAST; + m_bKeepAlive = false; + } + else if (strncmp((buf + 5), "1.0",3) == 0) + { + httpRev = HTTP_10; + // For 1.0 servers, the server itself has to explicitly + // tell us whether it supports persistent connection or + // not. By default, we assume it does not, but we do + // send the old style header "Connection: Keep-Alive" to + // inform it that we support persistence. + m_bKeepAlive = false; + } + else if (strncmp((buf + 5), "1.1",3) == 0) + { + httpRev = HTTP_11; + } + else + { + httpRev = HTTP_Unknown; + } + + if (m_responseCode) + m_prevResponseCode = m_responseCode; + + const char* rptr = buf; + while ( *rptr && *rptr > ' ' ) + ++rptr; + m_responseCode = atoi(rptr); + + // server side errors + if (m_responseCode >= 500 && m_responseCode <= 599) + { + if (m_request.method == HTTP_HEAD) + { + ; // Ignore error + } + else + { + if (m_request.bErrorPage) + errorPage(); + else + { + error(ERR_INTERNAL_SERVER, m_request.url.prettyURL()); + return false; + } + } + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + } + // Unauthorized access + else if (m_responseCode == 401 || m_responseCode == 407) + { + // Double authorization requests, i.e. a proxy auth + // request followed immediately by a regular auth request. + if ( m_prevResponseCode != m_responseCode && + (m_prevResponseCode == 401 || m_prevResponseCode == 407) ) + saveAuthorization(); + + m_bUnauthorized = true; + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + } + // + else if (m_responseCode == 416) // Range not supported + { + m_request.offset = 0; + httpCloseConnection(); + return false; // Try again. + } + // Upgrade Required + else if (m_responseCode == 426) + { + upgradeRequired = true; + } + // Any other client errors + else if (m_responseCode >= 400 && m_responseCode <= 499) + { + // Tell that we will only get an error page here. + if (m_request.bErrorPage) + errorPage(); + else + { + error(ERR_DOES_NOT_EXIST, m_request.url.prettyURL()); + return false; + } + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + } + else if (m_responseCode == 307) + { + // 307 Temporary Redirect + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + } + else if (m_responseCode == 304) + { + // 304 Not Modified + // The value in our cache is still valid. + cacheValidated = true; + } + else if (m_responseCode >= 301 && m_responseCode<= 303) + { + // 301 Moved permanently + if (m_responseCode == 301) + setMetaData("permanent-redirect", "true"); + + // 302 Found (temporary location) + // 303 See Other + if (m_request.method != HTTP_HEAD && m_request.method != HTTP_GET) + { +#if 0 + // Reset the POST buffer to avoid a double submit + // on redirection + if (m_request.method == HTTP_POST) + m_bufPOST.resize(0); +#endif + + // NOTE: This is wrong according to RFC 2616. However, + // because most other existing user agent implementations + // treat a 301/302 response as a 303 response and preform + // a GET action regardless of what the previous method was, + // many servers have simply adapted to this way of doing + // things!! Thus, we are forced to do the same thing or we + // won't be able to retrieve these pages correctly!! See RFC + // 2616 sections 10.3.[2/3/4/8] + m_request.method = HTTP_GET; // Force a GET + } + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + } + else if ( m_responseCode == 207 ) // Multi-status (for WebDav) + { + + } + else if ( m_responseCode == 204 ) // No content + { + // error(ERR_NO_CONTENT, i18n("Data have been successfully sent.")); + // Short circuit and do nothing! + + // The original handling here was wrong, this is not an error: eg. in the + // example of a 204 No Content response to a PUT completing. + // m_bError = true; + // return false; + } + else if ( m_responseCode == 206 ) + { + if ( m_request.offset ) + bCanResume = true; + } + else if (m_responseCode == 102) // Processing (for WebDAV) + { + /*** + * This status code is given when the server expects the + * command to take significant time to complete. So, inform + * the user. + */ + infoMessage( i18n( "Server processing request, please wait..." ) ); + cont = true; + } + else if (m_responseCode == 100) + { + // We got 'Continue' - ignore it + cont = true; + } + } + + // are we allowd to resume? this will tell us + else if (strncasecmp(buf, "Accept-Ranges:", 14) == 0) { + if (strncasecmp(trimLead(buf + 14), "none", 4) == 0) + bCanResume = false; + } + // Keep Alive + else if (strncasecmp(buf, "Keep-Alive:", 11) == 0) { + TQStringList options = TQStringList::split(',', + TQString::fromLatin1(trimLead(buf+11))); + for(TQStringList::ConstIterator it = options.begin(); + it != options.end(); + it++) + { + TQString option = (*it).stripWhiteSpace().lower(); + if (option.startsWith("timeout=")) + { + m_keepAliveTimeout = option.mid(8).toInt(); + } + } + } + + // Cache control + else if (strncasecmp(buf, "Cache-Control:", 14) == 0) { + TQStringList cacheControls = TQStringList::split(',', + TQString::fromLatin1(trimLead(buf+14))); + for(TQStringList::ConstIterator it = cacheControls.begin(); + it != cacheControls.end(); + it++) + { + TQString cacheControl = (*it).stripWhiteSpace(); + if (strncasecmp(cacheControl.latin1(), "no-cache", 8) == 0) + { + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + } + else if (strncasecmp(cacheControl.latin1(), "no-store", 8) == 0) + { + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + } + else if (strncasecmp(cacheControl.latin1(), "max-age=", 8) == 0) + { + TQString age = cacheControl.mid(8).stripWhiteSpace(); + if (!age.isNull()) + maxAge = STRTOLL(age.latin1(), 0, 10); + } + } + hasCacheDirective = true; + } + + // get the size of our data + else if (strncasecmp(buf, "Content-length:", 15) == 0) { + char* len = trimLead(buf + 15); + if (len) + m_iSize = STRTOLL(len, 0, 10); + } + + else if (strncasecmp(buf, "Content-location:", 17) == 0) { + setMetaData ("content-location", + TQString::fromLatin1(trimLead(buf+17)).stripWhiteSpace()); + } + + // what type of data do we have? + else if (strncasecmp(buf, "Content-type:", 13) == 0) { + char *start = trimLead(buf + 13); + char *pos = start; + + // Increment until we encounter ";" or the end of the buffer + while ( *pos && *pos != ';' ) pos++; + + // Assign the mime-type. + m_strMimeType = TQString::fromLatin1(start, pos-start).stripWhiteSpace().lower(); + kdDebug(7113) << "(" << m_pid << ") Content-type: " << m_strMimeType << endl; + + // If we still have text, then it means we have a mime-type with a + // parameter (eg: charset=iso-8851) ; so let's get that... + while (*pos) + { + start = ++pos; + while ( *pos && *pos != '=' ) pos++; + + char *end = pos; + while ( *end && *end != ';' ) end++; + + if (*pos) + { + mediaAttribute = TQString::fromLatin1(start, pos-start).stripWhiteSpace().lower(); + mediaValue = TQString::fromLatin1(pos+1, end-pos-1).stripWhiteSpace(); + pos = end; + if (mediaValue.length() && + (mediaValue[0] == '"') && + (mediaValue[mediaValue.length()-1] == '"')) + mediaValue = mediaValue.mid(1, mediaValue.length()-2); + + kdDebug (7113) << "(" << m_pid << ") Media-Parameter Attribute: " + << mediaAttribute << endl; + kdDebug (7113) << "(" << m_pid << ") Media-Parameter Value: " + << mediaValue << endl; + + if ( mediaAttribute == "charset") + { + mediaValue = mediaValue.lower(); + m_request.strCharset = mediaValue; + } + else + { + setMetaData("media-"+mediaAttribute, mediaValue); + } + } + } + } + + // Date + else if (strncasecmp(buf, "Date:", 5) == 0) { + dateHeader = KRFCDate::parseDate(trimLead(buf+5)); + } + + // Cache management + else if (strncasecmp(buf, "ETag:", 5) == 0) { + m_request.etag = trimLead(buf+5); + } + + // Cache management + else if (strncasecmp(buf, "Expires:", 8) == 0) { + expireDate = KRFCDate::parseDate(trimLead(buf+8)); + if (!expireDate) + expireDate = 1; // Already expired + } + + // Cache management + else if (strncasecmp(buf, "Last-Modified:", 14) == 0) { + m_request.lastModified = (TQString::fromLatin1(trimLead(buf+14))).stripWhiteSpace(); + } + + // whoops.. we received a warning + else if (strncasecmp(buf, "Warning:", 8) == 0) { + //Don't use warning() here, no need to bother the user. + //Those warnings are mostly about caches. + infoMessage(trimLead(buf + 8)); + } + + // Cache management (HTTP 1.0) + else if (strncasecmp(buf, "Pragma:", 7) == 0) { + TQCString pragma = TQCString(trimLead(buf+7)).stripWhiteSpace().lower(); + if (pragma == "no-cache") + { + m_request.bCachedWrite = false; // Don't put in cache + mayCache = false; + hasCacheDirective = true; + } + } + + // The deprecated Refresh Response + else if (strncasecmp(buf,"Refresh:", 8) == 0) { + mayCache = false; // Do not cache page as it defeats purpose of Refresh tag! + setMetaData( "http-refresh", TQString::fromLatin1(trimLead(buf+8)).stripWhiteSpace() ); + } + + // In fact we should do redirection only if we got redirection code + else if (strncasecmp(buf, "Location:", 9) == 0) { + // Redirect only for 3xx status code, will ya! Thanks, pal! + if ( m_responseCode > 299 && m_responseCode < 400 ) + locationStr = TQCString(trimLead(buf+9)).stripWhiteSpace(); + } + + // Check for cookies + else if (strncasecmp(buf, "Set-Cookie", 10) == 0) { + cookieStr += buf; + cookieStr += '\n'; + } + + // check for direct authentication + else if (strncasecmp(buf, "WWW-Authenticate:", 17) == 0) { + configAuth(trimLead(buf + 17), false); + } + + // check for proxy-based authentication + else if (strncasecmp(buf, "Proxy-Authenticate:", 19) == 0) { + configAuth(trimLead(buf + 19), true); + } + + else if (strncasecmp(buf, "Upgrade:", 8) == 0) { + // Now we have to check to see what is offered for the upgrade + TQString offered = &(buf[8]); + upgradeOffers = TQStringList::split(TQRegExp("[ \n,\r\t]"), offered); + } + + // content? + else if (strncasecmp(buf, "Content-Encoding:", 17) == 0) { + // This is so wrong !! No wonder tdeio_http is stripping the + // gzip encoding from downloaded files. This solves multiple + // bug reports and caitoo's problem with downloads when such a + // header is encountered... + + // A quote from RFC 2616: + // " When present, its (Content-Encoding) value indicates what additional + // content have been applied to the entity body, and thus what decoding + // mechanism must be applied to obtain the media-type referenced by the + // Content-Type header field. Content-Encoding is primarily used to allow + // a document to be compressed without loosing the identity of its underlying + // media type. Simply put if it is specified, this is the actual mime-type + // we should use when we pull the resource !!! + addEncoding(trimLead(buf + 17), m_qContentEncodings); + } + // Refer to RFC 2616 sec 15.5/19.5.1 and RFC 2183 + else if(strncasecmp(buf, "Content-Disposition:", 20) == 0) { + char* dispositionBuf = trimLead(buf + 20); + while ( *dispositionBuf ) + { + if ( strncasecmp( dispositionBuf, "filename", 8 ) == 0 ) + { + dispositionBuf += 8; + + while ( *dispositionBuf == ' ' || *dispositionBuf == '=' ) + dispositionBuf++; + + char* bufStart = dispositionBuf; + + while ( *dispositionBuf && *dispositionBuf != ';' ) + dispositionBuf++; + + if ( dispositionBuf > bufStart ) + { + // Skip any leading quotes... + while ( *bufStart == '"' ) + bufStart++; + + // Skip any trailing quotes as well as white spaces... + while ( *(dispositionBuf-1) == ' ' || *(dispositionBuf-1) == '"') + dispositionBuf--; + + if ( dispositionBuf > bufStart ) + dispositionFilename = TQString::fromLatin1( bufStart, dispositionBuf-bufStart ); + + break; + } + } + else + { + char *bufStart = dispositionBuf; + + while ( *dispositionBuf && *dispositionBuf != ';' ) + dispositionBuf++; + + if ( dispositionBuf > bufStart ) + dispositionType = TQString::fromLatin1( bufStart, dispositionBuf-bufStart ).stripWhiteSpace(); + + while ( *dispositionBuf == ';' || *dispositionBuf == ' ' ) + dispositionBuf++; + } + } + + // Content-Dispostion is not allowed to dictate directory + // path, thus we extract the filename only. + if ( !dispositionFilename.isEmpty() ) + { + int pos = dispositionFilename.findRev( '/' ); + + if( pos > -1 ) + dispositionFilename = dispositionFilename.mid(pos+1); + + kdDebug(7113) << "(" << m_pid << ") Content-Disposition: filename=" + << dispositionFilename<< endl; + } + } + else if(strncasecmp(buf, "Content-Language:", 17) == 0) { + TQString language = TQString::fromLatin1(trimLead(buf+17)).stripWhiteSpace(); + if (!language.isEmpty()) + setMetaData("content-language", language); + } + else if (strncasecmp(buf, "Proxy-Connection:", 17) == 0) + { + if (strncasecmp(trimLead(buf + 17), "Close", 5) == 0) + m_bKeepAlive = false; + else if (strncasecmp(trimLead(buf + 17), "Keep-Alive", 10)==0) + m_bKeepAlive = true; + } + else if (strncasecmp(buf, "Link:", 5) == 0) { + // We only support Link: ; rel="type" so far + TQStringList link = TQStringList::split(";", TQString(buf) + .replace(TQRegExp("^Link:[ ]*"), + "")); + if (link.count() == 2) { + TQString rel = link[1].stripWhiteSpace(); + if (rel.startsWith("rel=\"")) { + rel = rel.mid(5, rel.length() - 6); + if (rel.lower() == "pageservices") { + TQString url = TQString(link[0].replace(TQRegExp("[<>]"),"")).stripWhiteSpace(); + setMetaData("PageServices", url); + } + } + } + } + else if (strncasecmp(buf, "P3P:", 4) == 0) { + TQString p3pstr = buf; + p3pstr = p3pstr.mid(4).simplifyWhiteSpace(); + TQStringList policyrefs, compact; + TQStringList policyfields = TQStringList::split(TQRegExp(",[ ]*"), p3pstr); + for (TQStringList::Iterator it = policyfields.begin(); + it != policyfields.end(); + ++it) { + TQStringList policy = TQStringList::split("=", *it); + + if (policy.count() == 2) { + if (policy[0].lower() == "policyref") { + policyrefs << TQString(policy[1].replace(TQRegExp("[\"\']"), "")) + .stripWhiteSpace(); + } else if (policy[0].lower() == "cp") { + // We convert to cp\ncp\ncp\n[...]\ncp to be consistent with + // other metadata sent in strings. This could be a bit more + // efficient but I'm going for correctness right now. + TQStringList cps = TQStringList::split(" ", + TQString(policy[1].replace(TQRegExp("[\"\']"), "")) + .simplifyWhiteSpace()); + + for (TQStringList::Iterator j = cps.begin(); j != cps.end(); ++j) + compact << *j; + } + } + } + + if (!policyrefs.isEmpty()) + setMetaData("PrivacyPolicy", policyrefs.join("\n")); + + if (!compact.isEmpty()) + setMetaData("PrivacyCompactPolicy", compact.join("\n")); + } + // let them tell us if we should stay alive or not + else if (strncasecmp(buf, "Connection:", 11) == 0) + { + if (strncasecmp(trimLead(buf + 11), "Close", 5) == 0) + m_bKeepAlive = false; + else if (strncasecmp(trimLead(buf + 11), "Keep-Alive", 10)==0) + m_bKeepAlive = true; + else if (strncasecmp(trimLead(buf + 11), "Upgrade", 7)==0) + { + if (m_responseCode == 101) { + // Ok, an upgrade was accepted, now we must do it + upgradeRequired = true; + } else if (upgradeRequired) { // 426 + // Nothing to do since we did it above already + } else { + // Just an offer to upgrade - no need to take it + canUpgrade = true; + } + } + } + // continue only if we know that we're HTTP/1.1 + else if ( httpRev == HTTP_11) { + // what kind of encoding do we have? transfer? + if (strncasecmp(buf, "Transfer-Encoding:", 18) == 0) { + // If multiple encodings have been applied to an entity, the + // transfer-codings MUST be listed in the order in which they + // were applied. + addEncoding(trimLead(buf + 18), m_qTransferEncodings); + } + + // md5 signature + else if (strncasecmp(buf, "Content-MD5:", 12) == 0) { + m_sContentMD5 = TQString::fromLatin1(trimLead(buf + 12)); + } + + // *** Responses to the HTTP OPTIONS method follow + // WebDAV capabilities + else if (strncasecmp(buf, "DAV:", 4) == 0) { + if (m_davCapabilities.isEmpty()) { + m_davCapabilities << TQString::fromLatin1(trimLead(buf + 4)); + } + else { + m_davCapabilities << TQString::fromLatin1(trimLead(buf + 4)); + } + } + // *** Responses to the HTTP OPTIONS method finished + } + else if ((httpRev == HTTP_None) && (strlen(buf) != 0)) + { + // Remote server does not seem to speak HTTP at all + // Put the crap back into the buffer and hope for the best + rewind(); + if (m_responseCode) + m_prevResponseCode = m_responseCode; + + m_responseCode = 200; // Fake it + httpRev = HTTP_Unknown; + m_bKeepAlive = false; + break; + } + setRewindMarker(); + + // Clear out our buffer for further use. + memset(buffer, 0, sizeof(buffer)); + + } while (!m_bEOF && (len || noHeader) && (headerSize < maxHeaderSize) && (gets(buffer, sizeof(buffer)-1))); + + // Now process the HTTP/1.1 upgrade + TQStringList::Iterator opt = upgradeOffers.begin(); + for( ; opt != upgradeOffers.end(); ++opt) { + if (*opt == "TLS/1.0") { + if(upgradeRequired) { + if (!startTLS() && !usingTLS()) { + error(ERR_UPGRADE_REQUIRED, *opt); + return false; + } + } + } else if (*opt == "HTTP/1.1") { + httpRev = HTTP_11; + } else { + // unknown + if (upgradeRequired) { + error(ERR_UPGRADE_REQUIRED, *opt); + return false; + } + } + } + + setMetaData("charset", m_request.strCharset); + + // If we do not support the requested authentication method... + if ( (m_responseCode == 401 && Authentication == AUTH_None) || + (m_responseCode == 407 && ProxyAuthentication == AUTH_None) ) + { + m_bUnauthorized = false; + if (m_request.bErrorPage) + errorPage(); + else + { + error( ERR_UNSUPPORTED_ACTION, "Unknown Authorization method!" ); + return false; + } + } + + // Fixup expire date for clock drift. + if (expireDate && (expireDate <= dateHeader)) + expireDate = 1; // Already expired. + + // Convert max-age into expireDate (overriding previous set expireDate) + if (maxAge == 0) + expireDate = 1; // Already expired. + else if (maxAge > 0) + { + if (currentAge) + maxAge -= currentAge; + if (maxAge <=0) + maxAge = 0; + expireDate = time(0) + maxAge; + } + + if (!expireDate) + { + time_t lastModifiedDate = 0; + if (!m_request.lastModified.isEmpty()) + lastModifiedDate = KRFCDate::parseDate(m_request.lastModified); + + if (lastModifiedDate) + { + long diff = static_cast(difftime(dateHeader, lastModifiedDate)); + if (diff < 0) + expireDate = time(0) + 1; + else + expireDate = time(0) + (diff / 10); + } + else + { + expireDate = time(0) + DEFAULT_CACHE_EXPIRE; + } + } + + // DONE receiving the header! + if (!cookieStr.isEmpty()) + { + if ((m_request.cookieMode == HTTPRequest::CookiesAuto) && m_request.bUseCookiejar) + { + // Give cookies to the cookiejar. + TQString domain = config()->readEntry("cross-domain"); + if (!domain.isEmpty() && isCrossDomainRequest(m_request.url.host(), domain)) + cookieStr = "Cross-Domain\n" + cookieStr; + addCookies( m_request.url.url(), cookieStr ); + } + else if (m_request.cookieMode == HTTPRequest::CookiesManual) + { + // Pass cookie to application + setMetaData("setcookies", cookieStr); + } + } + + if (m_request.bMustRevalidate) + { + m_request.bMustRevalidate = false; // Reset just in case. + if (cacheValidated) + { + // Yippie, we can use the cached version. + // Update the cache with new "Expire" headers. + fclose(m_request.fcache); + m_request.fcache = 0; + updateExpireDate( expireDate, true ); + m_request.fcache = checkCacheEntry( ); // Re-read cache entry + + if (m_request.fcache) + { + m_request.bCachedRead = true; + goto try_again; // Read header again, but now from cache. + } + else + { + // Where did our cache entry go??? + } + } + else + { + // Validation failed. Close cache. + fclose(m_request.fcache); + m_request.fcache = 0; + } + } + + // We need to reread the header if we got a '100 Continue' or '102 Processing' + if ( cont ) + { + goto try_again; + } + + // Do not do a keep-alive connection if the size of the + // response is not known and the response is not Chunked. + if (!m_bChunked && (m_iSize == NO_SIZE)) + m_bKeepAlive = false; + + if ( m_responseCode == 204 ) + { + return true; + } + + // We need to try to login again if we failed earlier + if ( m_bUnauthorized ) + { + if ( (m_responseCode == 401) || + (m_bUseProxy && (m_responseCode == 407)) + ) + { + if ( getAuthorization() ) + { + // for NTLM Authentication we have to keep the connection open! + if ( Authentication == AUTH_NTLM && m_strAuthorization.length() > 4 ) + { + m_bKeepAlive = true; + readBody( true ); + } + else if (ProxyAuthentication == AUTH_NTLM && m_strProxyAuthorization.length() > 4) + { + readBody( true ); + } + else + httpCloseConnection(); + return false; // Try again. + } + + if (m_bError) + return false; // Error out + + // Show error page... + } + m_bUnauthorized = false; + } + + // We need to do a redirect + if (!locationStr.isEmpty()) + { + KURL u(m_request.url, locationStr); + if(!u.isValid()) + { + error(ERR_MALFORMED_URL, u.prettyURL()); + return false; + } + if ((u.protocol() != "http") && (u.protocol() != "https") && + (u.protocol() != "ftp") && (u.protocol() != "webdav") && + (u.protocol() != "webdavs")) + { + redirection(u); + error(ERR_ACCESS_DENIED, u.prettyURL()); + return false; + } + + // preserve #ref: (bug 124654) + // if we were at http://host/resource1#ref, we sent a GET for "/resource1" + // if we got redirected to http://host/resource2, then we have to re-add + // the fragment: + if (m_request.url.hasRef() && !u.hasRef() && + (m_request.url.host() == u.host()) && + (m_request.url.protocol() == u.protocol())) + u.setRef(m_request.url.ref()); + + m_bRedirect = true; + m_redirectLocation = u; + + if (!m_request.id.isEmpty()) + { + sendMetaData(); + } + + kdDebug(7113) << "(" << m_pid << ") request.url: " << m_request.url.prettyURL() + << endl << "LocationStr: " << locationStr.data() << endl; + + kdDebug(7113) << "(" << m_pid << ") Requesting redirection to: " << u.prettyURL() + << endl; + + // If we're redirected to a http:// url, remember that we're doing webdav... + if (m_protocol == "webdav" || m_protocol == "webdavs") + u.setProtocol(m_protocol); + + redirection(u); + m_request.bCachedWrite = false; // Turn off caching on re-direction (DA) + mayCache = false; + } + + // Inform the job that we can indeed resume... + if ( bCanResume && m_request.offset ) + canResume(); + else + m_request.offset = 0; + + // We don't cache certain text objects + if (m_strMimeType.startsWith("text/") && + (m_strMimeType != "text/css") && + (m_strMimeType != "text/x-javascript") && + !hasCacheDirective) + { + // Do not cache secure pages or pages + // originating from password protected sites + // unless the webserver explicitly allows it. + if ( m_bIsSSL || (Authentication != AUTH_None) ) + { + m_request.bCachedWrite = false; + mayCache = false; + } + } + + // WABA: Correct for tgz files with a gzip-encoding. + // They really shouldn't put gzip in the Content-Encoding field! + // Web-servers really shouldn't do this: They let Content-Size refer + // to the size of the tgz file, not to the size of the tar file, + // while the Content-Type refers to "tar" instead of "tgz". + if (m_qContentEncodings.last() == "gzip") + { + if (m_strMimeType == "application/x-tar") + { + m_qContentEncodings.remove(m_qContentEncodings.fromLast()); + m_strMimeType = TQString::fromLatin1("application/x-tgz"); + } + else if (m_strMimeType == "application/postscript") + { + // LEONB: Adding another exception for psgz files. + // Could we use the mimelnk files instead of hardcoding all this? + m_qContentEncodings.remove(m_qContentEncodings.fromLast()); + m_strMimeType = TQString::fromLatin1("application/x-gzpostscript"); + } + else if ( m_request.allowCompressedPage && + m_strMimeType != "application/x-tgz" && + m_strMimeType != "application/x-targz" && + m_strMimeType != "application/x-gzip" && + m_request.url.path().right(6) == ".ps.gz" ) + { + m_qContentEncodings.remove(m_qContentEncodings.fromLast()); + m_strMimeType = TQString::fromLatin1("application/x-gzpostscript"); + } + else if ( (m_request.allowCompressedPage && + m_strMimeType == "text/html") + || + (m_request.allowCompressedPage && + m_strMimeType != "application/x-tgz" && + m_strMimeType != "application/x-targz" && + m_strMimeType != "application/x-gzip" && + m_request.url.path().right(3) != ".gz") + ) + { + // Unzip! + } + else + { + m_qContentEncodings.remove(m_qContentEncodings.fromLast()); + m_strMimeType = TQString::fromLatin1("application/x-gzip"); + } + } + + // We can't handle "bzip2" encoding (yet). So if we get something with + // bzip2 encoding, we change the mimetype to "application/x-bzip2". + // Note for future changes: some web-servers send both "bzip2" as + // encoding and "application/x-bzip2" as mimetype. That is wrong. + // currently that doesn't bother us, because we remove the encoding + // and set the mimetype to x-bzip2 anyway. + if (m_qContentEncodings.last() == "bzip2") + { + m_qContentEncodings.remove(m_qContentEncodings.fromLast()); + m_strMimeType = TQString::fromLatin1("application/x-bzip2"); + } + + // Convert some common mimetypes to standard KDE mimetypes + if (m_strMimeType == "application/x-targz") + m_strMimeType = TQString::fromLatin1("application/x-tgz"); + else if (m_strMimeType == "application/zip") + m_strMimeType = TQString::fromLatin1("application/x-zip"); + else if (m_strMimeType == "image/x-png") + m_strMimeType = TQString::fromLatin1("image/png"); + else if (m_strMimeType == "image/bmp") + m_strMimeType = TQString::fromLatin1("image/x-bmp"); + else if (m_strMimeType == "audio/mpeg" || m_strMimeType == "audio/x-mpeg" || m_strMimeType == "audio/mp3") + m_strMimeType = TQString::fromLatin1("audio/x-mp3"); + else if (m_strMimeType == "audio/microsoft-wave") + m_strMimeType = TQString::fromLatin1("audio/x-wav"); + else if (m_strMimeType == "audio/midi") + m_strMimeType = TQString::fromLatin1("audio/x-midi"); + else if (m_strMimeType == "image/x-xpixmap") + m_strMimeType = TQString::fromLatin1("image/x-xpm"); + else if (m_strMimeType == "application/rtf") + m_strMimeType = TQString::fromLatin1("text/rtf"); + + // Crypto ones.... + else if (m_strMimeType == "application/pkix-cert" || + m_strMimeType == "application/binary-certificate") + { + m_strMimeType = TQString::fromLatin1("application/x-x509-ca-cert"); + } + + // Prefer application/x-tgz or x-gzpostscript over application/x-gzip. + else if (m_strMimeType == "application/x-gzip") + { + if ((m_request.url.path().right(7) == ".tar.gz") || + (m_request.url.path().right(4) == ".tar")) + m_strMimeType = TQString::fromLatin1("application/x-tgz"); + if ((m_request.url.path().right(6) == ".ps.gz")) + m_strMimeType = TQString::fromLatin1("application/x-gzpostscript"); + } + + // Some webservers say "text/plain" when they mean "application/x-bzip2" + else if ((m_strMimeType == "text/plain") || (m_strMimeType == "application/octet-stream")) + { + TQString ext = m_request.url.path().right(4).upper(); + if (ext == ".BZ2") + m_strMimeType = TQString::fromLatin1("application/x-bzip2"); + else if (ext == ".PEM") + m_strMimeType = TQString::fromLatin1("application/x-x509-ca-cert"); + else if (ext == ".SWF") + m_strMimeType = TQString::fromLatin1("application/x-shockwave-flash"); + else if (ext == ".PLS") + m_strMimeType = TQString::fromLatin1("audio/x-scpls"); + else if (ext == ".WMV") + m_strMimeType = TQString::fromLatin1("video/x-ms-wmv"); + } + +#if 0 + // Even if we can't rely on content-length, it seems that we should + // never get more data than content-length. Maybe less, if the + // content-length refers to the unzipped data. + if (!m_qContentEncodings.isEmpty()) + { + // If we still have content encoding we can't rely on the Content-Length. + m_iSize = NO_SIZE; + } +#endif + + if( !dispositionType.isEmpty() ) + { + kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition type to: " + << dispositionType << endl; + setMetaData("content-disposition-type", dispositionType); + } + if( !dispositionFilename.isEmpty() ) + { + kdDebug(7113) << "(" << m_pid << ") Setting Content-Disposition filename to: " + << dispositionFilename << endl; + // ### KDE4: setting content-disposition to filename for pre 3.5.2 compatability + setMetaData("content-disposition", dispositionFilename); + setMetaData("content-disposition-filename", dispositionFilename); + } + + if (!m_request.lastModified.isEmpty()) + setMetaData("modified", m_request.lastModified); + + if (!mayCache) + { + setMetaData("no-cache", "true"); + setMetaData("expire-date", "1"); // Expired + } + else + { + TQString tmp; + tmp.setNum(expireDate); + setMetaData("expire-date", tmp); + tmp.setNum(time(0)); // Cache entry will be created shortly. + setMetaData("cache-creation-date", tmp); + } + + // Let the app know about the mime-type iff this is not + // a redirection and the mime-type string is not empty. + if (locationStr.isEmpty() && (!m_strMimeType.isEmpty() || + m_request.method == HTTP_HEAD)) + { + kdDebug(7113) << "(" << m_pid << ") Emitting mimetype " << m_strMimeType << endl; + mimeType( m_strMimeType ); + } + + // Do not move send response header before any redirection as it seems + // to screw up some sites. See BR# 150904. + forwardHttpResponseHeader(); + + if (m_request.method == HTTP_HEAD) + return true; + + // Do we want to cache this request? + if (m_request.bUseCache) + { + ::unlink( TQFile::encodeName(m_request.cef)); + if ( m_request.bCachedWrite && !m_strMimeType.isEmpty() ) + { + // Check... + createCacheEntry(m_strMimeType, expireDate); // Create a cache entry + if (!m_request.fcache) + { + m_request.bCachedWrite = false; // Error creating cache entry. + kdDebug(7113) << "(" << m_pid << ") Error creating cache entry for " << m_request.url.prettyURL()<<"!\n"; + } + m_request.expireDate = expireDate; + m_maxCacheSize = config()->readNumEntry("MaxCacheSize", DEFAULT_MAX_CACHE_SIZE) / 2; + } + } + + if (m_request.bCachedWrite && !m_strMimeType.isEmpty()) + kdDebug(7113) << "(" << m_pid << ") Cache, adding \"" << m_request.url.prettyURL() << "\"" << endl; + else if (m_request.bCachedWrite && m_strMimeType.isEmpty()) + kdDebug(7113) << "(" << m_pid << ") Cache, pending \"" << m_request.url.prettyURL() << "\"" << endl; + else + kdDebug(7113) << "(" << m_pid << ") Cache, not adding \"" << m_request.url.prettyURL() << "\"" << endl; + return true; +} + + +void HTTPProtocol::addEncoding(TQString encoding, TQStringList &encs) +{ + encoding = encoding.stripWhiteSpace().lower(); + // Identity is the same as no encoding + if (encoding == "identity") { + return; + } else if (encoding == "8bit") { + // Strange encoding returned by http://linac.ikp.physik.tu-darmstadt.de + return; + } else if (encoding == "chunked") { + m_bChunked = true; + // Anyone know of a better way to handle unknown sizes possibly/ideally with unsigned ints? + //if ( m_cmd != CMD_COPY ) + m_iSize = NO_SIZE; + } else if ((encoding == "x-gzip") || (encoding == "gzip")) { + encs.append(TQString::fromLatin1("gzip")); + } else if ((encoding == "x-bzip2") || (encoding == "bzip2")) { + encs.append(TQString::fromLatin1("bzip2")); // Not yet supported! + } else if ((encoding == "x-deflate") || (encoding == "deflate")) { + encs.append(TQString::fromLatin1("deflate")); + } else { + kdDebug(7113) << "(" << m_pid << ") Unknown encoding encountered. " + << "Please write code. Encoding = \"" << encoding + << "\"" << endl; + } +} + +bool HTTPProtocol::sendBody() +{ + int result=-1; + int length=0; + + infoMessage( i18n( "Requesting data to send" ) ); + + // m_bufPOST will NOT be empty iff authentication was required before posting + // the data OR a re-connect is requested from ::readHeader because the + // connection was lost for some reason. + if ( !m_bufPOST.isNull() ) + { + kdDebug(7113) << "(" << m_pid << ") POST'ing saved data..." << endl; + + result = 0; + length = m_bufPOST.size(); + } + else + { + kdDebug(7113) << "(" << m_pid << ") POST'ing live data..." << endl; + + TQByteArray buffer; + int old_size; + + m_bufPOST.resize(0); + do + { + dataReq(); // Request for data + result = readData( buffer ); + if ( result > 0 ) + { + length += result; + old_size = m_bufPOST.size(); + m_bufPOST.resize( old_size+result ); + memcpy( m_bufPOST.data()+ old_size, buffer.data(), buffer.size() ); + buffer.resize(0); + } + } while ( result > 0 ); + } + + if ( result < 0 ) + { + error( ERR_ABORTED, m_request.hostname ); + return false; + } + + infoMessage( i18n( "Sending data to %1" ).arg( m_request.hostname ) ); + + TQString size = TQString ("Content-Length: %1\r\n\r\n").arg(length); + kdDebug( 7113 ) << "(" << m_pid << ")" << size << endl; + + // Send the content length... + bool sendOk = (write(size.latin1(), size.length()) == (ssize_t) size.length()); + if (!sendOk) + { + kdDebug( 7113 ) << "(" << m_pid << ") Connection broken when sending " + << "content length: (" << m_state.hostname << ")" << endl; + error( ERR_CONNECTION_BROKEN, m_state.hostname ); + return false; + } + + // Send the data... + // kdDebug( 7113 ) << "(" << m_pid << ") POST DATA: " << TQCString(m_bufPOST) << endl; + sendOk = (write(m_bufPOST.data(), m_bufPOST.size()) == (ssize_t) m_bufPOST.size()); + if (!sendOk) + { + kdDebug(7113) << "(" << m_pid << ") Connection broken when sending message body: (" + << m_state.hostname << ")" << endl; + error( ERR_CONNECTION_BROKEN, m_state.hostname ); + return false; + } + + return true; +} + +void HTTPProtocol::httpClose( bool keepAlive ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose" << endl; + + if (m_request.fcache) + { + fclose(m_request.fcache); + m_request.fcache = 0; + if (m_request.bCachedWrite) + { + TQString filename = m_request.cef + ".new"; + ::unlink( TQFile::encodeName(filename) ); + } + } + + // Only allow persistent connections for GET requests. + // NOTE: we might even want to narrow this down to non-form + // based submit requests which will require a meta-data from + // tdehtml. + if (keepAlive && (!m_bUseProxy || + m_bPersistentProxyConnection || m_bIsTunneled)) + { + if (!m_keepAliveTimeout) + m_keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT; + else if (m_keepAliveTimeout > 2*DEFAULT_KEEP_ALIVE_TIMEOUT) + m_keepAliveTimeout = 2*DEFAULT_KEEP_ALIVE_TIMEOUT; + + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpClose: keep alive (" << m_keepAliveTimeout << ")" << endl; + TQByteArray data; + TQDataStream stream( data, IO_WriteOnly ); + stream << int(99); // special: Close connection + setTimeoutSpecialCommand(m_keepAliveTimeout, data); + return; + } + + httpCloseConnection(); +} + +void HTTPProtocol::closeConnection() +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::closeConnection" << endl; + httpCloseConnection (); +} + +void HTTPProtocol::httpCloseConnection () +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpCloseConnection" << endl; + m_bIsTunneled = false; + m_bKeepAlive = false; + closeDescriptor(); + setTimeoutSpecialCommand(-1); // Cancel any connection timeout +} + +void HTTPProtocol::slave_status() +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::slave_status" << endl; + + if ( m_iSock != -1 && !isConnectionValid() ) + httpCloseConnection(); + + slaveStatus( m_state.hostname, (m_iSock != -1) ); +} + +void HTTPProtocol::mimetype( const KURL& url ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::mimetype: " + << url.prettyURL() << endl; + + if ( !checkRequestURL( url ) ) + return; + + m_request.method = HTTP_HEAD; + m_request.path = url.path(); + m_request.query = url.query(); + m_request.cache = CC_Cache; + m_request.doProxy = m_bUseProxy; + + retrieveHeader(); + + kdDebug(7113) << "(" << m_pid << ") http: mimetype = " << m_strMimeType + << endl; +} + +void HTTPProtocol::special( const TQByteArray &data ) +{ + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::special" << endl; + + int tmp; + TQDataStream stream(data, IO_ReadOnly); + + stream >> tmp; + switch (tmp) { + case 1: // HTTP POST + { + KURL url; + stream >> url; + post( url ); + break; + } + case 2: // cache_update + { + KURL url; + bool no_cache; + time_t expireDate; + stream >> url >> no_cache >> expireDate; + cacheUpdate( url, no_cache, expireDate ); + break; + } + case 5: // WebDAV lock + { + KURL url; + TQString scope, type, owner; + stream >> url >> scope >> type >> owner; + davLock( url, scope, type, owner ); + break; + } + case 6: // WebDAV unlock + { + KURL url; + stream >> url; + davUnlock( url ); + break; + } + case 7: // Generic WebDAV + { + KURL url; + int method; + stream >> url >> method; + davGeneric( url, (TDEIO::HTTP_METHOD) method ); + break; + } + case 99: // Close Connection + { + httpCloseConnection(); + break; + } + default: + // Some command we don't understand. + // Just ignore it, it may come from some future version of KDE. + break; + } +} + +/** + * Read a chunk from the data stream. + */ +int HTTPProtocol::readChunked() +{ + if ((m_iBytesLeft == 0) || (m_iBytesLeft == NO_SIZE)) + { + setRewindMarker(); + + m_bufReceive.resize(4096); + + if (!gets(m_bufReceive.data(), m_bufReceive.size()-1)) + { + kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl; + return -1; + } + // We could have got the CRLF of the previous chunk. + // If so, try again. + if (m_bufReceive[0] == '\0') + { + if (!gets(m_bufReceive.data(), m_bufReceive.size()-1)) + { + kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk header" << endl; + return -1; + } + } + + // m_bEOF is set to true when read called from gets returns 0. For chunked reading 0 + // means end of chunked transfer and not error. See RFC 2615 section 3.6.1 + #if 0 + if (m_bEOF) + { + kdDebug(7113) << "(" << m_pid << ") EOF on Chunk header" << endl; + return -1; + } + #endif + + long long trunkSize = STRTOLL(m_bufReceive.data(), 0, 16); + if (trunkSize < 0) + { + kdDebug(7113) << "(" << m_pid << ") Negative chunk size" << endl; + return -1; + } + m_iBytesLeft = trunkSize; + + // kdDebug(7113) << "(" << m_pid << ") Chunk size = " << m_iBytesLeft << " bytes" << endl; + + if (m_iBytesLeft == 0) + { + // Last chunk. + // Skip trailers. + do { + // Skip trailer of last chunk. + if (!gets(m_bufReceive.data(), m_bufReceive.size()-1)) + { + kdDebug(7113) << "(" << m_pid << ") gets() failure on Chunk trailer" << endl; + return -1; + } + // kdDebug(7113) << "(" << m_pid << ") Chunk trailer = \"" << m_bufReceive.data() << "\"" << endl; + } + while (strlen(m_bufReceive.data()) != 0); + + return 0; + } + } + + int bytesReceived = readLimited(); + if (!m_iBytesLeft) + m_iBytesLeft = NO_SIZE; // Don't stop, continue with next chunk + + // kdDebug(7113) << "(" << m_pid << ") readChunked: BytesReceived=" << bytesReceived << endl; + return bytesReceived; +} + +int HTTPProtocol::readLimited() +{ + if (!m_iBytesLeft) + return 0; + + m_bufReceive.resize(4096); + + int bytesReceived; + int bytesToReceive; + + if (m_iBytesLeft > m_bufReceive.size()) + bytesToReceive = m_bufReceive.size(); + else + bytesToReceive = m_iBytesLeft; + + bytesReceived = read(m_bufReceive.data(), bytesToReceive); + + if (bytesReceived <= 0) + return -1; // Error: connection lost + + m_iBytesLeft -= bytesReceived; + return bytesReceived; +} + +int HTTPProtocol::readUnlimited() +{ + if (m_bKeepAlive) + { + kdDebug(7113) << "(" << m_pid << ") Unbounded datastream on a Keep " + << "alive connection!" << endl; + m_bKeepAlive = false; + } + + m_bufReceive.resize(4096); + + int result = read(m_bufReceive.data(), m_bufReceive.size()); + if (result > 0) + return result; + + m_bEOF = true; + m_iBytesLeft = 0; + return 0; +} + +void HTTPProtocol::slotData(const TQByteArray &_d) +{ + if (!_d.size()) + { + m_bEOD = true; + return; + } + + if (m_iContentLeft != NO_SIZE) + { + if (m_iContentLeft >= _d.size()) + m_iContentLeft -= _d.size(); + else + m_iContentLeft = NO_SIZE; + } + + TQByteArray d = _d; + if ( !m_dataInternal ) + { + // If a broken server does not send the mime-type, + // we try to id it from the content before dealing + // with the content itself. + if ( m_strMimeType.isEmpty() && !m_bRedirect && + !( m_responseCode >= 300 && m_responseCode <=399) ) + { + kdDebug(7113) << "(" << m_pid << ") Determining mime-type from content..." << endl; + int old_size = m_mimeTypeBuffer.size(); + m_mimeTypeBuffer.resize( old_size + d.size() ); + memcpy( m_mimeTypeBuffer.data() + old_size, d.data(), d.size() ); + if ( (m_iBytesLeft != NO_SIZE) && (m_iBytesLeft > 0) + && (m_mimeTypeBuffer.size() < 1024) ) + { + m_cpMimeBuffer = true; + return; // Do not send up the data since we do not yet know its mimetype! + } + + kdDebug(7113) << "(" << m_pid << ") Mimetype buffer size: " << m_mimeTypeBuffer.size() + << endl; + + KMimeMagicResult *result; + result = KMimeMagic::self()->findBufferFileType( m_mimeTypeBuffer, + m_request.url.fileName() ); + if( result ) + { + m_strMimeType = result->mimeType(); + kdDebug(7113) << "(" << m_pid << ") Mimetype from content: " + << m_strMimeType << endl; + } + + if ( m_strMimeType.isEmpty() ) + { + m_strMimeType = TQString::fromLatin1( DEFAULT_MIME_TYPE ); + kdDebug(7113) << "(" << m_pid << ") Using default mimetype: " + << m_strMimeType << endl; + } + + if ( m_request.bCachedWrite ) + { + createCacheEntry( m_strMimeType, m_request.expireDate ); + if (!m_request.fcache) + m_request.bCachedWrite = false; + } + + if ( m_cpMimeBuffer ) + { + // Do not make any assumption about the state of the TQByteArray we received. + // Fix the crash described by BR# 130104. + d.detach(); + d.resize(0); + d.resize(m_mimeTypeBuffer.size()); + memcpy( d.data(), m_mimeTypeBuffer.data(), + d.size() ); + } + mimeType(m_strMimeType); + m_mimeTypeBuffer.resize(0); + } + + data( d ); + if (m_request.bCachedWrite && m_request.fcache) + writeCacheEntry(d.data(), d.size()); + } + else + { + uint old_size = m_bufWebDavData.size(); + m_bufWebDavData.resize (old_size + d.size()); + memcpy (m_bufWebDavData.data() + old_size, d.data(), d.size()); + } +} + +/** + * This function is our "receive" function. It is responsible for + * downloading the message (not the header) from the HTTP server. It + * is called either as a response to a client's TDEIOJob::dataEnd() + * (meaning that the client is done sending data) or by 'httpOpen()' + * (if we are in the process of a PUT/POST request). It can also be + * called by a webDAV function, to receive stat/list/property/etc. + * data; in this case the data is stored in m_bufWebDavData. + */ +bool HTTPProtocol::readBody( bool dataInternal /* = false */ ) +{ + if (m_responseCode == 204) + return true; + + m_bEOD = false; + // Note that when dataInternal is true, we are going to: + // 1) save the body data to a member variable, m_bufWebDavData + // 2) _not_ advertise the data, speed, size, etc., through the + // corresponding functions. + // This is used for returning data to WebDAV. + m_dataInternal = dataInternal; + if ( dataInternal ) + m_bufWebDavData.resize (0); + + // Check if we need to decode the data. + // If we are in copy mode, then use only transfer decoding. + bool useMD5 = !m_sContentMD5.isEmpty(); + + // Deal with the size of the file. + TDEIO::filesize_t sz = m_request.offset; + if ( sz ) + m_iSize += sz; + + // Update the application with total size except when + // it is compressed, or when the data is to be handled + // internally (webDAV). If compressed we have to wait + // until we uncompress to find out the actual data size + if ( !dataInternal ) { + if ( (m_iSize > 0) && (m_iSize != NO_SIZE)) { + totalSize(m_iSize); + infoMessage( i18n( "Retrieving %1 from %2...").arg(TDEIO::convertSize(m_iSize)) + .arg( m_request.hostname ) ); + } + else + { + totalSize ( 0 ); + } + } + else + infoMessage( i18n( "Retrieving from %1..." ).arg( m_request.hostname ) ); + + if (m_request.bCachedRead) + { + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: read data from cache!" << endl; + m_request.bCachedWrite = false; + + char buffer[ MAX_IPC_SIZE ]; + + m_iContentLeft = NO_SIZE; + + // Jippie! It's already in the cache :-) + while (!feof(m_request.fcache) && !ferror(m_request.fcache)) + { + int nbytes = fread( buffer, 1, MAX_IPC_SIZE, m_request.fcache); + + if (nbytes > 0) + { + m_bufReceive.setRawData( buffer, nbytes); + slotData( m_bufReceive ); + m_bufReceive.resetRawData( buffer, nbytes ); + sz += nbytes; + } + } + + m_bufReceive.resize( 0 ); + + if ( !dataInternal ) + { + processedSize( sz ); + data( TQByteArray() ); + } + + return true; + } + + + if (m_iSize != NO_SIZE) + m_iBytesLeft = m_iSize - sz; + else + m_iBytesLeft = NO_SIZE; + + m_iContentLeft = m_iBytesLeft; + + if (m_bChunked) + m_iBytesLeft = NO_SIZE; + + kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::readBody: retrieve data. " + << TDEIO::number(m_iBytesLeft) << " left." << endl; + + // Main incoming loop... Gather everything while we can... + m_cpMimeBuffer = false; + m_mimeTypeBuffer.resize(0); + struct timeval last_tv; + gettimeofday( &last_tv, 0L ); + + HTTPFilterChain chain; + + TQObject::connect(&chain, TQT_SIGNAL(output(const TQByteArray &)), + this, TQT_SLOT(slotData(const TQByteArray &))); + TQObject::connect(&chain, TQT_SIGNAL(error(int, const TQString &)), + this, TQT_SLOT(error(int, const TQString &))); + + // decode all of the transfer encodings + while (!m_qTransferEncodings.isEmpty()) + { + TQString enc = m_qTransferEncodings.last(); + m_qTransferEncodings.remove(m_qTransferEncodings.fromLast()); + if ( enc == "gzip" ) + chain.addFilter(new HTTPFilterGZip); + else if ( enc == "deflate" ) + chain.addFilter(new HTTPFilterDeflate); + } + + // From HTTP 1.1 Draft 6: + // The MD5 digest is computed based on the content of the entity-body, + // including any content-coding that has been applied, but not including + // any transfer-encoding applied to the message-body. If the message is + // received with a transfer-encoding, that encoding MUST be removed + // prior to checking the Content-MD5 value against the received entity. + HTTPFilterMD5 *md5Filter = 0; + if ( useMD5 ) + { + md5Filter = new HTTPFilterMD5; + chain.addFilter(md5Filter); + } + + // now decode all of the content encodings + // -- Why ?? We are not + // -- a proxy server, be a client side implementation!! The applications + // -- are capable of determinig how to extract the encoded implementation. + // WB: That's a misunderstanding. We are free to remove the encoding. + // WB: Some braindead www-servers however, give .tgz files an encoding + // WB: of "gzip" (or even "x-gzip") and a content-type of "applications/tar" + // WB: They shouldn't do that. We can work around that though... + while (!m_qContentEncodings.isEmpty()) + { + TQString enc = m_qContentEncodings.last(); + m_qContentEncodings.remove(m_qContentEncodings.fromLast()); + if ( enc == "gzip" ) + chain.addFilter(new HTTPFilterGZip); + else if ( enc == "deflate" ) + chain.addFilter(new HTTPFilterDeflate); + } + + while (!m_bEOF) + { + int bytesReceived; + + if (m_bChunked) + bytesReceived = readChunked(); + else if (m_iSize != NO_SIZE) + bytesReceived = readLimited(); + else + bytesReceived = readUnlimited(); + + // make sure that this wasn't an error, first + // kdDebug(7113) << "(" << (int) m_pid << ") readBody: bytesReceived: " + // << (int) bytesReceived << " m_iSize: " << (int) m_iSize << " Chunked: " + // << (int) m_bChunked << " BytesLeft: "<< (int) m_iBytesLeft << endl; + if (bytesReceived == -1) + { + if (m_iContentLeft == 0) + { + // gzip'ed data sometimes reports a too long content-length. + // (The length of the unzipped data) + m_iBytesLeft = 0; + break; + } + // Oh well... log an error and bug out + kdDebug(7113) << "(" << m_pid << ") readBody: bytesReceived==-1 sz=" << (int)sz + << " Connnection broken !" << endl; + error(ERR_CONNECTION_BROKEN, m_state.hostname); + return false; + } + + // I guess that nbytes == 0 isn't an error.. but we certainly + // won't work with it! + if (bytesReceived > 0) + { + // Important: truncate the buffer to the actual size received! + // Otherwise garbage will be passed to the app + m_bufReceive.truncate( bytesReceived ); + + chain.slotInput(m_bufReceive); + + if (m_bError) + return false; + + sz += bytesReceived; + if (!dataInternal) + processedSize( sz ); + } + m_bufReceive.resize(0); // res + + if (m_iBytesLeft && m_bEOD && !m_bChunked) + { + // gzip'ed data sometimes reports a too long content-length. + // (The length of the unzipped data) + m_iBytesLeft = 0; + } + + if (m_iBytesLeft == 0) + { + kdDebug(7113) << "("<call( "kded", "kcookiejar", "findCookies(TQString,long int)", + params, replyType, reply ) ) + { + kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl; + return result; + } + if ( replyType == "TQString" ) + { + TQDataStream stream2( reply, IO_ReadOnly ); + stream2 >> result; + } + else + { + kdError(7113) << "(" << m_pid << ") DCOP function findCookies(...) returns " + << replyType << ", expected TQString" << endl; + } + return result; +} + +/******************************* CACHING CODE ****************************/ + + +void HTTPProtocol::cacheUpdate( const KURL& url, bool no_cache, time_t expireDate) +{ + if ( !checkRequestURL( url ) ) + return; + + m_request.path = url.path(); + m_request.query = url.query(); + m_request.cache = CC_Reload; + m_request.doProxy = m_bUseProxy; + + if (no_cache) + { + m_request.fcache = checkCacheEntry( ); + if (m_request.fcache) + { + fclose(m_request.fcache); + m_request.fcache = 0; + ::unlink( TQFile::encodeName(m_request.cef) ); + } + } + else + { + updateExpireDate( expireDate ); + } + finished(); +} + +// !START SYNC! +// The following code should be kept in sync +// with the code in http_cache_cleaner.cpp + +FILE* HTTPProtocol::checkCacheEntry( bool readWrite) +{ + const TQChar separator = '_'; + + TQString CEF = m_request.path; + + int p = CEF.find('/'); + + while(p != -1) + { + CEF[p] = separator; + p = CEF.find('/', p); + } + + TQString host = m_request.hostname.lower(); + CEF = host + CEF + '_'; + + TQString dir = m_strCacheDir; + if (dir[dir.length()-1] != '/') + dir += "/"; + + int l = host.length(); + for(int i = 0; i < l; i++) + { + if (host[i].isLetter() && (host[i] != 'w')) + { + dir += host[i]; + break; + } + } + if (dir[dir.length()-1] == '/') + dir += "0"; + + unsigned long hash = 0x00000000; + TQCString u = m_request.url.url().latin1(); + for(int i = u.length(); i--;) + { + hash = (hash * 12211 + static_cast(u.at(i))) % 2147483563; + } + + TQString hashString; + hashString.sprintf("%08lx", hash); + + CEF = CEF + hashString; + + CEF = dir + "/" + CEF; + + m_request.cef = CEF; + + const char *mode = (readWrite ? "r+" : "r"); + + FILE *fs = fopen( TQFile::encodeName(CEF), mode); // Open for reading and writing + if (!fs) + return 0; + + char buffer[401]; + bool ok = true; + + // CacheRevision + if (ok && (!fgets(buffer, 400, fs))) + ok = false; + if (ok && (strcmp(buffer, CACHE_REVISION) != 0)) + ok = false; + + time_t date; + time_t currentDate = time(0); + + // URL + if (ok && (!fgets(buffer, 400, fs))) + ok = false; + if (ok) + { + int l = strlen(buffer); + if (l>0) + buffer[l-1] = 0; // Strip newline + if (m_request.url.url() != buffer) + { + ok = false; // Hash collision + } + } + + // Creation Date + if (ok && (!fgets(buffer, 400, fs))) + ok = false; + if (ok) + { + date = (time_t) strtoul(buffer, 0, 10); + m_request.creationDate = date; + if (m_maxCacheAge && (difftime(currentDate, date) > m_maxCacheAge)) + { + m_request.bMustRevalidate = true; + m_request.expireDate = currentDate; + } + } + + // Expiration Date + m_request.cacheExpireDateOffset = ftell(fs); + if (ok && (!fgets(buffer, 400, fs))) + ok = false; + if (ok) + { + if (m_request.cache == CC_Verify) + { + date = (time_t) strtoul(buffer, 0, 10); + // After the expire date we need to revalidate. + if (!date || difftime(currentDate, date) >= 0) + m_request.bMustRevalidate = true; + m_request.expireDate = date; + } + else if (m_request.cache == CC_Refresh) + { + m_request.bMustRevalidate = true; + m_request.expireDate = currentDate; + } + } + + // ETag + if (ok && (!fgets(buffer, 400, fs))) + ok = false; + if (ok) + { + m_request.etag = TQString(buffer).stripWhiteSpace(); + } + + // Last-Modified + if (ok && (!fgets(buffer, 400, fs))) + ok = false; + if (ok) + { + m_request.lastModified = TQString(buffer).stripWhiteSpace(); + } + + if (ok) + return fs; + + fclose(fs); + unlink( TQFile::encodeName(CEF)); + return 0; +} + +void HTTPProtocol::updateExpireDate(time_t expireDate, bool updateCreationDate) +{ + bool ok = true; + + FILE *fs = checkCacheEntry(true); + if (fs) + { + TQString date; + char buffer[401]; + time_t creationDate; + + fseek(fs, 0, SEEK_SET); + if (ok && !fgets(buffer, 400, fs)) + ok = false; + if (ok && !fgets(buffer, 400, fs)) + ok = false; + long cacheCreationDateOffset = ftell(fs); + if (ok && !fgets(buffer, 400, fs)) + ok = false; + creationDate = strtoul(buffer, 0, 10); + if (!creationDate) + ok = false; + + if (updateCreationDate) + { + if (!ok || fseek(fs, cacheCreationDateOffset, SEEK_SET)) + return; + TQString date; + date.setNum( time(0) ); + date = date.leftJustify(16); + fputs(date.latin1(), fs); // Creation date + fputc('\n', fs); + } + + if (expireDate>(30*365*24*60*60)) + { + // expire date is a really a big number, it can't be + // a relative date. + date.setNum( expireDate ); + } + else + { + // expireDate before 2000. those values must be + // interpreted as relative expiration dates from + // tags. + // so we have to scan the creation time and add + // it to the expiryDate + date.setNum( creationDate + expireDate ); + } + date = date.leftJustify(16); + if (!ok || fseek(fs, m_request.cacheExpireDateOffset, SEEK_SET)) + return; + fputs(date.latin1(), fs); // Expire date + fseek(fs, 0, SEEK_END); + fclose(fs); + } +} + +void HTTPProtocol::createCacheEntry( const TQString &mimetype, time_t expireDate) +{ + TQString dir = m_request.cef; + int p = dir.findRev('/'); + if (p == -1) return; // Error. + dir.truncate(p); + + // Create file + (void) ::mkdir( TQFile::encodeName(dir), 0700 ); + + TQString filename = m_request.cef + ".new"; // Create a new cache entryexpireDate + +// kdDebug( 7103 ) << "creating new cache entry: " << filename << endl; + + m_request.fcache = fopen( TQFile::encodeName(filename), "w"); + if (!m_request.fcache) + { + kdWarning(7113) << "(" << m_pid << ")createCacheEntry: opening " << filename << " failed." << endl; + return; // Error. + } + + fputs(CACHE_REVISION, m_request.fcache); // Revision + + fputs(m_request.url.url().latin1(), m_request.fcache); // Url + fputc('\n', m_request.fcache); + + TQString date; + m_request.creationDate = time(0); + date.setNum( m_request.creationDate ); + date = date.leftJustify(16); + fputs(date.latin1(), m_request.fcache); // Creation date + fputc('\n', m_request.fcache); + + date.setNum( expireDate ); + date = date.leftJustify(16); + fputs(date.latin1(), m_request.fcache); // Expire date + fputc('\n', m_request.fcache); + + if (!m_request.etag.isEmpty()) + fputs(m_request.etag.latin1(), m_request.fcache); //ETag + fputc('\n', m_request.fcache); + + if (!m_request.lastModified.isEmpty()) + fputs(m_request.lastModified.latin1(), m_request.fcache); // Last modified + fputc('\n', m_request.fcache); + + fputs(mimetype.latin1(), m_request.fcache); // Mimetype + fputc('\n', m_request.fcache); + + if (!m_request.strCharset.isEmpty()) + fputs(m_request.strCharset.latin1(), m_request.fcache); // Charset + fputc('\n', m_request.fcache); + + return; +} +// The above code should be kept in sync +// with the code in http_cache_cleaner.cpp +// !END SYNC! + +void HTTPProtocol::writeCacheEntry( const char *buffer, int nbytes) +{ + if (fwrite( buffer, nbytes, 1, m_request.fcache) != 1) + { + kdWarning(7113) << "(" << m_pid << ") writeCacheEntry: writing " << nbytes << " bytes failed." << endl; + fclose(m_request.fcache); + m_request.fcache = 0; + TQString filename = m_request.cef + ".new"; + ::unlink( TQFile::encodeName(filename) ); + return; + } + long file_pos = ftell( m_request.fcache ) / 1024; + if ( file_pos > m_maxCacheSize ) + { + kdDebug(7113) << "writeCacheEntry: File size reaches " << file_pos + << "Kb, exceeds cache limits. (" << m_maxCacheSize << "Kb)" << endl; + fclose(m_request.fcache); + m_request.fcache = 0; + TQString filename = m_request.cef + ".new"; + ::unlink( TQFile::encodeName(filename) ); + return; + } +} + +void HTTPProtocol::closeCacheEntry() +{ + TQString filename = m_request.cef + ".new"; + int result = fclose( m_request.fcache); + m_request.fcache = 0; + if (result == 0) + { + if (::rename( TQFile::encodeName(filename), TQFile::encodeName(m_request.cef)) == 0) + return; // Success + + kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error renaming " + << "cache entry. (" << filename << " -> " << m_request.cef + << ")" << endl; + } + + kdWarning(7113) << "(" << m_pid << ") closeCacheEntry: error closing cache " + << "entry. (" << filename<< ")" << endl; +} + +void HTTPProtocol::cleanCache() +{ + const time_t maxAge = DEFAULT_CLEAN_CACHE_INTERVAL; // 30 Minutes. + bool doClean = false; + TQString cleanFile = m_strCacheDir; + if (cleanFile[cleanFile.length()-1] != '/') + cleanFile += "/"; + cleanFile += "cleaned"; + + struct stat stat_buf; + + int result = ::stat(TQFile::encodeName(cleanFile), &stat_buf); + if (result == -1) + { + int fd = creat( TQFile::encodeName(cleanFile), 0600); + if (fd != -1) + { + doClean = true; + ::close(fd); + } + } + else + { + time_t age = (time_t) difftime( time(0), stat_buf.st_mtime ); + if (age > maxAge) // + doClean = true; + } + if (doClean) + { + // Touch file. + utime(TQFile::encodeName(cleanFile), 0); + TDEApplication::startServiceByDesktopPath("http_cache_cleaner.desktop"); + } +} + + + +//************************** AUTHENTICATION CODE ********************/ + + +void HTTPProtocol::configAuth( char *p, bool isForProxy ) +{ + HTTP_AUTH f = AUTH_None; + const char *strAuth = p; + + if ( strncasecmp( p, "Basic", 5 ) == 0 ) + { + f = AUTH_Basic; + p += 5; + strAuth = "Basic"; // Correct for upper-case variations. + } + else if ( strncasecmp (p, "Digest", 6) == 0 ) + { + f = AUTH_Digest; + memcpy((void *)p, "Digest", 6); // Correct for upper-case variations. + p += 6; + } + else if (strncasecmp( p, "MBS_PWD_COOKIE", 14 ) == 0) + { + // Found on http://www.webscription.net/baen/default.asp + f = AUTH_Basic; + p += 14; + strAuth = "Basic"; + } +#ifdef HAVE_LIBGSSAPI + else if ( strncasecmp( p, "Negotiate", 9 ) == 0 ) + { + // if we get two 401 in a row let's assume for now that + // Negotiate isn't working and ignore it + if ( !isForProxy && !(m_responseCode == 401 && m_prevResponseCode == 401) ) + { + f = AUTH_Negotiate; + memcpy((void *)p, "Negotiate", 9); // Correct for upper-case variations. + p += 9; + }; + } +#endif + else if ( strncasecmp( p, "NTLM", 4 ) == 0 ) + { + f = AUTH_NTLM; + memcpy((void *)p, "NTLM", 4); // Correct for upper-case variations. + p += 4; + m_strRealm = "NTLM"; // set a dummy realm + } + else + { + kdWarning(7113) << "(" << m_pid << ") Unsupported or invalid authorization " + << "type requested" << endl; + if (isForProxy) + kdWarning(7113) << "(" << m_pid << ") Proxy URL: " << m_proxyURL << endl; + else + kdWarning(7113) << "(" << m_pid << ") URL: " << m_request.url << endl; + kdWarning(7113) << "(" << m_pid << ") Request Authorization: " << p << endl; + } + + /* + This check ensures the following: + 1.) Rejection of any unknown/unsupported authentication schemes + 2.) Usage of the strongest possible authentication schemes if + and when multiple Proxy-Authenticate or WWW-Authenticate + header field is sent. + */ + if (isForProxy) + { + if ((f == AUTH_None) || + ((m_iProxyAuthCount > 0) && (f < ProxyAuthentication))) + { + // Since I purposefully made the Proxy-Authentication settings + // persistent to reduce the number of round-trips to tdesud we + // have to take special care when an unknown/unsupported auth- + // scheme is received. This check accomplishes just that... + if ( m_iProxyAuthCount == 0) + ProxyAuthentication = f; + kdDebug(7113) << "(" << m_pid << ") Rejected proxy auth method: " << f << endl; + return; + } + m_iProxyAuthCount++; + kdDebug(7113) << "(" << m_pid << ") Accepted proxy auth method: " << f << endl; + } + else + { + if ((f == AUTH_None) || + ((m_iWWWAuthCount > 0) && (f < Authentication))) + { + kdDebug(7113) << "(" << m_pid << ") Rejected auth method: " << f << endl; + return; + } + m_iWWWAuthCount++; + kdDebug(7113) << "(" << m_pid << ") Accepted auth method: " << f << endl; + } + + + while (*p) + { + int i = 0; + while( (*p == ' ') || (*p == ',') || (*p == '\t') ) { p++; } + if ( strncasecmp( p, "realm=", 6 ) == 0 ) + { + //for sites like lib.homelinux.org + TQTextCodec* oldCodec=TQTextCodec::codecForCStrings(); + if (TDEGlobal::locale()->language().contains("ru")) + TQTextCodec::setCodecForCStrings(TQTextCodec::codecForName("CP1251")); + + p += 6; + if (*p == '"') p++; + while( p[i] && p[i] != '"' ) i++; + if( isForProxy ) + m_strProxyRealm = TQString::fromAscii( p, i ); + else + m_strRealm = TQString::fromAscii( p, i ); + + TQTextCodec::setCodecForCStrings(oldCodec); + + if (!p[i]) break; + } + p+=(i+1); + } + + if( isForProxy ) + { + ProxyAuthentication = f; + m_strProxyAuthorization = TQString::fromLatin1( strAuth ); + } + else + { + Authentication = f; + m_strAuthorization = TQString::fromLatin1( strAuth ); + } +} + + +bool HTTPProtocol::retryPrompt() +{ + TQString prompt; + switch ( m_responseCode ) + { + case 401: + prompt = i18n("Authentication Failed."); + break; + case 407: + prompt = i18n("Proxy Authentication Failed."); + break; + default: + break; + } + prompt += i18n(" Do you want to retry?"); + return (messageBox(QuestionYesNo, prompt, i18n("Authentication")) == 3); +} + +void HTTPProtocol::promptInfo( AuthInfo& info ) +{ + if ( m_responseCode == 401 ) + { + info.url = m_request.url; + if ( !m_state.user.isEmpty() ) + info.username = m_state.user; + info.readOnly = !m_request.url.user().isEmpty(); + info.prompt = i18n( "You need to supply a username and a " + "password to access this site." ); + info.keepPassword = true; // Prompt the user for persistence as well. + if ( !m_strRealm.isEmpty() ) + { + info.realmValue = m_strRealm; + info.verifyPath = false; + info.digestInfo = m_strAuthorization; + info.commentLabel = i18n( "Site:" ); + info.comment = i18n("%1 at %2").arg( htmlEscape(m_strRealm) ).arg( m_request.hostname ); + } + } + else if ( m_responseCode == 407 ) + { + info.url = m_proxyURL; + info.username = m_proxyURL.user(); + info.prompt = i18n( "You need to supply a username and a password for " + "the proxy server listed below before you are allowed " + "to access any sites." ); + info.keepPassword = true; + if ( !m_strProxyRealm.isEmpty() ) + { + info.realmValue = m_strProxyRealm; + info.verifyPath = false; + info.digestInfo = m_strProxyAuthorization; + info.commentLabel = i18n( "Proxy:" ); + info.comment = i18n("%1 at %2").arg( htmlEscape(m_strProxyRealm) ).arg( m_proxyURL.host() ); + } + } +} + +bool HTTPProtocol::getAuthorization() +{ + AuthInfo info; + bool result = false; + + kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::getAuthorization: " + << "Current Response: " << m_responseCode << ", " + << "Previous Response: " << m_prevResponseCode << ", " + << "Authentication: " << Authentication << ", " + << "ProxyAuthentication: " << ProxyAuthentication << endl; + + if (m_request.bNoAuth) + { + if (m_request.bErrorPage) + errorPage(); + else + error( ERR_COULD_NOT_LOGIN, i18n("Authentication needed for %1 but authentication is disabled.").arg(m_request.hostname)); + return false; + } + + bool repeatFailure = (m_prevResponseCode == m_responseCode); + + TQString errorMsg; + + if (repeatFailure) + { + bool prompt = true; + if ( Authentication == AUTH_Digest || ProxyAuthentication == AUTH_Digest ) + { + bool isStaleNonce = false; + TQString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization; + int pos = auth.find("stale", 0, false); + if ( pos != -1 ) + { + pos += 5; + int len = auth.length(); + while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++; + if ( pos < len && auth.find("true", pos, false) != -1 ) + { + isStaleNonce = true; + kdDebug(7113) << "(" << m_pid << ") Stale nonce value. " + << "Will retry using same info..." << endl; + } + } + if ( isStaleNonce ) + { + prompt = false; + result = true; + if ( m_responseCode == 401 ) + { + info.username = m_request.user; + info.password = m_request.passwd; + info.realmValue = m_strRealm; + info.digestInfo = m_strAuthorization; + } + else if ( m_responseCode == 407 ) + { + info.username = m_proxyURL.user(); + info.password = m_proxyURL.pass(); + info.realmValue = m_strProxyRealm; + info.digestInfo = m_strProxyAuthorization; + } + } + } + + if ( Authentication == AUTH_NTLM || ProxyAuthentication == AUTH_NTLM ) + { + TQString auth = ( m_responseCode == 401 ) ? m_strAuthorization : m_strProxyAuthorization; + kdDebug(7113) << "auth: " << auth << endl; + if ( auth.length() > 4 ) + { + prompt = false; + result = true; + kdDebug(7113) << "(" << m_pid << ") NTLM auth second phase, " + << "sending response..." << endl; + if ( m_responseCode == 401 ) + { + info.username = m_request.user; + info.password = m_request.passwd; + info.realmValue = m_strRealm; + info.digestInfo = m_strAuthorization; + } + else if ( m_responseCode == 407 ) + { + info.username = m_proxyURL.user(); + info.password = m_proxyURL.pass(); + info.realmValue = m_strProxyRealm; + info.digestInfo = m_strProxyAuthorization; + } + } + } + + if ( prompt ) + { + switch ( m_responseCode ) + { + case 401: + errorMsg = i18n("Authentication Failed."); + break; + case 407: + errorMsg = i18n("Proxy Authentication Failed."); + break; + default: + break; + } + } + } + else + { + // At this point we know more details, so use it to find + // out if we have a cached version and avoid a re-prompt! + // We also do not use verify path unlike the pre-emptive + // requests because we already know the realm value... + + if (m_bProxyAuthValid) + { + // Reset cached proxy auth + m_bProxyAuthValid = false; + KURL proxy ( config()->readEntry("UseProxy") ); + m_proxyURL.setUser(proxy.user()); + m_proxyURL.setPass(proxy.pass()); + } + + info.verifyPath = false; + if ( m_responseCode == 407 ) + { + info.url = m_proxyURL; + info.username = m_proxyURL.user(); + info.password = m_proxyURL.pass(); + info.realmValue = m_strProxyRealm; + info.digestInfo = m_strProxyAuthorization; + } + else + { + info.url = m_request.url; + info.username = m_request.user; + info.password = m_request.passwd; + info.realmValue = m_strRealm; + info.digestInfo = m_strAuthorization; + } + + // If either username or password is not supplied + // with the request, check the password cache. + if ( info.username.isNull() || + info.password.isNull() ) + result = checkCachedAuthentication( info ); + + if ( Authentication == AUTH_Digest ) + { + TQString auth; + + if (m_responseCode == 401) + auth = m_strAuthorization; + else + auth = m_strProxyAuthorization; + + int pos = auth.find("stale", 0, false); + if ( pos != -1 ) + { + pos += 5; + int len = auth.length(); + while( pos < len && (auth[pos] == ' ' || auth[pos] == '=') ) pos++; + if ( pos < len && auth.find("true", pos, false) != -1 ) + { + info.digestInfo = (m_responseCode == 401) ? m_strAuthorization : m_strProxyAuthorization; + kdDebug(7113) << "(" << m_pid << ") Just a stale nonce value! " + << "Retrying using the new nonce sent..." << endl; + } + } + } + } + + if (!result ) + { + // Do not prompt if the username & password + // is already supplied and the login attempt + // did not fail before. + if ( !repeatFailure && + !info.username.isNull() && + !info.password.isNull() ) + result = true; + else + { + if (Authentication == AUTH_Negotiate) + { + if (!repeatFailure) + result = true; + } + else if ( m_request.disablePassDlg == false ) + { + kdDebug( 7113 ) << "(" << m_pid << ") Prompting the user for authorization..." << endl; + promptInfo( info ); + result = openPassDlg( info, errorMsg ); + } + } + } + + if ( result ) + { + switch (m_responseCode) + { + case 401: // Request-Authentication + m_request.user = info.username; + m_request.passwd = info.password; + m_strRealm = info.realmValue; + m_strAuthorization = info.digestInfo; + break; + case 407: // Proxy-Authentication + m_proxyURL.setUser( info.username ); + m_proxyURL.setPass( info.password ); + m_strProxyRealm = info.realmValue; + m_strProxyAuthorization = info.digestInfo; + break; + default: + break; + } + return true; + } + + if (m_request.bErrorPage) + errorPage(); + else + error( ERR_USER_CANCELED, TQString::null ); + return false; +} + +void HTTPProtocol::saveAuthorization() +{ + AuthInfo info; + if ( m_prevResponseCode == 407 ) + { + if (!m_bUseProxy) + return; + m_bProxyAuthValid = true; + info.url = m_proxyURL; + info.username = m_proxyURL.user(); + info.password = m_proxyURL.pass(); + info.realmValue = m_strProxyRealm; + info.digestInfo = m_strProxyAuthorization; + cacheAuthentication( info ); + } + else + { + info.url = m_request.url; + info.username = m_request.user; + info.password = m_request.passwd; + info.realmValue = m_strRealm; + info.digestInfo = m_strAuthorization; + cacheAuthentication( info ); + } +} + +#ifdef HAVE_LIBGSSAPI +TQCString HTTPProtocol::gssError( int major_status, int minor_status ) +{ + OM_uint32 new_status; + OM_uint32 msg_ctx = 0; + gss_buffer_desc major_string; + gss_buffer_desc minor_string; + OM_uint32 ret; + TQCString errorstr; + + errorstr = ""; + + do { + ret = gss_display_status(&new_status, major_status, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &major_string); + errorstr += (const char *)major_string.value; + errorstr += " "; + ret = gss_display_status(&new_status, minor_status, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &minor_string); + errorstr += (const char *)minor_string.value; + errorstr += " "; + } while (!GSS_ERROR(ret) && msg_ctx != 0); + + return errorstr; +} + +TQString HTTPProtocol::createNegotiateAuth() +{ + TQString auth; + TQCString servicename; + TQByteArray input; + OM_uint32 major_status, minor_status; + OM_uint32 req_flags = 0; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_name_t server; + gss_ctx_id_t ctx; + gss_OID mech_oid; + static gss_OID_desc krb5_oid_desc = {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"}; + static gss_OID_desc spnego_oid_desc = {6, (void *) "\x2b\x06\x01\x05\x05\x02"}; + int found = 0; + unsigned int i; + gss_OID_set mech_set; + gss_OID tmp_oid; + + ctx = GSS_C_NO_CONTEXT; + mech_oid = &krb5_oid_desc; + + // see whether we can use the SPNEGO mechanism + major_status = gss_indicate_mechs(&minor_status, &mech_set); + if (GSS_ERROR(major_status)) { + kdDebug(7113) << "(" << m_pid << ") gss_indicate_mechs failed: " << gssError(major_status, minor_status) << endl; + } else { + for (i=0; icount && !found; i++) { + tmp_oid = &mech_set->elements[i]; + if (tmp_oid->length == spnego_oid_desc.length && + !memcmp(tmp_oid->elements, spnego_oid_desc.elements, tmp_oid->length)) { + kdDebug(7113) << "(" << m_pid << ") createNegotiateAuth: found SPNEGO mech" << endl; + found = 1; + mech_oid = &spnego_oid_desc; + break; + } + } + gss_release_oid_set(&minor_status, &mech_set); + } + + // the service name is "HTTP/f.q.d.n" + servicename = "HTTP@"; + servicename += m_state.hostname.ascii(); + + input_token.value = (void *)servicename.data(); + input_token.length = servicename.length() + 1; + + major_status = gss_import_name(&minor_status, &input_token, + GSS_C_NT_HOSTBASED_SERVICE, &server); + + input_token.value = NULL; + input_token.length = 0; + + if (GSS_ERROR(major_status)) { + kdDebug(7113) << "(" << m_pid << ") gss_import_name failed: " << gssError(major_status, minor_status) << endl; + // reset the auth string so that subsequent methods aren't confused + m_strAuthorization = TQString::null; + return TQString::null; + } + + major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, + &ctx, server, mech_oid, + req_flags, GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + GSS_C_NO_BUFFER, NULL, &output_token, + NULL, NULL); + + + if (GSS_ERROR(major_status) || (output_token.length == 0)) { + kdDebug(7113) << "(" << m_pid << ") gss_init_sec_context failed: " << gssError(major_status, minor_status) << endl; + gss_release_name(&minor_status, &server); + if (ctx != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); + ctx = GSS_C_NO_CONTEXT; + } + // reset the auth string so that subsequent methods aren't confused + m_strAuthorization = TQString::null; + return TQString::null; + } + + input.duplicate((const char *)output_token.value, output_token.length); + auth = "Authorization: Negotiate "; + auth += KCodecs::base64Encode( input ); + auth += "\r\n"; + + // free everything + gss_release_name(&minor_status, &server); + if (ctx != GSS_C_NO_CONTEXT) { + gss_delete_sec_context(&minor_status, &ctx, GSS_C_NO_BUFFER); + ctx = GSS_C_NO_CONTEXT; + } + gss_release_buffer(&minor_status, &output_token); + + return auth; +} +#else + +// Dummy +TQCString HTTPProtocol::gssError( int, int ) +{ + return ""; +} + +// Dummy +TQString HTTPProtocol::createNegotiateAuth() +{ + return TQString::null; +} +#endif + +TQString HTTPProtocol::createNTLMAuth( bool isForProxy ) +{ + uint len; + TQString auth, user, domain, passwd; + TQCString strauth; + TQByteArray buf; + + if ( isForProxy ) + { + auth = "Proxy-Connection: Keep-Alive\r\n"; + auth += "Proxy-Authorization: NTLM "; + user = m_proxyURL.user(); + passwd = m_proxyURL.pass(); + strauth = m_strProxyAuthorization.latin1(); + len = m_strProxyAuthorization.length(); + } + else + { + auth = "Authorization: NTLM "; + user = m_state.user; + passwd = m_state.passwd; + strauth = m_strAuthorization.latin1(); + len = m_strAuthorization.length(); + } + if ( user.contains('\\') ) { + domain = user.section( '\\', 0, 0); + user = user.section( '\\', 1 ); + } + + kdDebug(7113) << "(" << m_pid << ") NTLM length: " << len << endl; + if ( user.isEmpty() || passwd.isEmpty() || len < 4 ) + return TQString::null; + + if ( len > 4 ) + { + // create a response + TQByteArray challenge; + KCodecs::base64Decode( strauth.right( len - 5 ), challenge ); + KNTLM::getAuth( buf, challenge, user, passwd, domain, + KNetwork::KResolver::localHostName(), false, false ); + } + else + { + KNTLM::getNegotiate( buf ); + } + + // remove the challenge to prevent reuse + if ( isForProxy ) + m_strProxyAuthorization = "NTLM"; + else + m_strAuthorization = "NTLM"; + + auth += KCodecs::base64Encode( buf ); + auth += "\r\n"; + + return auth; +} + +TQString HTTPProtocol::createBasicAuth( bool isForProxy ) +{ + TQString auth; + TQCString user, passwd; + if ( isForProxy ) + { + auth = "Proxy-Authorization: Basic "; + user = m_proxyURL.user().latin1(); + passwd = m_proxyURL.pass().latin1(); + } + else + { + auth = "Authorization: Basic "; + user = m_state.user.latin1(); + passwd = m_state.passwd.latin1(); + } + + if ( user.isEmpty() ) + user = ""; + if ( passwd.isEmpty() ) + passwd = ""; + + user += ':'; + user += passwd; + auth += KCodecs::base64Encode( user ); + auth += "\r\n"; + + return auth; +} + +void HTTPProtocol::calculateResponse( DigestAuthInfo& info, TQCString& Response ) +{ + KMD5 md; + TQCString HA1; + TQCString HA2; + + // Calculate H(A1) + TQCString authStr = info.username; + authStr += ':'; + authStr += info.realm; + authStr += ':'; + authStr += info.password; + md.update( authStr ); + + if ( info.algorithm.lower() == "md5-sess" ) + { + authStr = md.hexDigest(); + authStr += ':'; + authStr += info.nonce; + authStr += ':'; + authStr += info.cnonce; + md.reset(); + md.update( authStr ); + } + HA1 = md.hexDigest(); + + kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A1 => " << HA1 << endl; + + // Calcualte H(A2) + authStr = info.method; + authStr += ':'; + authStr += m_request.url.encodedPathAndQuery(0, true).latin1(); + if ( info.qop == "auth-int" ) + { + authStr += ':'; + authStr += info.entityBody; + } + md.reset(); + md.update( authStr ); + HA2 = md.hexDigest(); + + kdDebug(7113) << "(" << m_pid << ") calculateResponse(): A2 => " + << HA2 << endl; + + // Calcualte the response. + authStr = HA1; + authStr += ':'; + authStr += info.nonce; + authStr += ':'; + if ( !info.qop.isEmpty() ) + { + authStr += info.nc; + authStr += ':'; + authStr += info.cnonce; + authStr += ':'; + authStr += info.qop; + authStr += ':'; + } + authStr += HA2; + md.reset(); + md.update( authStr ); + Response = md.hexDigest(); + + kdDebug(7113) << "(" << m_pid << ") calculateResponse(): Response => " + << Response << endl; +} + +TQString HTTPProtocol::createDigestAuth ( bool isForProxy ) +{ + const char *p; + + TQString auth; + TQCString opaque; + TQCString Response; + + DigestAuthInfo info; + + opaque = ""; + if ( isForProxy ) + { + auth = "Proxy-Authorization: Digest "; + info.username = m_proxyURL.user().latin1(); + info.password = m_proxyURL.pass().latin1(); + p = m_strProxyAuthorization.latin1(); + } + else + { + auth = "Authorization: Digest "; + info.username = m_state.user.latin1(); + info.password = m_state.passwd.latin1(); + p = m_strAuthorization.latin1(); + } + if (!p || !*p) + return TQString::null; + + p += 6; // Skip "Digest" + + if ( info.username.isEmpty() || info.password.isEmpty() || !p ) + return TQString::null; + + // info.entityBody = p; // FIXME: send digest of data for POST action ?? + info.realm = ""; + info.algorithm = "MD5"; + info.nonce = ""; + info.qop = ""; + + // cnonce is recommended to contain about 64 bits of entropy + info.cnonce = TDEApplication::randomString(16).latin1(); + + // HACK: Should be fixed according to RFC 2617 section 3.2.2 + info.nc = "00000001"; + + // Set the method used... + switch ( m_request.method ) + { + case HTTP_GET: + info.method = "GET"; + break; + case HTTP_PUT: + info.method = "PUT"; + break; + case HTTP_POST: + info.method = "POST"; + break; + case HTTP_HEAD: + info.method = "HEAD"; + break; + case HTTP_DELETE: + info.method = "DELETE"; + break; + case DAV_PROPFIND: + info.method = "PROPFIND"; + break; + case DAV_PROPPATCH: + info.method = "PROPPATCH"; + break; + case DAV_MKCOL: + info.method = "MKCOL"; + break; + case DAV_COPY: + info.method = "COPY"; + break; + case DAV_MOVE: + info.method = "MOVE"; + break; + case DAV_LOCK: + info.method = "LOCK"; + break; + case DAV_UNLOCK: + info.method = "UNLOCK"; + break; + case DAV_SEARCH: + info.method = "SEARCH"; + break; + case DAV_SUBSCRIBE: + info.method = "SUBSCRIBE"; + break; + case DAV_UNSUBSCRIBE: + info.method = "UNSUBSCRIBE"; + break; + case DAV_POLL: + info.method = "POLL"; + break; + default: + error( ERR_UNSUPPORTED_ACTION, i18n("Unsupported method: authentication will fail. Please submit a bug report.")); + break; + } + + // Parse the Digest response.... + while (*p) + { + int i = 0; + while ( (*p == ' ') || (*p == ',') || (*p == '\t')) { p++; } + if (strncasecmp(p, "realm=", 6 )==0) + { + p+=6; + while ( *p == '"' ) p++; // Go past any number of " mark(s) first + while ( p[i] != '"' ) i++; // Read everything until the last " mark + info.realm = TQCString( p, i+1 ); + } + else if (strncasecmp(p, "algorith=", 9)==0) + { + p+=9; + while ( *p == '"' ) p++; // Go past any number of " mark(s) first + while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++; + info.algorithm = TQCString(p, i+1); + } + else if (strncasecmp(p, "algorithm=", 10)==0) + { + p+=10; + while ( *p == '"' ) p++; // Go past any " mark(s) first + while ( ( p[i] != '"' ) && ( p[i] != ',' ) && ( p[i] != '\0' ) ) i++; + info.algorithm = TQCString(p,i+1); + } + else if (strncasecmp(p, "domain=", 7)==0) + { + p+=7; + while ( *p == '"' ) p++; // Go past any " mark(s) first + while ( p[i] != '"' ) i++; // Read everything until the last " mark + int pos; + int idx = 0; + TQCString uri = TQCString(p,i+1); + do + { + pos = uri.find( ' ', idx ); + if ( pos != -1 ) + { + KURL u (m_request.url, uri.mid(idx, pos-idx)); + if (u.isValid ()) + info.digestURI.append( u.url().latin1() ); + } + else + { + KURL u (m_request.url, uri.mid(idx, uri.length()-idx)); + if (u.isValid ()) + info.digestURI.append( u.url().latin1() ); + } + idx = pos+1; + } while ( pos != -1 ); + } + else if (strncasecmp(p, "nonce=", 6)==0) + { + p+=6; + while ( *p == '"' ) p++; // Go past any " mark(s) first + while ( p[i] != '"' ) i++; // Read everything until the last " mark + info.nonce = TQCString(p,i+1); + } + else if (strncasecmp(p, "opaque=", 7)==0) + { + p+=7; + while ( *p == '"' ) p++; // Go past any " mark(s) first + while ( p[i] != '"' ) i++; // Read everything until the last " mark + opaque = TQCString(p,i+1); + } + else if (strncasecmp(p, "qop=", 4)==0) + { + p+=4; + while ( *p == '"' ) p++; // Go past any " mark(s) first + while ( p[i] != '"' ) i++; // Read everything until the last " mark + info.qop = TQCString(p,i+1); + } + p+=(i+1); + } + + if (info.realm.isEmpty() || info.nonce.isEmpty()) + return TQString::null; + + // If the "domain" attribute was not specified and the current response code + // is authentication needed, add the current request url to the list over which + // this credential can be automatically applied. + if (info.digestURI.isEmpty() && (m_responseCode == 401 || m_responseCode == 407)) + info.digestURI.append (m_request.url.url().latin1()); + else + { + // Verify whether or not we should send a cached credential to the + // server based on the stored "domain" attribute... + bool send = true; + + // Determine the path of the request url... + TQString requestPath = m_request.url.directory(false, false); + if (requestPath.isEmpty()) + requestPath = "/"; + + int count = info.digestURI.count(); + + for (int i = 0; i < count; i++ ) + { + KURL u ( info.digestURI.at(i) ); + + send &= (m_request.url.protocol().lower() == u.protocol().lower()); + send &= (m_request.hostname.lower() == u.host().lower()); + + if (m_request.port > 0 && u.port() > 0) + send &= (m_request.port == u.port()); + + TQString digestPath = u.directory (false, false); + if (digestPath.isEmpty()) + digestPath = "/"; + + send &= (requestPath.startsWith(digestPath)); + + if (send) + break; + } + + kdDebug(7113) << "(" << m_pid << ") createDigestAuth(): passed digest " + "authentication credential test: " << send << endl; + + if (!send) + return TQString::null; + } + + kdDebug(7113) << "(" << m_pid << ") RESULT OF PARSING:" << endl; + kdDebug(7113) << "(" << m_pid << ") algorithm: " << info.algorithm << endl; + kdDebug(7113) << "(" << m_pid << ") realm: " << info.realm << endl; + kdDebug(7113) << "(" << m_pid << ") nonce: " << info.nonce << endl; + kdDebug(7113) << "(" << m_pid << ") opaque: " << opaque << endl; + kdDebug(7113) << "(" << m_pid << ") qop: " << info.qop << endl; + + // Calculate the response... + calculateResponse( info, Response ); + + auth += "username=\""; + auth += info.username; + + auth += "\", realm=\""; + auth += info.realm; + auth += "\""; + + auth += ", nonce=\""; + auth += info.nonce; + + auth += "\", uri=\""; + auth += m_request.url.encodedPathAndQuery(0, true); + + auth += "\", algorithm=\""; + auth += info.algorithm; + auth +="\""; + + if ( !info.qop.isEmpty() ) + { + auth += ", qop=\""; + auth += info.qop; + auth += "\", cnonce=\""; + auth += info.cnonce; + auth += "\", nc="; + auth += info.nc; + } + + auth += ", response=\""; + auth += Response; + if ( !opaque.isEmpty() ) + { + auth += "\", opaque=\""; + auth += opaque; + } + auth += "\"\r\n"; + + return auth; +} + +TQString HTTPProtocol::proxyAuthenticationHeader() +{ + TQString header; + + // We keep proxy authentication locally until they are changed. + // Thus, no need to check with the password manager for every + // connection. + if ( m_strProxyRealm.isEmpty() ) + { + AuthInfo info; + info.url = m_proxyURL; + info.username = m_proxyURL.user(); + info.password = m_proxyURL.pass(); + info.verifyPath = true; + + // If the proxy URL already contains username + // and password simply attempt to retrieve it + // without prompting the user... + if ( !info.username.isNull() && !info.password.isNull() ) + { + if( m_strProxyAuthorization.isEmpty() ) + ProxyAuthentication = AUTH_None; + else if( m_strProxyAuthorization.startsWith("Basic") ) + ProxyAuthentication = AUTH_Basic; + else if( m_strProxyAuthorization.startsWith("NTLM") ) + ProxyAuthentication = AUTH_NTLM; + else + ProxyAuthentication = AUTH_Digest; + } + else + { + if ( checkCachedAuthentication(info) && !info.digestInfo.isEmpty() ) + { + m_proxyURL.setUser( info.username ); + m_proxyURL.setPass( info.password ); + m_strProxyRealm = info.realmValue; + m_strProxyAuthorization = info.digestInfo; + if( m_strProxyAuthorization.startsWith("Basic") ) + ProxyAuthentication = AUTH_Basic; + else if( m_strProxyAuthorization.startsWith("NTLM") ) + ProxyAuthentication = AUTH_NTLM; + else + ProxyAuthentication = AUTH_Digest; + } + else + { + ProxyAuthentication = AUTH_None; + } + } + } + + /********* Only for debugging purpose... *********/ + if ( ProxyAuthentication != AUTH_None ) + { + kdDebug(7113) << "(" << m_pid << ") Using Proxy Authentication: " << endl; + kdDebug(7113) << "(" << m_pid << ") HOST= " << m_proxyURL.host() << endl; + kdDebug(7113) << "(" << m_pid << ") PORT= " << m_proxyURL.port() << endl; + kdDebug(7113) << "(" << m_pid << ") USER= " << m_proxyURL.user() << endl; + kdDebug(7113) << "(" << m_pid << ") PASSWORD= [protected]" << endl; + kdDebug(7113) << "(" << m_pid << ") REALM= " << m_strProxyRealm << endl; + kdDebug(7113) << "(" << m_pid << ") EXTRA= " << m_strProxyAuthorization << endl; + } + + switch ( ProxyAuthentication ) + { + case AUTH_Basic: + header += createBasicAuth( true ); + break; + case AUTH_Digest: + header += createDigestAuth( true ); + break; + case AUTH_NTLM: + if ( m_bFirstRequest ) header += createNTLMAuth( true ); + break; + case AUTH_None: + default: + break; + } + + return header; +} + +#include "http.moc" diff --git a/tdeioslave/http/http_cache_cleaner.cpp b/tdeioslave/http/http_cache_cleaner.cpp index aaf94d25a..1f2be7abf 100644 --- a/tdeioslave/http/http_cache_cleaner.cpp +++ b/tdeioslave/http/http_cache_cleaner.cpp @@ -77,7 +77,7 @@ public: }; // !START OF SYNC! -// Keep the following in sync with the cache code in http.cc +// Keep the following in sync with the cache code in http.cpp #define CACHE_REVISION "7\n" FileInfo *readEntry( const TQString &filename) @@ -158,7 +158,7 @@ FileInfo *readEntry( const TQString &filename) unlink( CEF.data()); return 0; } -// Keep the above in sync with the cache code in http.cc +// Keep the above in sync with the cache code in http.cpp // !END OF SYNC! void scanDirectory(FileInfoList &fileEntries, const TQString &name, const TQString &strDir) diff --git a/tdeioslave/iso/Makefile.am b/tdeioslave/iso/Makefile.am index ddb1c60f3..709b38270 100644 --- a/tdeioslave/iso/Makefile.am +++ b/tdeioslave/iso/Makefile.am @@ -60,7 +60,7 @@ uninstall-local: # make messages.po. Move this one to ../po/ and "make merge" in po # the -x is for skipping messages already translated in tdelibs messages: - LIST=`find . -name \*.h -o -name \*.hh -o -name \*.H -o -name \*.hxx -o -name \*.hpp -o -name \*.cpp -o -name \*.cc -o -name \*.cxx -o -name \*.ecpp -o -name \*.C`; \ + LIST=`find . -name \*.h -o -name \*.cpp -o -name \*.c`; \ if test -n "$$LIST"; then \ $(XGETTEXT) -C -ki18n -x $(kde_includes)/tde.pot $$LIST -o ../po/iso.pot; \ fi diff --git a/tdeioslave/iso/iso.cpp b/tdeioslave/iso/iso.cpp index 8696b0c67..80e32b415 100644 --- a/tdeioslave/iso/iso.cpp +++ b/tdeioslave/iso/iso.cpp @@ -15,7 +15,7 @@ * * ***************************************************************************/ - /* This file is heavily based on tar.cc from tdebase + /* This file is heavily based on tar.cpp from tdebase * (c) David Faure */ -- cgit v1.2.1