summaryrefslogtreecommitdiffstats
path: root/kioslave
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave')
-rw-r--r--kioslave/Mainpage.dox9
-rw-r--r--kioslave/Makefile.am27
-rw-r--r--kioslave/bzip2/Makefile.am11
-rw-r--r--kioslave/bzip2/configure.in.in11
-rw-r--r--kioslave/bzip2/kbzip2filter.cpp187
-rw-r--r--kioslave/bzip2/kbzip2filter.desktop86
-rw-r--r--kioslave/bzip2/kbzip2filter.h54
-rw-r--r--kioslave/file/Makefile.am22
-rw-r--r--kioslave/file/file.cc1797
-rw-r--r--kioslave/file/file.h98
-rw-r--r--kioslave/file/file.protocol15
-rw-r--r--kioslave/ftp/Makefile.am16
-rw-r--r--kioslave/ftp/configure.in.in5
-rw-r--r--kioslave/ftp/ftp.cc2652
-rw-r--r--kioslave/ftp/ftp.h598
-rw-r--r--kioslave/ftp/ftp.protocol18
-rw-r--r--kioslave/gzip/Makefile.am12
-rw-r--r--kioslave/gzip/kgzipfilter.cpp336
-rw-r--r--kioslave/gzip/kgzipfilter.desktop86
-rw-r--r--kioslave/gzip/kgzipfilter.h52
-rw-r--r--kioslave/http/Makefile.am31
-rw-r--r--kioslave/http/README.http_cache_cleaner20
-rw-r--r--kioslave/http/README.webdav184
-rw-r--r--kioslave/http/THOUGHTS28
-rw-r--r--kioslave/http/TODO45
-rw-r--r--kioslave/http/configure.in.bot10
-rw-r--r--kioslave/http/configure.in.in110
-rw-r--r--kioslave/http/http.cc6095
-rw-r--r--kioslave/http/http.h577
-rw-r--r--kioslave/http/http.protocol12
-rw-r--r--kioslave/http/http_cache_cleaner.cpp284
-rw-r--r--kioslave/http/http_cache_cleaner.desktop168
-rw-r--r--kioslave/http/https.protocol12
-rw-r--r--kioslave/http/kcookiejar/Makefile.am31
-rw-r--r--kioslave/http/kcookiejar/domain_info1
-rw-r--r--kioslave/http/kcookiejar/kcookiejar.cpp1558
-rw-r--r--kioslave/http/kcookiejar/kcookiejar.desktop157
-rw-r--r--kioslave/http/kcookiejar/kcookiejar.h365
-rw-r--r--kioslave/http/kcookiejar/kcookiescfg.upd16
-rw-r--r--kioslave/http/kcookiejar/kcookieserver.cpp606
-rw-r--r--kioslave/http/kcookiejar/kcookieserver.h98
-rw-r--r--kioslave/http/kcookiejar/kcookiewin.cpp382
-rw-r--r--kioslave/http/kcookiejar/kcookiewin.h84
-rw-r--r--kioslave/http/kcookiejar/main.cpp92
-rw-r--r--kioslave/http/kcookiejar/netscape_cookie_spec.html331
-rw-r--r--kioslave/http/kcookiejar/rfc21091179
-rw-r--r--kioslave/http/kcookiejar/rfc29651459
-rw-r--r--kioslave/http/kcookiejar/tests/Makefile.am18
-rw-r--r--kioslave/http/kcookiejar/tests/cookie.test162
-rw-r--r--kioslave/http/kcookiejar/tests/cookie_rfc.test148
-rw-r--r--kioslave/http/kcookiejar/tests/cookie_saving.test430
-rw-r--r--kioslave/http/kcookiejar/tests/cookie_settings.test116
-rw-r--r--kioslave/http/kcookiejar/tests/kcookiejartest.cpp270
-rw-r--r--kioslave/http/rfc2518.txt1
-rw-r--r--kioslave/http/rfc2616.txt1
-rw-r--r--kioslave/http/rfc2617.txt1
-rw-r--r--kioslave/http/rfc2817.txt1
-rw-r--r--kioslave/http/rfc2818.txt1
-rw-r--r--kioslave/http/rfc3229.txt1
-rw-r--r--kioslave/http/rfc3253.txt1
-rw-r--r--kioslave/http/shoutcast-icecast.txt605
-rw-r--r--kioslave/http/webdav.protocol18
-rw-r--r--kioslave/http/webdavs.protocol18
-rw-r--r--kioslave/metainfo/Makefile.am24
-rw-r--r--kioslave/metainfo/metainfo.cpp103
-rw-r--r--kioslave/metainfo/metainfo.h38
-rw-r--r--kioslave/metainfo/metainfo.protocol9
67 files changed, 21993 insertions, 0 deletions
diff --git a/kioslave/Mainpage.dox b/kioslave/Mainpage.dox
new file mode 100644
index 000000000..44979b667
--- /dev/null
+++ b/kioslave/Mainpage.dox
@@ -0,0 +1,9 @@
+/** @mainpage KIO Slaves
+*
+* KIO Slaves are out-of-process worker applications that
+* perform protocol-specific communications.
+*
+* - <a href="../http/html/">http</a> ioslave
+* - ftp ioslave
+*
+*/
diff --git a/kioslave/Makefile.am b/kioslave/Makefile.am
new file mode 100644
index 000000000..42f294e76
--- /dev/null
+++ b/kioslave/Makefile.am
@@ -0,0 +1,27 @@
+# This file is part of the KDE libraries
+# Copyright (C) 1997 Torben Weis (weis@kde.org)
+
+# 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.
+
+if include_bzip2
+BZIP2DIR=bzip2
+endif
+
+SUBDIRS = file http ftp gzip $(BZIP2DIR) metainfo
+
+messages: # they get into kio.pot
+
+include $(top_srcdir)/admin/Doxyfile.am
diff --git a/kioslave/bzip2/Makefile.am b/kioslave/bzip2/Makefile.am
new file mode 100644
index 000000000..bb5ca3dc5
--- /dev/null
+++ b/kioslave/bzip2/Makefile.am
@@ -0,0 +1,11 @@
+INCLUDES = -I$(top_srcdir)/kio $(all_includes)
+METASOURCES = AUTO
+
+kde_module_LTLIBRARIES = kbzip2filter.la
+
+kbzip2filter_la_SOURCES = kbzip2filter.cpp
+kbzip2filter_la_LIBADD = $(LIB_KIO) $(LIBBZ2)
+kbzip2filter_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+
+kde_services_DATA = kbzip2filter.desktop
+
diff --git a/kioslave/bzip2/configure.in.in b/kioslave/bzip2/configure.in.in
new file mode 100644
index 000000000..99392042d
--- /dev/null
+++ b/kioslave/bzip2/configure.in.in
@@ -0,0 +1,11 @@
+AC_DEFUN([KIOBZIP2_CHECK_BZIP2],
+[
+AC_REQUIRE([AC_FIND_BZIP2])
+
+AM_CONDITIONAL(include_bzip2, test -n "$BZIP2DIR")
+if test -n "$BZIP2DIR"; then
+ AC_DEFINE(HAVE_BZIP2_SUPPORT, 1, [Defines if bzip2 is compiled])
+fi
+])
+
+KIOBZIP2_CHECK_BZIP2
diff --git a/kioslave/bzip2/kbzip2filter.cpp b/kioslave/bzip2/kbzip2filter.cpp
new file mode 100644
index 000000000..5ef6aaf5b
--- /dev/null
+++ b/kioslave/bzip2/kbzip2filter.cpp
@@ -0,0 +1,187 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ $Id$
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <config.h>
+
+#if defined( HAVE_BZIP2_SUPPORT )
+
+// we don't need that
+#define BZ_NO_STDIO
+extern "C" {
+ #include <bzlib.h>
+}
+
+#ifdef NEED_BZ2_PREFIX
+ #define bzDecompressInit(x,y,z) BZ2_bzDecompressInit(x,y,z)
+ #define bzDecompressEnd(x) BZ2_bzDecompressEnd(x)
+ #define bzCompressEnd(x) BZ2_bzCompressEnd(x)
+ #define bzDecompress(x) BZ2_bzDecompress(x)
+ #define bzCompress(x,y) BZ2_bzCompress(x, y)
+ #define bzCompressInit(x,y,z,a) BZ2_bzCompressInit(x, y, z, a);
+#endif
+
+#include <kdebug.h>
+#include <klibloader.h>
+
+#include "kbzip2filter.h"
+
+// For docu on this, see /usr/doc/bzip2-0.9.5d/bzip2-0.9.5d/manual_3.html
+
+class KBzip2FilterFactory : public KLibFactory
+{
+public:
+ KBzip2FilterFactory() : KLibFactory() {}
+ virtual ~KBzip2FilterFactory(){}
+ QObject *createObject( QObject *, const char *, const char*, const QStringList & )
+ {
+ return new KBzip2Filter;
+ }
+};
+
+K_EXPORT_COMPONENT_FACTORY( kbzip2filter, KBzip2FilterFactory )
+
+// Not really useful anymore
+class KBzip2Filter::KBzip2FilterPrivate
+{
+public:
+ bz_stream zStream;
+};
+
+KBzip2Filter::KBzip2Filter()
+{
+ d = new KBzip2FilterPrivate;
+ d->zStream.bzalloc = 0;
+ d->zStream.bzfree = 0;
+ d->zStream.opaque = 0;
+ m_mode = 0;
+}
+
+
+KBzip2Filter::~KBzip2Filter()
+{
+ delete d;
+}
+
+void KBzip2Filter::init( int mode )
+{
+ d->zStream.next_in = 0;
+ d->zStream.avail_in = 0;
+ if ( mode == IO_ReadOnly )
+ {
+ (void)bzDecompressInit(&d->zStream, 0, 0);
+ //kdDebug(7118) << "bzDecompressInit returned " << result << endl;
+ // No idea what to do with result :)
+ } else if ( mode == IO_WriteOnly ) {
+ (void)bzCompressInit(&d->zStream, 5, 0, 0);
+ //kdDebug(7118) << "bzDecompressInit returned " << result << endl;
+ } else
+ kdWarning(7118) << "Unsupported mode " << mode << ". Only IO_ReadOnly and IO_WriteOnly supported" << endl;
+ m_mode = mode;
+}
+
+void KBzip2Filter::terminate()
+{
+ if ( m_mode == IO_ReadOnly )
+ {
+ int result = bzDecompressEnd(&d->zStream);
+ kdDebug(7118) << "bzDecompressEnd returned " << result << endl;
+ } else if ( m_mode == IO_WriteOnly )
+ {
+ int result = bzCompressEnd(&d->zStream);
+ kdDebug(7118) << "bzCompressEnd returned " << result << endl;
+ } else
+ kdWarning(7118) << "Unsupported mode " << m_mode << ". Only IO_ReadOnly and IO_WriteOnly supported" << endl;
+}
+
+
+void KBzip2Filter::reset()
+{
+ kdDebug(7118) << "KBzip2Filter::reset" << endl;
+ // bzip2 doesn't seem to have a reset call...
+ terminate();
+ init( m_mode );
+}
+
+void KBzip2Filter::setOutBuffer( char * data, uint maxlen )
+{
+ d->zStream.avail_out = maxlen;
+ d->zStream.next_out = data;
+}
+
+void KBzip2Filter::setInBuffer( const char *data, unsigned int size )
+{
+ d->zStream.avail_in = size;
+ d->zStream.next_in = const_cast<char *>(data);
+}
+
+int KBzip2Filter::inBufferAvailable() const
+{
+ return d->zStream.avail_in;
+}
+
+int KBzip2Filter::outBufferAvailable() const
+{
+ return d->zStream.avail_out;
+}
+
+KBzip2Filter::Result KBzip2Filter::uncompress()
+{
+ //kdDebug(7118) << "Calling bzDecompress with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable() << endl;
+ int result = bzDecompress(&d->zStream);
+ if ( result != BZ_OK )
+ {
+ kdDebug(7118) << "bzDecompress returned " << result << endl;
+ kdDebug(7118) << "KBzip2Filter::uncompress " << ( result == BZ_OK ? OK : ( result == BZ_STREAM_END ? END : ERROR ) ) << endl;
+ }
+
+ switch (result) {
+ case BZ_OK:
+ return OK;
+ case BZ_STREAM_END:
+ return END;
+ default:
+ return ERROR;
+ }
+}
+
+KBzip2Filter::Result KBzip2Filter::compress( bool finish )
+{
+ //kdDebug(7118) << "Calling bzCompress with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable() << endl;
+ int result = bzCompress(&d->zStream, finish ? BZ_FINISH : BZ_RUN );
+
+ switch (result) {
+ case BZ_OK:
+ case BZ_FLUSH_OK:
+ case BZ_RUN_OK:
+ case BZ_FINISH_OK:
+ return OK;
+ break;
+ case BZ_STREAM_END:
+ kdDebug(7118) << " bzCompress returned " << result << endl;
+ return END;
+ break;
+ default:
+ kdDebug(7118) << " bzCompress returned " << result << endl;
+ return ERROR;
+ break;
+ }
+}
+
+#endif
diff --git a/kioslave/bzip2/kbzip2filter.desktop b/kioslave/bzip2/kbzip2filter.desktop
new file mode 100644
index 000000000..d9d90ec88
--- /dev/null
+++ b/kioslave/bzip2/kbzip2filter.desktop
@@ -0,0 +1,86 @@
+[Desktop Entry]
+Type=Service
+Name=BZip2 Filter
+Name[af]=Bzip2 Filter
+Name[ar]=فلتر BZip2
+Name[az]=BZip2 Filtri
+Name[be]=Фільтр BZip2
+Name[bg]=Филтър BZip2
+Name[bn]=বি-জিপ২ (BZip2) ফিল্টার
+Name[br]=Sil BZip2
+Name[ca]=Filtre BZip2
+Name[cs]=Filtr BZip2
+Name[csb]=Filter BZip2
+Name[cy]=Hidl BZip2
+Name[da]=BZip2-filter
+Name[de]=BZip2-Filter
+Name[el]=Φίλτρο BZip2
+Name[eo]=Bzip2-filtrilo
+Name[es]=Filtro BZip2
+Name[et]=BZip2 filter
+Name[eu]=BZip2 iragazkia
+Name[fa]=پالایۀ BZip2
+Name[fi]=BZip2-suodin
+Name[fr]=Filtre Bzip2
+Name[fy]=BZip2-filter
+Name[ga]=Scagaire bzip2
+Name[gl]=Filtro BZip2
+Name[he]=מסנן BZip2
+Name[hi]=BZip2 फ़िल्टर
+Name[hr]=BZip2 filtar
+Name[hu]=BZip2 szűrő
+Name[id]=Filter BZip2
+Name[is]=BZip2 sía
+Name[it]=Filtro Bzip2
+Name[ja]=BZip2 フィルタ
+Name[ka]=Bzip2 ფილტრი
+Name[kk]=BZip2 сүзгісі
+Name[km]=តម្រង BZip2
+Name[ko]=BZip2 거르개
+Name[lb]=BZip2-Filter
+Name[lt]=BZip2 filtras
+Name[lv]=BZip2 Filtrs
+Name[mk]=BZip2 филтер
+Name[mn]=BZip2-Filter
+Name[ms]=Penapis BZip2
+Name[mt]=Filtru BZip2
+Name[nb]=BZip2-filter
+Name[nds]=BZip2-Filter
+Name[ne]=BZip2 फिल्टर
+Name[nl]=BZip2-filter
+Name[nn]=BZip2-filter
+Name[nso]=Sesekodi sa BZip2
+Name[pa]=BZip2 ਫਿਲਟਰ
+Name[pl]=Filtr BZip2
+Name[pt]=Filtro do Bzip2
+Name[pt_BR]=Filtro BZip2
+Name[ro]=Filtru BZip2
+Name[ru]=Фильтр bzip2
+Name[rw]=Muyunguruzi BZipu2
+Name[se]=BZip2-filter
+Name[sk]=BZip2 filter
+Name[sl]=Filter za bzip2
+Name[sq]=Filteri BZip2
+Name[sr]=BZip2 филтер
+Name[sr@Latn]=BZip2 filter
+Name[ss]=Sisefo se BZip2
+Name[sv]=Bzip2-filter
+Name[ta]=BZip2 வடிகட்டி
+Name[te]=బిజిప్2 గలని
+Name[tg]=Таровиши BZip2
+Name[th]=ตัวกรอง BZip2
+Name[tr]=BZip2 Filtresi
+Name[tt]=BZip2 Sözgeçe
+Name[uk]=Фільтр BZip2
+Name[uz]=BZip2-filter
+Name[uz@cyrillic]=BZip2-филтер
+Name[ven]=Filithara ya BZip2
+Name[vi]=Bộ lọc BZip2
+Name[wa]=Passete BZip2
+Name[xh]=Isihluzi se BZip2
+Name[zh_CN]=BZip2 过滤程序
+Name[zh_HK]=BZip2 過濾器
+Name[zh_TW]=BZip2 過濾器
+Name[zu]=Ihluzo le-BZip2
+X-KDE-Library=kbzip2filter
+ServiceTypes=KDECompressionFilter,application/x-bzip2,application/x-tbz
diff --git a/kioslave/bzip2/kbzip2filter.h b/kioslave/bzip2/kbzip2filter.h
new file mode 100644
index 000000000..47ccef915
--- /dev/null
+++ b/kioslave/bzip2/kbzip2filter.h
@@ -0,0 +1,54 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef __kbzip2filter__h
+#define __kbzip2filter__h
+
+#include <config.h>
+
+#if defined( HAVE_BZIP2_SUPPORT )
+
+#include "kfilterbase.h"
+
+class KBzip2Filter : public KFilterBase
+{
+public:
+ KBzip2Filter();
+ virtual ~KBzip2Filter();
+
+ virtual void init( int );
+ virtual int mode() const { return m_mode; }
+ virtual void terminate();
+ virtual void reset();
+ virtual bool readHeader() { return true; } // bzip2 handles it by itself ! Cool !
+ virtual bool writeHeader( const QCString & ) { return true; }
+ virtual void setOutBuffer( char * data, uint maxlen );
+ virtual void setInBuffer( const char * data, uint size );
+ virtual int inBufferAvailable() const;
+ virtual int outBufferAvailable() const;
+ virtual Result uncompress();
+ virtual Result compress( bool finish );
+private:
+ class KBzip2FilterPrivate;
+ KBzip2FilterPrivate *d;
+ int m_mode;
+};
+
+#endif
+
+#endif
diff --git a/kioslave/file/Makefile.am b/kioslave/file/Makefile.am
new file mode 100644
index 000000000..0dd7a760f
--- /dev/null
+++ b/kioslave/file/Makefile.am
@@ -0,0 +1,22 @@
+## Makefile.am of kdebase/kioslave/file
+
+AM_CPPFLAGS = -D_LARGEFILE64_SOURCE
+
+INCLUDES = $(all_includes)
+
+####### Files
+
+kde_module_LTLIBRARIES = kio_file.la
+
+kio_file_la_SOURCES = file.cc
+kio_file_la_LIBADD = $(LIB_KIO)
+kio_file_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+noinst_HEADERS = file.h
+
+fileinclude_HEADERS = file.h
+fileincludedir = $(includedir)/kio
+
+METASOURCES = AUTO
+
+kdelnkdir = $(kde_servicesdir)
+kdelnk_DATA = file.protocol
diff --git a/kioslave/file/file.cc b/kioslave/file/file.cc
new file mode 100644
index 000000000..718d42125
--- /dev/null
+++ b/kioslave/file/file.cc
@@ -0,0 +1,1797 @@
+/*
+ Copyright (C) 2000-2002 Stephan Kulow <coolo@kde.org>
+ Copyright (C) 2000-2002 David Faure <faure@kde.org>
+ Copyright (C) 2000-2002 Waldo Bastian <bastian@kde.org>
+
+ 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 <config.h>
+
+#include <qglobal.h> //for Q_OS_XXX
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#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 <sys/sendfile.h>
+#endif
+
+#ifdef USE_POSIX_ACL
+#include <sys/acl.h>
+#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
+#include <acl/libacl.h>
+#else
+#include <posixacladdons.h>
+#endif
+#endif
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <utime.h>
+#include <unistd.h>
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include <qvaluelist.h>
+#include <qregexp.h>
+
+#include <kshred.h>
+#include <kdebug.h>
+#include <kurl.h>
+#include <kinstance.h>
+#include <ksimpleconfig.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <qfile.h>
+#include <qstrlist.h>
+#include "file.h"
+#include <limits.h>
+#include <kprocess.h>
+#include <kmountpoint.h>
+#include <kstandarddirs.h>
+
+#ifdef HAVE_VOLMGT
+#include <volmgt.h>
+#include <sys/mnttab.h>
+#endif
+
+#include <kstandarddirs.h>
+#include <kio/ioslave_defaults.h>
+#include <klargefile.h>
+#include <kglobal.h>
+#include <kmimetype.h>
+
+using namespace KIO;
+
+#define MAX_IPC_SIZE (1024*32)
+
+static QString testLogFile( const char *_filename );
+#ifdef USE_POSIX_ACL
+static QString aclAsString( acl_t p_acl );
+static bool isExtendedACL( acl_t p_acl );
+static void appendACLAtoms( const QCString & 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 )
+{
+ KLocale::setMainCatalogue("kdelibs");
+ KInstance instance( "kio_file" );
+ ( void ) KGlobal::locale();
+
+ kdDebug(7101) << "Starting " << getpid() << endl;
+
+ if (argc != 4)
+ {
+ fprintf(stderr, "Usage: kio_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 QCString &pool, const QCString &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 QString ACLString = metaData( "ACL_STRING" );
+ const QString 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 )
+{
+ QCString _path( QFile::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( KIO::ERR_ACCESS_DENIED, url.path() );
+ break;
+ case ENOTSUP:
+ error( KIO::ERR_UNSUPPORTED_ACTION, url.path() );
+ break;
+ case ENOSPC:
+ error( KIO::ERR_DISK_FULL, url.path() );
+ break;
+ default:
+ error( KIO::ERR_CANNOT_CHMOD, url.path() );
+ }
+ } else
+ finished();
+}
+
+void FileProtocol::mkdir( const KURL& url, int permissions )
+{
+ QCString _path( QFile::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( KIO::ERR_ACCESS_DENIED, url.path() );
+ return;
+ } else if ( errno == ENOSPC ) {
+ error( KIO::ERR_DISK_FULL, url.path() );
+ return;
+ } else {
+ error( KIO::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( KIO::ERR_DIR_ALREADY_EXIST, url.path() );
+ return;
+ }
+ error( KIO::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;
+ }
+
+ QCString _path( QFile::encodeName(url.path()));
+ KDE_struct_stat buff;
+ if ( KDE_stat( _path.data(), &buff ) == -1 ) {
+ if ( errno == EACCES )
+ error( KIO::ERR_ACCESS_DENIED, url.path() );
+ else
+ error( KIO::ERR_DOES_NOT_EXIST, url.path() );
+ return;
+ }
+
+ if ( S_ISDIR( buff.st_mode ) ) {
+ error( KIO::ERR_IS_DIRECTORY, url.path() );
+ return;
+ }
+ if ( !S_ISREG( buff.st_mode ) ) {
+ error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.path() );
+ return;
+ }
+
+ int fd = KDE_open( _path.data(), O_RDONLY);
+ if ( fd < 0 ) {
+ error( KIO::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() );
+
+ KIO::filesize_t processed_size = 0;
+
+ QString resumeOffset = metaData("resume");
+ if ( !resumeOffset.isEmpty() )
+ {
+ bool ok;
+ KIO::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: " << KIO::number(offset) << endl;
+ }
+ }
+ }
+
+ totalSize( buff.st_size );
+
+ char buffer[ MAX_IPC_SIZE ];
+ QByteArray array;
+
+ while( 1 )
+ {
+ int n = ::read( fd, buffer, MAX_IPC_SIZE );
+ if (n == -1)
+ {
+ if (errno == EINTR)
+ continue;
+ error( KIO::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: " << KIO::number (processed_size) << endl;
+ }
+
+ data( QByteArray() );
+
+ 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 )
+{
+ QString dest_orig = url.path();
+ QCString _dest_orig( QFile::encodeName(dest_orig));
+
+ kdDebug(7101) << "put(): " << dest_orig << ", mode=" << _mode << endl;
+
+ QString dest_part( dest_orig );
+ dest_part += QString::fromLatin1(".part");
+ QCString _dest_part( QFile::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 "
+ << KIO::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( KIO::ERR_DIR_ALREADY_EXIST, dest_orig );
+ else
+ error( KIO::ERR_FILE_ALREADY_EXIST, dest_orig );
+ return;
+ }
+
+ int result;
+ QString dest;
+ QCString _dest;
+
+ int fd = -1;
+
+ // Loop until we got 0 (end of data)
+ do
+ {
+ QByteArray 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 = QFile::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( KIO::ERR_WRITE_ACCESS_DENIED, dest );
+ else
+ error( KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest );
+ return;
+ }
+ }
+
+ if (write_all( fd, buffer.data(), buffer.size()))
+ {
+ if ( errno == ENOSPC ) // disk full
+ {
+ error( KIO::ERR_DISK_FULL, dest_orig);
+ result = -2; // means: remove dest file
+ }
+ else
+ {
+ kdWarning(7101) << "Couldn't write. Error:" << strerror(errno) << endl;
+ error( KIO::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( KIO::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( KIO::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 ( KIO::testFileSystemFlag( _dest_orig, KIO::SupportsChmod ) )
+ warning( i18n( "Could not change permissions for\n%1" ).arg( dest_orig ) );
+ }
+ }
+
+ // set modification time
+ const QString mtimeStr = metaData( "modified" );
+ if ( !mtimeStr.isEmpty() ) {
+ QDateTime dt = QDateTime::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;
+
+ QCString _src( QFile::encodeName(src.path()));
+ QCString _dest( QFile::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( KIO::ERR_ACCESS_DENIED, src.path() );
+ else
+ error( KIO::ERR_DOES_NOT_EXIST, src.path() );
+ return;
+ }
+
+ if ( S_ISDIR( buff_src.st_mode ) ) {
+ error( KIO::ERR_IS_DIRECTORY, src.path() );
+ return;
+ }
+ if ( S_ISFIFO( buff_src.st_mode ) || S_ISSOCK ( buff_src.st_mode ) ) {
+ error( KIO::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( KIO::ERR_DIR_ALREADY_EXIST, dest.path() );
+ return;
+ }
+
+ if ( same_inode( buff_dest, buff_src) )
+ {
+ error( KIO::ERR_IDENTICAL_FILES, dest.path() );
+ return;
+ }
+
+ if (!_overwrite)
+ {
+ error( KIO::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( KIO::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( KIO::ERR_WRITE_ACCESS_DENIED, dest.path() );
+ } else {
+ error( KIO::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 );
+
+ KIO::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( KIO::ERR_DISK_FULL, dest.path());
+ remove( _dest.data() );
+ }
+ else {
+ error( KIO::ERR_SLAVE_DEFINED,
+ i18n("Cannot copy file from %1 to %2. (Errno: %3)")
+ .arg( src.path() ).arg( dest.path() ).arg( errno ) );
+ }
+ } else
+#endif
+ error( KIO::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( KIO::ERR_DISK_FULL, dest.path());
+ remove( _dest.data() );
+ }
+ else
+ {
+ kdWarning(7101) << "Couldn't write[2]. Error:" << strerror(errno) << endl;
+ error( KIO::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( KIO::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 ( KIO::testFileSystemFlag( _dest, KIO::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() << QString::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 )
+{
+ QCString _src( QFile::encodeName(src.path()));
+ QCString _dest( QFile::encodeName(dest.path()));
+ KDE_struct_stat buff_src;
+ if ( KDE_lstat( _src.data(), &buff_src ) == -1 ) {
+ if ( errno == EACCES )
+ error( KIO::ERR_ACCESS_DENIED, src.path() );
+ else
+ error( KIO::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( KIO::ERR_DIR_ALREADY_EXIST, dest.path() );
+ return;
+ }
+
+ if ( same_inode( buff_dest, buff_src) )
+ {
+ error( KIO::ERR_IDENTICAL_FILES, dest.path() );
+ return;
+ }
+
+ if (!_overwrite)
+ {
+ error( KIO::ERR_FILE_ALREADY_EXIST, dest.path() );
+ return;
+ }
+ }
+
+ if ( ::rename( _src.data(), _dest.data()))
+ {
+ if (( errno == EACCES ) || (errno == EPERM)) {
+ error( KIO::ERR_ACCESS_DENIED, dest.path() );
+ }
+ else if (errno == EXDEV) {
+ error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("rename"));
+ }
+ else if (errno == EROFS) { // The file is on a read-only filesystem
+ error( KIO::ERR_CANNOT_DELETE, src.path() );
+ }
+ else {
+ error( KIO::ERR_CANNOT_RENAME, src.path() );
+ }
+ return;
+ }
+
+ finished();
+}
+
+void FileProtocol::symlink( const QString &target, const KURL &dest, bool overwrite )
+{
+ // Assume dest is local too (wouldn't be here otherwise)
+ if ( ::symlink( QFile::encodeName( target ), QFile::encodeName( dest.path() ) ) == -1 )
+ {
+ // Does the destination already exist ?
+ if ( errno == EEXIST )
+ {
+ if ( overwrite )
+ {
+ // Try to delete the destination
+ if ( unlink( QFile::encodeName( dest.path() ) ) != 0 )
+ {
+ error( KIO::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( QFile::encodeName( dest.path() ), &buff_dest );
+ if (S_ISDIR(buff_dest.st_mode))
+ error( KIO::ERR_DIR_ALREADY_EXIST, dest.path() );
+ else
+ error( KIO::ERR_FILE_ALREADY_EXIST, dest.path() );
+ return;
+ }
+ }
+ else
+ {
+ // Some error occurred while we tried to symlink
+ error( KIO::ERR_CANNOT_SYMLINK, dest.path() );
+ return;
+ }
+ }
+ finished();
+}
+
+void FileProtocol::del( const KURL& url, bool isfile)
+{
+ QCString _path( QFile::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( KIO::ERR_ACCESS_DENIED, url.path());
+ else if (errno == EISDIR)
+ error( KIO::ERR_IS_DIRECTORY, url.path());
+ else
+ error( KIO::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( KIO::ERR_ACCESS_DENIED, url.path());
+ else {
+ kdDebug( 7101 ) << "could not rmdir " << perror << endl;
+ error( KIO::ERR_COULD_NOT_RMDIR, url.path() );
+ return;
+ }
+ }
+ }
+
+ finished();
+}
+
+
+QString FileProtocol::getUserName( uid_t uid )
+{
+ QString *temp;
+ temp = usercache.find( uid );
+ if ( !temp ) {
+ struct passwd *user = getpwuid( uid );
+ if ( user ) {
+ usercache.insert( uid, new QString(QString::fromLatin1(user->pw_name)) );
+ return QString::fromLatin1( user->pw_name );
+ }
+ else
+ return QString::number( uid );
+ }
+ else
+ return *temp;
+}
+
+QString FileProtocol::getGroupName( gid_t gid )
+{
+ QString *temp;
+ temp = groupcache.find( gid );
+ if ( !temp ) {
+ struct group *grp = getgrgid( gid );
+ if ( grp ) {
+ groupcache.insert( gid, new QString(QString::fromLatin1(grp->gr_name)) );
+ return QString::fromLatin1( grp->gr_name );
+ }
+ else
+ return QString::number( gid );
+ }
+ else
+ return *temp;
+}
+
+
+
+bool FileProtocol::createUDSEntry( const QString & filename, const QCString & 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 kio_file for returning the complete
+ // details. Please consider doing it in your kioslave if you're using this one as a model :)
+ UDSAtom atom;
+ atom.m_uds = KIO::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 = KIO::UDS_LINK_DEST;
+ atom.m_str = QFile::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 = KIO::UDS_FILE_TYPE;
+ atom.m_long = type;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_ACCESS;
+ atom.m_long = access;
+ entry.append( atom );
+
+ atom.m_uds = KIO::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 = KIO::UDS_FILE_TYPE;
+ atom.m_long = type;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_ACCESS;
+ atom.m_long = access;
+ entry.append( atom );
+
+ atom.m_uds = KIO::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 = KIO::UDS_MODIFICATION_TIME;
+ atom.m_long = buff.st_mtime;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_USER;
+ atom.m_str = getUserName( buff.st_uid );
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_GROUP;
+ atom.m_str = getGroupName( buff.st_gid );
+ entry.append( atom );
+
+ atom.m_uds = KIO::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
+ */
+ QCString _path( QFile::encodeName(url.path(-1)));
+
+ QString sDetails = metaData(QString::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( KIO::ERR_DOES_NOT_EXIST, url.path(-1) );
+ return;
+ }
+#if 0
+///////// debug code
+ KIO::UDSEntry::ConstIterator it = entry.begin();
+ for( ; it != entry.end(); it++ ) {
+ switch ((*it).m_uds) {
+ case KIO::UDS_FILE_TYPE:
+ kdDebug(7101) << "File Type : " << (mode_t)((*it).m_long) << endl;
+ break;
+ case KIO::UDS_ACCESS:
+ kdDebug(7101) << "Access permissions : " << (mode_t)((*it).m_long) << endl;
+ break;
+ case KIO::UDS_USER:
+ kdDebug(7101) << "User : " << ((*it).m_str.ascii() ) << endl;
+ break;
+ case KIO::UDS_GROUP:
+ kdDebug(7101) << "Group : " << ((*it).m_str.ascii() ) << endl;
+ break;
+ case KIO::UDS_NAME:
+ kdDebug(7101) << "Name : " << ((*it).m_str.ascii() ) << endl;
+ //m_strText = decodeFileName( (*it).m_str );
+ break;
+ case KIO::UDS_URL:
+ kdDebug(7101) << "URL : " << ((*it).m_str.ascii() ) << endl;
+ break;
+ case KIO::UDS_MIME_TYPE:
+ kdDebug(7101) << "MimeType : " << ((*it).m_str.ascii() ) << endl;
+ break;
+ case KIO::UDS_LINK_DEST:
+ kdDebug(7101) << "LinkDest : " << ((*it).m_str.ascii() ) << endl;
+ break;
+ case KIO::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;
+ }
+
+ QCString _path( QFile::encodeName(url.path()));
+
+ KDE_struct_stat buff;
+ if ( KDE_stat( _path.data(), &buff ) == -1 ) {
+ error( KIO::ERR_DOES_NOT_EXIST, url.path() );
+ return;
+ }
+
+ if ( !S_ISDIR( buff.st_mode ) ) {
+ error( KIO::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( KIO::ERR_CANNOT_ENTER_DIRECTORY, url.path() );
+ break;
+ }
+ return;
+ }
+
+ // Don't make this a QStringList. The locale file name we get here
+ // should be passed intact to createUDSEntry to avoid problems with
+ // files where QFile::encodeName(QFile::decodeName(a)) != a.
+ QStrList 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 */
+ char path_buffer[PATH_MAX];
+ (void) getcwd(path_buffer, PATH_MAX - 1);
+ if ( chdir( _path.data() ) ) {
+ if (errno == EACCES)
+ error(ERR_ACCESS_DENIED, _path);
+ else
+ error(ERR_CANNOT_ENTER_DIRECTORY, _path);
+ finished();
+ }
+
+ UDSEntry entry;
+ QStrListIterator it(entryNames);
+ for (; it.current(); ++it) {
+ entry.clear();
+ if ( createUDSEntry( QFile::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);
+
+ finished();
+}
+
+/*
+void FileProtocol::testDir( const QString& path )
+{
+ QCString _path( QFile::encodeName(path));
+ KDE_struct_stat buff;
+ if ( KDE_stat( _path.data(), &buff ) == -1 ) {
+ error( KIO::ERR_DOES_NOT_EXIST, path );
+ return;
+ }
+
+ if ( S_ISDIR( buff.st_mode ) )
+ isDirectory();
+ else
+ isFile();
+
+ finished();
+}
+*/
+
+void FileProtocol::special( const QByteArray &data)
+{
+ int tmp;
+ QDataStream stream(data, IO_ReadOnly);
+
+ stream >> tmp;
+ switch (tmp) {
+ case 1:
+ {
+ QString fstype, dev, point;
+ Q_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:
+ {
+ QString point;
+ stream >> point;
+ bool ok = pumount( point );
+ if (ok)
+ finished();
+ else
+ unmount( point );
+ }
+ break;
+
+ case 3:
+ {
+ QString filename;
+ stream >> filename;
+ KShred shred( filename );
+ connect( &shred, SIGNAL( processedSize( KIO::filesize_t ) ),
+ this, SLOT( slotProcessedSize( KIO::filesize_t ) ) );
+ connect( &shred, SIGNAL( infoMessage( const QString & ) ),
+ this, SLOT( slotInfoMessage( const QString & ) ) );
+ if (!shred.shred())
+ error( KIO::ERR_CANNOT_DELETE, filename );
+ else
+ finished();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+// Connected to KShred
+void FileProtocol::slotProcessedSize( KIO::filesize_t bytes )
+{
+ kdDebug(7101) << "FileProtocol::slotProcessedSize (" << (unsigned int) bytes << ")" << endl;
+ processedSize( bytes );
+}
+
+// Connected to KShred
+void FileProtocol::slotInfoMessage( const QString & msg )
+{
+ kdDebug(7101) << "FileProtocol::slotInfoMessage (" << msg << ")" << endl;
+ infoMessage( msg );
+}
+
+void FileProtocol::mount( bool _ro, const char *_fstype, const QString& _dev, const QString& _point )
+{
+ kdDebug(7101) << "FileProtocol::mount _fstype=" << _fstype << endl;
+ QCString buffer;
+
+#ifdef HAVE_VOLMGT
+ /*
+ * support for Solaris volume management
+ */
+ QString err;
+ QCString devname = QFile::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( KIO::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( KIO::ERR_COULD_NOT_MOUNT, err );
+ return;
+ }
+#else
+
+
+ KTempFile tmpFile;
+ QCString tmpFileC = QFile::encodeName(tmpFile.name());
+ const char *tmp = tmpFileC.data();
+ QCString dev;
+ if ( _dev.startsWith( "LABEL=" ) ) { // turn LABEL=foo into -L foo (#71430)
+ QString labelName = _dev.mid( 6 );
+ dev = "-L ";
+ dev += QFile::encodeName( KProcess::quote( labelName ) ); // is it correct to assume same encoding as filesystem?
+ } else if ( _dev.startsWith( "UUID=" ) ) { // and UUID=bar into -U bar
+ QString uuidName = _dev.mid( 5 );
+ dev = "-U ";
+ dev += QFile::encodeName( KProcess::quote( uuidName ) );
+ }
+ else
+ dev = QFile::encodeName( KProcess::quote(_dev) ); // get those ready to be given to a shell
+
+ QCString point = QFile::encodeName( KProcess::quote(_point) );
+ bool fstype_empty = !_fstype || !*_fstype;
+ QCString fstype = KProcess::quote(_fstype).latin1(); // good guess
+ QCString readonly = _ro ? "-r" : "";
+ QString epath = QString::fromLatin1(getenv("PATH"));
+ QString path = QString::fromLatin1("/sbin:/bin");
+ if(!epath.isEmpty())
+ path += QString::fromLatin1(":") + epath;
+ QString mountProg = KGlobal::dirs()->findExe("mount", path);
+ if (mountProg.isEmpty()){
+ error( KIO::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 );
+#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() );
+
+ QString err = testLogFile( tmp );
+ if ( err.isEmpty() && mount_ret == 0)
+ {
+ finished();
+ return;
+ }
+ else
+ {
+ // Didn't work - or maybe we just got a warning
+ QString mp = KIO::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( KIO::ERR_COULD_NOT_MOUNT, err );
+ return;
+ }
+ }
+ }
+ }
+#endif /* ! HAVE_VOLMGT */
+}
+
+
+void FileProtocol::unmount( const QString& _point )
+{
+ QCString buffer;
+
+ KTempFile tmpFile;
+ QCString tmpFileC = QFile::encodeName(tmpFile.name());
+ QString 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( KIO::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: "
+ << QFile::encodeName(_point).data()
+ << ": " << err << endl;
+ error( KIO::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';
+ QCString qdevname(QFile::encodeName(KProcess::quote(QFile::decodeName(QCString(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 QString -> 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( KIO::ERR_COULD_NOT_UNMOUNT, err );
+ return;
+ }
+#else
+ QString epath = getenv("PATH");
+ QString path = QString::fromLatin1("/sbin:/bin");
+ if (!epath.isEmpty())
+ path += ":" + epath;
+ QString umountProg = KGlobal::dirs()->findExe("umount", path);
+
+ if (umountProg.isEmpty()) {
+ error( KIO::ERR_COULD_NOT_UNMOUNT, i18n("Could not find program \"umount\""));
+ return;
+ }
+ buffer.sprintf( "%s %s 2>%s", umountProg.latin1(), QFile::encodeName(KProcess::quote(_point)).data(), tmp );
+ system( buffer.data() );
+#endif /* HAVE_VOLMGT */
+
+ err = testLogFile( tmp );
+ if ( err.isEmpty() )
+ finished();
+ else
+ error( KIO::ERR_COULD_NOT_UNMOUNT, err );
+}
+
+/*************************************
+ *
+ * pmount handling
+ *
+ *************************************/
+
+bool FileProtocol::pmount(const QString &dev)
+{
+ QString epath = getenv("PATH");
+ QString path = QString::fromLatin1("/sbin:/bin");
+ if (!epath.isEmpty())
+ path += ":" + epath;
+ QString pmountProg = KGlobal::dirs()->findExe("pmount", path);
+
+ if (pmountProg.isEmpty())
+ return false;
+
+ QCString buffer;
+ buffer.sprintf( "%s %s", QFile::encodeName(pmountProg).data(),
+ QFile::encodeName(KProcess::quote(dev)).data() );
+
+ int res = system( buffer.data() );
+
+ return res==0;
+}
+
+bool FileProtocol::pumount(const QString &point)
+{
+ QString real_point = KStandardDirs::realPath(point);
+
+ KMountPoint::List mtab = KMountPoint::currentMountPoints();
+
+ KMountPoint::List::const_iterator it = mtab.begin();
+ KMountPoint::List::const_iterator end = mtab.end();
+
+ QString dev;
+
+ for (; it!=end; ++it)
+ {
+ QString tmp = (*it)->mountedFrom();
+ QString mp = (*it)->mountPoint();
+ mp = KStandardDirs::realPath(mp);
+
+ if (mp==real_point)
+ dev = KStandardDirs::realPath(tmp);
+ }
+
+ if (dev.isEmpty()) return false;
+ if (dev.endsWith("/")) dev.truncate(dev.length()-1);
+
+ QString epath = getenv("PATH");
+ QString path = QString::fromLatin1("/sbin:/bin");
+ if (!epath.isEmpty())
+ path += ":" + epath;
+ QString pumountProg = KGlobal::dirs()->findExe("pumount", path);
+
+ if (pumountProg.isEmpty())
+ return false;
+
+ QCString buffer;
+ buffer.sprintf( "%s %s", QFile::encodeName(pumountProg).data(),
+ QFile::encodeName(KProcess::quote(dev)).data() );
+
+ int res = system( buffer.data() );
+
+ return res==0;
+}
+
+/*************************************
+ *
+ * Utilities
+ *
+ *************************************/
+
+static QString testLogFile( const char *_filename )
+{
+ char buffer[ 1024 ];
+ KDE_struct_stat buff;
+
+ QString 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(QFile::decodeName(_filename));
+ return result;
+ }
+
+ result = "";
+ const char *p = "";
+ while ( p != 0L ) {
+ p = fgets( buffer, sizeof(buffer)-1, f );
+ if ( p != 0L )
+ result += QString::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 QString aclAsString( acl_t acl )
+{
+ char *aclString = acl_to_text( acl, 0 );
+ QString ret = QString::fromLatin1( aclString );
+ acl_free( (void*)aclString );
+ return ret;
+}
+
+static void appendACLAtoms( const QCString & 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 = KIO::UDS_EXTENDED_ACL;
+ atom.m_long = 1;
+ entry.append( atom );
+ }
+ if ( withACL ) {
+ if ( acl ) {
+ atom.m_uds = KIO::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 = KIO::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/kioslave/file/file.h b/kioslave/file/file.h
new file mode 100644
index 000000000..eef71798b
--- /dev/null
+++ b/kioslave/file/file.h
@@ -0,0 +1,98 @@
+/*
+ Copyright (C) 2000-2002 Stephan Kulow <coolo@kde.org>
+ Copyright (C) 2000-2002 David Faure <faure@kde.org>
+ Copyright (C) 2000-2002 Waldo Bastian <bastian@kde.org>
+
+ 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.
+*/
+
+#ifndef __file_h__
+#define __file_h__ "$Id$"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <qobject.h>
+#include <qintdict.h>
+#include <qstring.h>
+#include <qvaluelist.h>
+
+#include <kio/global.h>
+#include <kio/slavebase.h>
+
+// Note that this header file is installed, so think twice
+// before breaking binary compatibility (read: it is forbidden :)
+
+class FileProtocol : public QObject, public KIO::SlaveBase
+{
+ Q_OBJECT
+public:
+ FileProtocol( const QCString &pool, const QCString &app);
+ virtual ~FileProtocol() { }
+
+ virtual void get( const KURL& url );
+ virtual void put( const KURL& url, int permissions,
+ bool overwrite, bool resume );
+ virtual void copy( const KURL &src, const KURL &dest,
+ int permissions, bool overwrite );
+ virtual void rename( const KURL &src, const KURL &dest,
+ bool overwrite );
+ virtual void symlink( const QString &target, const KURL &dest,
+ bool overwrite );
+
+ virtual void stat( const KURL& url );
+ virtual void listDir( const KURL& url );
+ virtual void mkdir( const KURL& url, int permissions );
+ virtual void chmod( const KURL& url, int permissions );
+ virtual void del( const KURL& url, bool isfile);
+
+ /**
+ * Special commands supported by this slave:
+ * 1 - mount
+ * 2 - unmount
+ * 3 - shred
+ */
+ virtual void special( const QByteArray &data);
+ void unmount( const QString& point );
+ void mount( bool _ro, const char *_fstype, const QString& dev, const QString& point );
+ bool pumount( const QString &point );
+ bool pmount( const QString &dev );
+
+protected slots:
+ void slotProcessedSize( KIO::filesize_t _bytes );
+ void slotInfoMessage( const QString & msg );
+
+protected:
+
+ bool createUDSEntry( const QString & filename, const QCString & path, KIO::UDSEntry & entry,
+ short int details, bool withACL );
+ int setACL( const char *path, mode_t perm, bool _directoryDefault );
+
+ QString getUserName( uid_t uid );
+ QString getGroupName( gid_t gid );
+
+ QIntDict<QString> usercache; // maps long ==> QString *
+ QIntDict<QString> groupcache;
+
+ class FileProtocolPrivate;
+ FileProtocolPrivate *d;
+};
+
+#endif
diff --git a/kioslave/file/file.protocol b/kioslave/file/file.protocol
new file mode 100644
index 000000000..ae3487999
--- /dev/null
+++ b/kioslave/file/file.protocol
@@ -0,0 +1,15 @@
+[Protocol]
+exec=kio_file
+protocol=file
+input=none
+output=filesystem
+listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link
+reading=true
+writing=true
+makedir=true
+deleting=true
+linking=true
+moving=true
+maxInstances=4
+DocPath=kioslave/file.html
+Class=:local
diff --git a/kioslave/ftp/Makefile.am b/kioslave/ftp/Makefile.am
new file mode 100644
index 000000000..cfa3de6ad
--- /dev/null
+++ b/kioslave/ftp/Makefile.am
@@ -0,0 +1,16 @@
+INCLUDES= $(all_includes)
+
+####### Files
+
+kde_module_LTLIBRARIES = kio_ftp.la
+
+kio_ftp_la_SOURCES = ftp.cc
+kio_ftp_la_LIBADD = $(LIB_KIO)
+kio_ftp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+
+noinst_HEADERS = ftp.h
+
+kdelnk_DATA = ftp.protocol
+kdelnkdir = $(kde_servicesdir)
+
+
diff --git a/kioslave/ftp/configure.in.in b/kioslave/ftp/configure.in.in
new file mode 100644
index 000000000..0c94a9b9b
--- /dev/null
+++ b/kioslave/ftp/configure.in.in
@@ -0,0 +1,5 @@
+dnl For kio_ftp
+AC_LANG_SAVE
+AC_LANG_CPLUSPLUS
+AC_CHECK_FUNCS( setfsent )
+AC_LANG_RESTORE
diff --git a/kioslave/ftp/ftp.cc b/kioslave/ftp/ftp.cc
new file mode 100644
index 000000000..4da1912d7
--- /dev/null
+++ b/kioslave/ftp/ftp.cc
@@ -0,0 +1,2652 @@
+// -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*-
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ 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 KIO_FTP_PRIVATE_INCLUDE
+#include "ftp.h"
+
+#include <sys/stat.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#if TIME_WITH_SYS_TIME
+#include <time.h>
+#endif
+
+#include <qdir.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kinstance.h>
+#include <kmimemagic.h>
+#include <kmimetype.h>
+#include <ksockaddr.h>
+#include <ksocketaddress.h>
+#include <kio/ioslave_defaults.h>
+#include <kio/slaveconfig.h>
+#include <kremoteencoding.h>
+#include <klargefile.h>
+
+#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 QString::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 QString::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 KIO
+// JPF: anyhow, in KDE 3.2.0 I found diffent MAX_IPC_SIZE definitions!
+namespace KIO {
+ enum buffersizes
+ { /**
+ * largest buffer size that should be used to transfer data between
+ * KIO slaves using the data() function
+ */
+ maximumIpcSize = 32 * 1024,
+ /**
+ * this is a reasonable value for an initial read() that a KIO 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;
+ }
+}
+
+KIO::filesize_t Ftp::UnknownSize = (KIO::filesize_t)-1;
+
+using namespace KIO;
+
+extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); }
+
+int kdemain( int argc, char **argv )
+{
+ KLocale::setMainCatalogue("kdelibs");
+ KInstance instance( "kio_ftp" );
+ ( void ) KGlobal::locale();
+
+ kdDebug(7102) << "Starting " << getpid() << endl;
+
+ if (argc != 4)
+ {
+ fprintf(stderr, "Usage: kio_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;
+ QString strMsg = KExtendedSocket::strError(status(), systemError());
+ strMsg.prepend("connect failed (code %1): ");
+ return errorMessage(iErrorCode, 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 QCString &pool, const QCString &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 QString& _host, int _port, const QString& _user,
+ const QString& _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, QString::null );
+ return false;
+ }
+
+ assert( !m_bLoggedOn );
+
+ m_initialPath = QString::null;
+ m_currentPath = QString::null;
+
+ QString 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 QString &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;
+ QString 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 );
+
+ QString user = m_user;
+ QString pass = m_pass;
+
+ if ( config()->readBoolEntry("EnableAutoLogin") )
+ {
+ QString 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 );
+
+ QCString 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()) )
+ {
+ QString 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("<b>%1</b>").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 += QString::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 Sgaard) 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 <Keith.Refson@earth.ox.ac.uk>
+ 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;
+ }
+
+ QString 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 ()
+{
+ QString macro = metaData( "autoLoginMacro" );
+
+ if ( macro.isEmpty() )
+ return;
+
+ QStringList list = QStringList::split('\n', macro);
+
+ for(QStringList::Iterator it = list.begin() ; it != list.end() ; ++it )
+ {
+ if ( (*it).startsWith("init") )
+ {
+ list = QStringList::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 QCString& 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...
+ QCString 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 KSocketAddress *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<const KInetSocketAddress*>(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 KSocketAddress *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<const KInetSocketAddress*>(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<const KInetSocketAddress*>(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<const KInetSocketAddress*>(m_data->localAddress());
+ if (sin == NULL)
+ return ERR_INTERNAL;
+
+ // QString command = QString::fromLatin1("eprt |%1|%2|%3|").arg(sin->ianaFamily())
+ // .arg(sin->nodeName())
+ // .arg(sin->port());
+ QCString 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<const KInetSocketAddress*>(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<const KInetSocketAddress*>(m_data->localAddress());
+ struct sockaddr* psa = (struct sockaddr*)pAddr->addressV4();
+ unsigned char* pData = (unsigned char*)(psa->sa_data);
+ QCString 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 QString & _path, char _mode,
+ int errorcode, KIO::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;
+ }
+ }
+
+ QCString tmp = _command;
+ QString 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;
+
+ QString path = remoteEncoding()->encode(url);
+ QCString buf = "mkd ";
+ buf += remoteEncoding()->encode(path);
+
+ if( !ftpSendCmd( buf ) || (m_iRespType != 2) )
+ {
+ QString 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 QString & src, const QString & 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;
+ }
+
+ int pos = src.findRev("/");
+ if( !ftpFolder(src.left(pos+1), false) )
+ return false;
+
+ QCString from_cmd = "RNFR ";
+ from_cmd += remoteEncoding()->encode(src.mid(pos+1));
+ if( !ftpSendCmd( from_cmd ) || (m_iRespType != 3) )
+ return false;
+
+ QCString 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
+
+ QCString 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 QString & 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)
+ QCString 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 QString & 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 QString& filename, bool isDir )
+{
+ UDSEntry entry;
+ UDSAtom atom;
+
+ atom.m_uds = KIO::UDS_NAME;
+ atom.m_str = filename;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_FILE_TYPE;
+ atom.m_long = isDir ? S_IFDIR : S_IFREG;
+ entry.append( atom );
+
+ atom.m_uds = KIO::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 QString & path, const QString & 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.
+ QString 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 <blah>" in a case insensitive way
+ // But "retr <blah>" 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;
+
+ QString path = QDir::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 = KIO::UDS_NAME;
+ atom.m_str = QString::null;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_FILE_TYPE;
+ atom.m_long = S_IFDIR;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_ACCESS;
+ atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_USER;
+ atom.m_str = "root";
+ entry.append( atom );
+ atom.m_uds = KIO::UDS_GROUP;
+ entry.append( atom );
+
+ // no size
+
+ statEntry( entry );
+ finished();
+ return;
+ }
+
+ KURL tempurl( url );
+ tempurl.setPath( path ); // take the clean one
+ QString listarg; // = tempurl.directory(false /*keep trailing slash*/);
+ QString parentDir;
+ QString filename = tempurl.fileName();
+ Q_ASSERT(!filename.isEmpty());
+ QString 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
+ QString 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 <filename>" 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 = KIO::UDS_NAME;
+ atom.m_str = filename;
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_FILE_TYPE;
+ atom.m_long = S_IFDIR;
+ entry.append( atom );
+
+ atom.m_uds = KIO::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 ?
+ QString 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 QString & path )
+{
+ //QString 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)
+ QString tmp = path.isEmpty() ? QString("/") : 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", QString::null, 'I', ERR_CANNOT_ENTER_DIRECTORY ) )
+ {
+ if ( !ftpOpenCommand( "list", QString::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 )
+ {
+ {
+ QCString 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 = QString::null;
+ }
+ else
+ de.link = QString::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( &currentTime );
+ 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 KLocale 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 = (char*)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, KIO::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;
+ }
+
+ QString 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;
+ }
+
+ KIO::filesize_t bytesLeft = 0;
+ if ( m_size != UnknownSize )
+ bytesLeft = m_size - llOffset;
+
+ kdDebug(7102) << "ftpGet: starting with offset=" << llOffset << endl;
+ KIO::fileoffset_t processed_size = llOffset;
+
+ QByteArray 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 ];
+ QByteArray array;
+ // Get one chunk of data only and send it, KIO::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;
+ QCString buf = "ABOR\r\n";
+ if ( KSocks::self()->write( sControl, buf.data(), buf.length() ) <= 0 ) {
+ error( ERR_COULD_NOT_WRITE, QString::null );
+ return;
+ }
+
+ //
+ kdDebug(7102) << "read resp" << endl;
+ if ( readresp() != '2' )
+ {
+ error( ERR_COULD_NOT_READ, QString::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);
+
+ QString dest_orig = dest_url.path();
+ QString dest_part( dest_orig );
+ dest_part += ".part";
+
+ if ( ftpSize( dest_orig, 'I' ) )
+ {
+ if ( m_size == 0 )
+ { // delete files with zero size
+ QCString 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
+ QCString 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;
+
+ QString 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;
+
+ KIO::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;
+ KIO::fileoffset_t processed_size = offset;
+
+ QByteArray 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) ) )
+ {
+ QCString 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 QString & path, char mode )
+{
+ m_size = UnknownSize;
+ if( !ftpDataMode(mode) )
+ return false;
+
+ QCString 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;
+}
+
+// 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;
+
+ QCString buf;
+ buf.sprintf("TYPE %c", cMode);
+ if( !ftpSendCmd(buf) || (m_iRespType != 2) )
+ return false;
+ m_cDataMode = cMode;
+ return true;
+}
+
+
+bool Ftp::ftpFolder(const QString& path, bool bReportError)
+{
+ QString 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;
+
+ QCString 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 kio 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();
+ QString 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, QString::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, QString sCopyFile,
+ const KURL& url, int permissions, bool overwrite)
+{
+ // check if source is ok ...
+ KDE_struct_stat buff;
+ QCString sSrc( QFile::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 QString sCopyFile,
+ const KURL& url, int permissions, bool overwrite)
+{
+ // check if destination is ok ...
+ KDE_struct_stat buff;
+ QCString sDest( QFile::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?
+ QCString sPart = QFile::encodeName(sCopyFile + ".part");
+ bool bResume = false;
+ bool bPartExists = (KDE_stat( sPart.data(), &buff ) != -1);
+ const bool bMarkPartial = config()->readBoolEntry("MarkPartial", true);
+
+ if(!bMarkPartial)
+ {
+ sPart = QFile::encodeName(sCopyFile);
+ }
+ else if(bPartExists && buff.st_size > 0)
+ { // must not be a folder! please fix a similar bug in kio_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 kio_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 ...
+ KIO::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/kioslave/ftp/ftp.h b/kioslave/ftp/ftp.h
new file mode 100644
index 000000000..e754152d2
--- /dev/null
+++ b/kioslave/ftp/ftp.h
@@ -0,0 +1,598 @@
+// -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*-
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ 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.
+*/
+
+// $Id$
+
+#ifndef __ftp_h__
+#define __ftp_h__
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <qcstring.h>
+#include <qstring.h>
+
+#include <kurl.h>
+#include <kio/slavebase.h>
+#include <kextsock.h>
+#include <ksocks.h>
+
+struct FtpEntry
+{
+ QString name;
+ QString owner;
+ QString group;
+ QString link;
+
+ KIO::filesize_t size;
+ mode_t type;
+ mode_t access;
+ time_t date;
+};
+
+//===============================================================================
+// FtpTextReader A helper class to read text lines from a socket
+//===============================================================================
+
+#ifdef KIO_FTP_PRIVATE_INCLUDE
+class FtpSocket;
+
+class FtpTextReader
+{
+public:
+ FtpTextReader() { textClear(); }
+
+/**
+ * Resets the status of the object, also called from xtor
+ */
+ void textClear();
+
+/**
+ * Read a line from the socket into m_szText. Only the first RESP_READ_LIMIT
+ * characters are copied. If the server response is longer all extra data up to
+ * the new-line gets discarded. An ending CR gets stripped. The number of chars
+ * in the buffer is returned. Use textToLong() to check for truncation!
+ */
+ int textRead(FtpSocket *pSock);
+
+/**
+ * An accessor to the data read by textRead()
+ */
+ const char* textLine() const { return m_szText; }
+
+/**
+ * Returns true if the last textRead() resulted in a truncated line
+ */
+ bool textTooLong() const { return m_bTextTruncated; }
+
+/**
+ * Returns true if the last textRead() got an EOF or an error
+ */
+ bool textEOF() const { return m_bTextEOF; }
+
+ enum {
+
+ /**
+ * This is the physical size of m_szText. Only up to textReadLimit
+ * characters are used to store a server reply. If the server reply
+ * is longer, the stored line gets truncated - see textTooLong()!
+ */
+ textReadBuffer = 2048,
+
+/**
+ * Max number of chars returned from textLine(). If the server
+ * sends more all chars until the next new-line are discarded.
+ */
+ textReadLimit = 1024
+ };
+
+private:
+ /**
+ * textRead() sets this true on trucation (e.g. line too long)
+ */
+ bool m_bTextTruncated;
+
+ /**
+ * textRead() sets this true if the read returns 0 bytes or error
+ */
+ bool m_bTextEOF;
+
+ /**
+ * textRead() fills this buffer with data
+ */
+ char m_szText[textReadBuffer];
+
+ /**
+ * the number of bytes in the current response line
+ */
+ int m_iTextLine;
+
+ /**
+ * the number of bytes in the response buffer (includes m_iRespLine)
+ */
+ int m_iTextBuff;
+};
+#endif // KIO_FTP_PRIVATE_INCLUDE
+
+//===============================================================================
+// FtpSocket Helper Class for Data or Control Connections
+//===============================================================================
+#ifdef KIO_FTP_PRIVATE_INCLUDE
+class FtpSocket : public FtpTextReader, public KExtendedSocket
+{
+private:
+ // hide the default xtor
+ FtpSocket() {}
+public:
+/**
+ * The one and only public xtor. The string data passed to the
+ * xtor must remain valid during the object's lifetime - it is
+ * used in debug messages to identify the socket instance.
+ */
+ FtpSocket(const char* pszName)
+ {
+ m_pszName = pszName;
+ m_server = -1;
+ }
+
+ ~FtpSocket() { closeSocket(); }
+
+/**
+ * Resets the status of the object, also called from xtor
+ */
+ void closeSocket();
+
+/**
+ * We may have a server connection socket if not in passive mode. This
+ * routine returns the server socket set by setServer. The sock()
+ * function will return the server socket - if it is set.
+ */
+ int server() const { return m_server; }
+
+/**
+ * Set the server socket if arg >= 0, otherwise clear it.
+ */
+ void setServer(int i) { m_server = (i >= 0) ? i : -1; }
+
+/**
+ * returns the effective socket that user used for read/write. See server()
+ */
+ int sock() const { return (m_server != -1) ? m_server : fd(); }
+
+/**
+ * output an debug message via kdDebug
+ */
+ void debugMessage(const char* pszMsg) const;
+
+/**
+ * output an error message via kdError, returns iErrorCode
+ */
+ int errorMessage(int iErrorCode, const char* pszMsg) const;
+
+/**
+ * connect socket and set some options (reuse, keepalive, linger)
+ */
+ int connectSocket(int iTimeOutSec, bool bControl);
+
+/**
+ * utility to simplify calls to ::setsockopt(). Uses sock().
+ */
+ bool setSocketOption(int opt, char*arg, socklen_t len) const;
+
+/**
+ * utility to read data from the effective socket, see sock()
+ */
+ long read(void* pData, long iMaxlen)
+ {
+ return KSocks::self()->read(sock(), pData, iMaxlen);
+ }
+
+/**
+ * utility to write data to the effective socket, see sock()
+ */
+ long write(void* pData, long iMaxlen)
+ {
+ return KSocks::self()->write(sock(), pData, iMaxlen);
+ }
+
+/**
+ * Use the inherited FtpTextReader to read a line from the socket
+ */
+ int textRead()
+ {
+ return FtpTextReader::textRead(this);
+ }
+
+private:
+ const char* m_pszName; // set by the xtor, used for debug output
+ int m_server; // socket override, see setSock()
+};
+#else
+ class FtpSocket;
+#endif // KIO_FTP_PRIVATE_INCLUDE
+
+//===============================================================================
+// Ftp
+//===============================================================================
+class Ftp : public KIO::SlaveBase
+{
+ // Ftp() {}
+
+public:
+ Ftp( const QCString &pool, const QCString &app );
+ virtual ~Ftp();
+
+ virtual void setHost( const QString& host, int port, const QString& user, const QString& pass );
+
+ /**
+ * Connects to a ftp server and logs us in
+ * m_bLoggedOn is set to true if logging on was successful.
+ * It is set to false if the connection becomes closed.
+ *
+ */
+ virtual void openConnection();
+
+ /**
+ * Closes the connection
+ */
+ virtual void closeConnection();
+
+ virtual void stat( const KURL &url );
+
+ virtual void listDir( const KURL & url );
+ virtual void mkdir( const KURL & url, int permissions );
+ virtual void rename( const KURL & src, const KURL & dest, bool overwrite );
+ virtual void del( const KURL & url, bool isfile );
+ virtual void chmod( const KURL & url, int permissions );
+
+ virtual void get( const KURL& url );
+ virtual void put( const KURL& url, int permissions, bool overwrite, bool resume);
+ //virtual void mimetype( const KURL& url );
+
+ virtual void slave_status();
+
+ /**
+ * Handles the case that one side of the job is a local file
+ */
+ virtual void copy( const KURL &src, const KURL &dest, int permissions, bool overwrite );
+
+private:
+ // ------------------------------------------------------------------------
+ // All the methods named ftpXyz are lowlevel methods that are not exported.
+ // The implement functionality used by the public high-level methods. Some
+ // low-level methods still use error() to emit errors. This behaviour is not
+ // recommended - please return a boolean status or an error code instead!
+ // ------------------------------------------------------------------------
+
+ /**
+ * Status Code returned from ftpPut() and ftpGet(), used to select
+ * source or destination url for error messages
+ */
+ typedef enum {
+ statusSuccess,
+ statusClientError,
+ statusServerError
+ } StatusCode;
+
+ /**
+ * Login Mode for ftpOpenConnection
+ */
+ typedef enum {
+ loginDefered,
+ loginExplicit,
+ loginImplicit
+ } LoginMode;
+
+ /**
+ * Connect and login to the FTP server.
+ *
+ * @param loginMode controls if login info should be sent<br>
+ * loginDefered - must not be logged on, no login info is sent<br>
+ * loginExplicit - must not be logged on, login info is sent<br>
+ * loginImplicit - login info is sent if not logged on
+ *
+ * @return true on success (a login failure would return false).
+ */
+ bool ftpOpenConnection (LoginMode loginMode);
+
+ /**
+ * Executes any auto login macro's as specified in a .netrc file.
+ */
+ void ftpAutoLoginMacro ();
+
+ /**
+ * Called by openConnection. It logs us in.
+ * m_initialPath is set to the current working directory
+ * if logging on was successful.
+ *
+ * @return true on success.
+ */
+ bool ftpLogin();
+
+ /**
+ * 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 ftpSendCmd( const QCString& cmd, int maxretries = 1 );
+
+ /**
+ * Use the SIZE command to get the file size.
+ * @param mode the size depends on the transfer mode, hence this arg.
+ * @return true on success
+ * Gets the size into m_size.
+ */
+ bool ftpSize( const QString & path, char mode );
+
+ /**
+ * Set the current working directory, but only if not yet current
+ */
+ bool ftpFolder(const QString& path, bool bReportError);
+
+ /**
+ * Runs a command on the ftp server like "list" or "retr". In contrast to
+ * ftpSendCmd a data connection is opened. The corresponding socket
+ * sData is available for reading/writing on success.
+ * The connection must be closed afterwards with ftpCloseCommand.
+ *
+ * @param mode is 'A' or 'I'. 'A' means ASCII transfer, 'I' means binary transfer.
+ * @param errorcode the command-dependent error code to emit on error
+ *
+ * @return true if the command was accepted by the server.
+ */
+ bool ftpOpenCommand( const char *command, const QString & path, char mode,
+ int errorcode, KIO::fileoffset_t offset = 0 );
+
+ /**
+ * The counterpart to openCommand.
+ * Closes data sockets and then reads line sent by server at
+ * end of command.
+ * @return false on error (line doesn't start with '2')
+ */
+ bool ftpCloseCommand();
+
+ /**
+ * Send "TYPE I" or "TYPE A" only if required, see m_cDataMode.
+ *
+ * Use 'A' to select ASCII and 'I' to select BINARY mode. If
+ * cMode is '?' the m_bTextMode flag is used to choose a mode.
+ */
+ bool ftpDataMode(char cMode);
+
+ //void ftpAbortTransfer();
+
+ /**
+ * Used by ftpOpenCommand, return 0 on success or an error code
+ */
+ int ftpOpenDataConnection();
+
+ /**
+ * closes a data connection, see ftpOpenDataConnection()
+ */
+ void ftpCloseDataConnection();
+
+ /**
+ * Helper for ftpOpenDataConnection
+ */
+ int ftpOpenPASVDataConnection();
+ /**
+ * Helper for ftpOpenDataConnection
+ */
+ int ftpOpenEPSVDataConnection();
+ /**
+ * Helper for ftpOpenDataConnection
+ */
+ int ftpOpenEPRTDataConnection();
+ /**
+ * Helper for ftpOpenDataConnection
+ */
+ int ftpOpenPortDataConnection();
+
+ /**
+ * ftpAcceptConnect - wait for incoming connection
+ *
+ * return -2 on error or timeout
+ * otherwise returns socket descriptor
+ */
+ int ftpAcceptConnect();
+
+ bool ftpChmod( const QString & path, int permissions );
+
+ // used by listDir
+ bool ftpOpenDir( const QString & path );
+ /**
+ * Called to parse directory listings, call this until it returns false
+ */
+ bool ftpReadDir(FtpEntry& ftpEnt);
+
+ /**
+ * Helper to fill an UDSEntry
+ */
+ void ftpCreateUDSEntry( const QString & filename, FtpEntry& ftpEnt, KIO::UDSEntry& entry, bool isDir );
+
+ void ftpShortStatAnswer( const QString& filename, bool isDir );
+
+ void ftpStatAnswerNotFound( const QString & path, const QString & filename );
+
+ /**
+ * This is the internal implementation of rename() - set put().
+ *
+ * @return true on success.
+ */
+ bool ftpRename( const QString & src, const QString & dst, bool overwrite );
+
+ /**
+ * Called by openConnection. It opens the control connection to the ftp server.
+ *
+ * @return true on success.
+ */
+ bool ftpOpenControlConnection( const QString & host, unsigned short int port );
+
+ /**
+ * closes the socket holding the control connection (see ftpOpenControlConnection)
+ */
+ void ftpCloseControlConnection();
+
+ /**
+ * read a response from the server (a trailing CR gets stripped)
+ * @param iOffset -1 to read a new line from the server<br>
+ * 0 to return the whole response string
+ * >0 to return the response with iOffset chars skipped
+ * @return the reponse message with iOffset chars skipped (or "" if iOffset points
+ * behind the available data)
+ */
+ const char* ftpResponse(int iOffset);
+
+ /**
+ * This is the internal implementation of get() - see copy().
+ *
+ * IMPORTANT: the caller should call ftpCloseCommand() on return.
+ * The function does not call error(), the caller should do this.
+ *
+ * @param iError set to an ERR_xxxx code on error
+ * @param iCopyFile -1 -or- handle of a local destination file
+ * @param hCopyOffset local file only: non-zero for resume
+ * @return 0 for success, -1 for server error, -2 for client error
+ */
+ StatusCode ftpGet(int& iError, int iCopyFile, const KURL& url, KIO::fileoffset_t hCopyOffset);
+
+ /**
+ * This is the internal implementation of put() - see copy().
+ *
+ * IMPORTANT: the caller should call ftpCloseCommand() on return.
+ * The function does not call error(), the caller should do this.
+ *
+ * @param iError set to an ERR_xxxx code on error
+ * @param iCopyFile -1 -or- handle of a local source file
+ * @return 0 for success, -1 for server error, -2 for client error
+ */
+ StatusCode ftpPut(int& iError, int iCopyFile, const KURL& url, int permissions, bool overwrite, bool resume);
+
+ /**
+ * helper called from copy() to implement FILE -> FTP transfers
+ *
+ * @param iError set to an ERR_xxxx code on error
+ * @param iCopyFile [out] handle of a local source file
+ * @param sCopyFile path of the local source file
+ * @return 0 for success, -1 for server error, -2 for client error
+ */
+ StatusCode ftpCopyPut(int& iError, int& iCopyFile, QString sCopyFile, const KURL& url, int permissions, bool overwrite);
+
+ /**
+ * helper called from copy() to implement FTP -> FILE transfers
+ *
+ * @param iError set to an ERR_xxxx code on error
+ * @param iCopyFile [out] handle of a local source file
+ * @param sCopyFile path of the local destination file
+ * @return 0 for success, -1 for server error, -2 for client error
+ */
+ StatusCode ftpCopyGet(int& iError, int& iCopyFile, QString sCopyFile, const KURL& url, int permissions, bool overwrite);
+
+private: // data members
+
+ QString m_host;
+ unsigned short int m_port;
+ QString m_user;
+ QString m_pass;
+ /**
+ * Where we end up after connecting
+ */
+ QString m_initialPath;
+ KURL m_proxyURL;
+
+ /**
+ * the current working directory - see ftpFolder
+ */
+ QString m_currentPath;
+
+ /**
+ * the status returned by the FTP protocol, set in ftpResponse()
+ */
+ int m_iRespCode;
+
+ /**
+ * the status/100 returned by the FTP protocol, set in ftpResponse()
+ */
+ int m_iRespType;
+
+ /**
+ * This flag is maintained by ftpDataMode() and contains I or A after
+ * ftpDataMode() has successfully set the mode.
+ */
+ char m_cDataMode;
+
+ /**
+ * true if logged on (m_control should also be non-NULL)
+ */
+ bool m_bLoggedOn;
+
+ /**
+ * true if a "textmode" metadata key was found by ftpLogin(). This
+ * switches the ftp data transfer mode from binary to ASCII.
+ */
+ bool m_bTextMode;
+
+ /**
+ * true if a data stream is open, used in closeConnection().
+ *
+ * When the user cancels a get or put command the Ftp dtor will be called,
+ * which in turn calls closeConnection(). The later would try to send QUIT
+ * which won't work until timeout. ftpOpenCommand sets the m_bBusy flag so
+ * that the sockets will be closed immedeately - the server should be
+ * capable of handling this and return an error code on thru the control
+ * connection. The m_bBusy gets cleared by the ftpCloseCommand() routine.
+ */
+ bool m_bBusy;
+
+ bool m_bPasv;
+ bool m_bUseProxy;
+
+ KIO::filesize_t m_size;
+ static KIO::filesize_t UnknownSize;
+
+ enum
+ {
+ epsvUnknown = 0x01,
+ epsvAllUnknown = 0x02,
+ eprtUnknown = 0x04,
+ epsvAllSent = 0x10,
+ pasvUnknown = 0x20,
+ chmodUnknown = 0x100
+ };
+ int m_extControl;
+
+ /**
+ * control connection socket, only set if openControl() succeeded
+ */
+ FtpSocket *m_control;
+
+ /**
+ * data connection socket
+ */
+ FtpSocket *m_data;
+};
+
+#endif
diff --git a/kioslave/ftp/ftp.protocol b/kioslave/ftp/ftp.protocol
new file mode 100644
index 000000000..3af1e33ec
--- /dev/null
+++ b/kioslave/ftp/ftp.protocol
@@ -0,0 +1,18 @@
+[Protocol]
+exec=kio_ftp
+protocol=ftp
+input=none
+output=filesystem
+copyToFile=true
+copyFromFile=true
+listing=Name,Type,Size,Date,Access,Owner,Group,Link,
+reading=true
+writing=true
+makedir=true
+deleting=true
+moving=true
+ProxiedBy=http
+Icon=ftp
+maxInstances=2
+DocPath=kioslave/ftp.html
+Class=:internet
diff --git a/kioslave/gzip/Makefile.am b/kioslave/gzip/Makefile.am
new file mode 100644
index 000000000..2b290c206
--- /dev/null
+++ b/kioslave/gzip/Makefile.am
@@ -0,0 +1,12 @@
+INCLUDES = -I$(top_srcdir)/kio $(all_includes)
+AM_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+METASOURCES = AUTO
+
+kde_module_LTLIBRARIES = kgzipfilter.la
+
+kgzipfilter_la_SOURCES = kgzipfilter.cpp
+kgzipfilter_la_LIBADD = $(LIB_KIO) $(LIBZ)
+kgzipfilter_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+
+kde_services_DATA = kgzipfilter.desktop
+
diff --git a/kioslave/gzip/kgzipfilter.cpp b/kioslave/gzip/kgzipfilter.cpp
new file mode 100644
index 000000000..21380c6c1
--- /dev/null
+++ b/kioslave/gzip/kgzipfilter.cpp
@@ -0,0 +1,336 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kgzipfilter.h"
+#include <time.h>
+#include <zlib.h>
+#include <kdebug.h>
+#include <klibloader.h>
+
+/* gzip flag byte */
+#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */
+#define HEAD_CRC 0x02 /* bit 1 set: header CRC present */
+#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */
+#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
+#define COMMENT 0x10 /* bit 4 set: file comment present */
+#define RESERVED 0xE0 /* bits 5..7: reserved */
+
+
+// #define DEBUG_GZIP
+
+class KGzipFilterFactory : public KLibFactory
+{
+public:
+ KGzipFilterFactory() : KLibFactory() {}
+ ~KGzipFilterFactory(){}
+ QObject *createObject( QObject *parent, const char *name, const char*className, const QStringList & args )
+ {
+ Q_UNUSED(parent);
+ Q_UNUSED(name);
+ Q_UNUSED(className);
+ Q_UNUSED(args);
+ return new KGzipFilter;
+ }
+};
+
+K_EXPORT_COMPONENT_FACTORY( kgzipfilter, KGzipFilterFactory )
+
+// Not really necessary anymore, now that this is a dynamically-loaded lib.
+class KGzipFilter::KGzipFilterPrivate
+{
+public:
+ z_stream zStream;
+ bool bCompressed;
+};
+
+KGzipFilter::KGzipFilter()
+{
+ d = new KGzipFilterPrivate;
+ d->zStream.zalloc = (alloc_func)0;
+ d->zStream.zfree = (free_func)0;
+ d->zStream.opaque = (voidpf)0;
+}
+
+
+KGzipFilter::~KGzipFilter()
+{
+ delete d;
+}
+
+void KGzipFilter::init( int mode )
+{
+ d->zStream.next_in = Z_NULL;
+ d->zStream.avail_in = 0;
+ if ( mode == IO_ReadOnly )
+ {
+ int result = inflateInit2(&d->zStream, -MAX_WBITS); // windowBits is passed < 0 to suppress zlib header
+ if ( result != Z_OK )
+ kdDebug(7005) << "inflateInit returned " << result << endl;
+ // No idea what to do with result :)
+ } else if ( mode == IO_WriteOnly )
+ {
+ int result = deflateInit2(&d->zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); // same here
+ if ( result != Z_OK )
+ kdDebug(7005) << "deflateInit returned " << result << endl;
+ } else {
+ kdWarning(7005) << "KGzipFilter: Unsupported mode " << mode << ". Only IO_ReadOnly and IO_WriteOnly supported" << endl;
+ }
+ m_mode = mode;
+ d->bCompressed = true;
+ m_headerWritten = false;
+}
+
+void KGzipFilter::terminate()
+{
+ if ( m_mode == IO_ReadOnly )
+ {
+ int result = inflateEnd(&d->zStream);
+ if ( result != Z_OK )
+ kdDebug(7005) << "inflateEnd returned " << result << endl;
+ } else if ( m_mode == IO_WriteOnly )
+ {
+ int result = deflateEnd(&d->zStream);
+ if ( result != Z_OK )
+ kdDebug(7005) << "deflateEnd returned " << result << endl;
+ }
+}
+
+
+void KGzipFilter::reset()
+{
+ if ( m_mode == IO_ReadOnly )
+ {
+ int result = inflateReset(&d->zStream);
+ if ( result != Z_OK )
+ kdDebug(7005) << "inflateReset returned " << result << endl;
+ } else if ( m_mode == IO_WriteOnly ) {
+ int result = deflateReset(&d->zStream);
+ if ( result != Z_OK )
+ kdDebug(7005) << "deflateReset returned " << result << endl;
+ m_headerWritten = false;
+ }
+}
+
+bool KGzipFilter::readHeader()
+{
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << "KGzipFilter::readHeader avail=" << d->zStream.avail_in << endl;
+#endif
+ // Assume not compressed until we successfully decode the header
+ d->bCompressed = false;
+ // Assume the first block of data contains the whole header.
+ // The right way is to build this as a big state machine which
+ // is a pain in the ass.
+ // With 8K-blocks, we don't risk much anyway.
+ Bytef *p = d->zStream.next_in;
+ int i = d->zStream.avail_in;
+ if ((i -= 10) < 0) return false; // Need at least 10 bytes
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << "KGzipFilter::readHeader first byte is " << QString::number(*p,16) << endl;
+#endif
+ if (*p++ != 0x1f) return false; // GZip magic
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << "KGzipFilter::readHeader second byte is " << QString::number(*p,16) << endl;
+#endif
+ if (*p++ != 0x8b) return false;
+ int method = *p++;
+ int flags = *p++;
+ if ((method != Z_DEFLATED) || (flags & RESERVED) != 0) return false;
+ p += 6;
+ if ((flags & EXTRA_FIELD) != 0) // skip extra field
+ {
+ if ((i -= 2) < 0) return false; // Need at least 2 bytes
+ int len = *p++;
+ len += (*p++) << 8;
+ if ((i -= len) < 0) return false; // Need at least len bytes
+ p += len;
+ }
+ if ((flags & ORIG_NAME) != 0) // skip original file name
+ {
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << "ORIG_NAME=" << p << endl;
+#endif
+ while( (i > 0) && (*p))
+ {
+ i--; p++;
+ }
+ if (--i <= 0) return false;
+ p++;
+ }
+ if ((flags & COMMENT) != 0) // skip comment
+ {
+ while( (i > 0) && (*p))
+ {
+ i--; p++;
+ }
+ if (--i <= 0) return false;
+ p++;
+ }
+ if ((flags & HEAD_CRC) != 0) // skip the header crc
+ {
+ if ((i-=2) < 0) return false;
+ p += 2;
+ }
+
+ d->zStream.avail_in = i;
+ d->zStream.next_in = p;
+ d->bCompressed = true;
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << "header OK" << endl;
+#endif
+ return true;
+}
+
+/* Output a 16 bit value, lsb first */
+#define put_short(w) \
+ *p++ = (uchar) ((w) & 0xff); \
+ *p++ = (uchar) ((ushort)(w) >> 8);
+
+/* Output a 32 bit value to the bit stream, lsb first */
+#define put_long(n) \
+ put_short((n) & 0xffff); \
+ put_short(((ulong)(n)) >> 16);
+
+bool KGzipFilter::writeHeader( const QCString & fileName )
+{
+ Bytef *p = d->zStream.next_out;
+ int i = d->zStream.avail_out;
+ *p++ = 0x1f;
+ *p++ = 0x8b;
+ *p++ = Z_DEFLATED;
+ *p++ = ORIG_NAME;
+ put_long( time( 0L ) ); // Modification time (in unix format)
+ *p++ = 0; // Extra flags (2=max compress, 4=fastest compress)
+ *p++ = 3; // Unix
+
+ uint len = fileName.length();
+ for ( uint j = 0 ; j < len ; ++j )
+ *p++ = fileName[j];
+ *p++ = 0;
+ int headerSize = p - d->zStream.next_out;
+ i -= headerSize;
+ Q_ASSERT(i>0);
+ m_crc = crc32(0L, Z_NULL, 0);
+ d->zStream.next_out = p;
+ d->zStream.avail_out = i;
+ m_headerWritten = true;
+ return true;
+}
+
+void KGzipFilter::writeFooter()
+{
+ Q_ASSERT( m_headerWritten );
+ if (!m_headerWritten) kdDebug() << kdBacktrace();
+ Bytef *p = d->zStream.next_out;
+ int i = d->zStream.avail_out;
+ //kdDebug(7005) << "KGzipFilter::writeFooter writing CRC= " << QString::number( m_crc, 16 ) << endl;
+ put_long( m_crc );
+ //kdDebug(7005) << "KGzipFilter::writing writing totalin= " << d->zStream.total_in << endl;
+ put_long( d->zStream.total_in );
+ i -= p - d->zStream.next_out;
+ d->zStream.next_out = p;
+ d->zStream.avail_out = i;
+}
+
+void KGzipFilter::setOutBuffer( char * data, uint maxlen )
+{
+ d->zStream.avail_out = maxlen;
+ d->zStream.next_out = (Bytef *) data;
+}
+void KGzipFilter::setInBuffer( const char * data, uint size )
+{
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << "KGzipFilter::setInBuffer avail_in=" << size << endl;
+#endif
+ d->zStream.avail_in = size;
+ d->zStream.next_in = (Bytef*) data;
+}
+int KGzipFilter::inBufferAvailable() const
+{
+ return d->zStream.avail_in;
+}
+int KGzipFilter::outBufferAvailable() const
+{
+ return d->zStream.avail_out;
+}
+
+KGzipFilter::Result KGzipFilter::uncompress_noop()
+{
+ // I'm not sure we really need support for that (uncompressed streams),
+ // but why not, it can't hurt to have it. One case I can think of is someone
+ // naming a tar file "blah.tar.gz" :-)
+ if ( d->zStream.avail_in > 0 )
+ {
+ int n = (d->zStream.avail_in < d->zStream.avail_out) ? d->zStream.avail_in : d->zStream.avail_out;
+ memcpy( d->zStream.next_out, d->zStream.next_in, n );
+ d->zStream.avail_out -= n;
+ d->zStream.next_in += n;
+ d->zStream.avail_in -= n;
+ return OK;
+ } else
+ return END;
+}
+
+KGzipFilter::Result KGzipFilter::uncompress()
+{
+ Q_ASSERT ( m_mode == IO_ReadOnly );
+ if ( d->bCompressed )
+ {
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << "Calling inflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable() << endl;
+ kdDebug(7005) << " next_in=" << d->zStream.next_in << endl;
+#endif
+ int result = inflate(&d->zStream, Z_SYNC_FLUSH);
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << " -> inflate returned " << result << endl;
+ kdDebug(7005) << "Now avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable() << endl;
+ kdDebug(7005) << " next_in=" << d->zStream.next_in << endl;
+#else
+ if ( result != Z_OK && result != Z_STREAM_END )
+ kdDebug(7005) << "Warning: inflate() returned " << result << endl;
+#endif
+ return ( result == Z_OK ? OK : ( result == Z_STREAM_END ? END : ERROR ) );
+ } else
+ return uncompress_noop();
+}
+
+KGzipFilter::Result KGzipFilter::compress( bool finish )
+{
+ Q_ASSERT ( d->bCompressed );
+ Q_ASSERT ( m_mode == IO_WriteOnly );
+
+ Bytef* p = d->zStream.next_in;
+ ulong len = d->zStream.avail_in;
+#ifdef DEBUG_GZIP
+ kdDebug(7005) << " calling deflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable() << endl;
+#endif
+ int result = deflate(&d->zStream, finish ? Z_FINISH : Z_NO_FLUSH);
+ if ( result != Z_OK && result != Z_STREAM_END )
+ kdDebug(7005) << " deflate returned " << result << endl;
+ if ( m_headerWritten )
+ {
+ //kdDebug(7005) << "Computing CRC for the next " << len - d->zStream.avail_in << " bytes" << endl;
+ m_crc = crc32(m_crc, p, len - d->zStream.avail_in);
+ }
+ if ( result == Z_STREAM_END && m_headerWritten )
+ {
+ //kdDebug(7005) << "KGzipFilter::compress finished, write footer" << endl;
+ writeFooter();
+ }
+ return ( result == Z_OK ? OK : ( result == Z_STREAM_END ? END : ERROR ) );
+}
diff --git a/kioslave/gzip/kgzipfilter.desktop b/kioslave/gzip/kgzipfilter.desktop
new file mode 100644
index 000000000..e9f3c119c
--- /dev/null
+++ b/kioslave/gzip/kgzipfilter.desktop
@@ -0,0 +1,86 @@
+[Desktop Entry]
+Type=Service
+Name=GZip Filter
+Name[af]=Gzip Filter
+Name[ar]=فلتر GZip
+Name[az]=GZip Filtri
+Name[be]=Фільтр GZip
+Name[bg]=Филтър GZip
+Name[bn]=জি-জিপ (Gzip) ফিল্টার
+Name[br]=Sil GZip
+Name[ca]=Filtre GZip
+Name[cs]=Filtr GZip2
+Name[csb]=Filter GZipa
+Name[cy]=Hidl GZip
+Name[da]=GZip-filter
+Name[de]=GZip-Filter
+Name[el]=Φίλτρο GZip
+Name[eo]=GZip-filtrilo
+Name[es]=Filtro GZip
+Name[et]=GZip filter
+Name[eu]=GZip iragazkia
+Name[fa]=پالایۀ GZip
+Name[fi]=GZip-suodin
+Name[fr]=Filtre Gzip
+Name[fy]=GZip-filter
+Name[ga]=Scagaire gzip
+Name[gl]=Filtro GZip
+Name[he]=מסנן GZip
+Name[hi]=GZip फ़िल्टर
+Name[hr]=GZip filtar
+Name[hu]=GZip szűrő
+Name[id]=Filter Gzip
+Name[is]=GZip sía
+Name[it]=Filtro Gzip
+Name[ja]=GZip フィルタ
+Name[ka]=GZip ფილტრი
+Name[kk]=GZip сүзгісі
+Name[km]=តម្រង GZip
+Name[ko]=GZip 거르개
+Name[lb]=GZip-Filter
+Name[lt]=GZip filtras
+Name[lv]=GZip Filtrs
+Name[mk]=GZip филтер
+Name[mn]=GZip-Filter
+Name[ms]=Penapis GZip
+Name[mt]=Filtru GZip
+Name[nb]=GZip-filter
+Name[nds]=GZip-Filter
+Name[ne]=GZip फिल्टर
+Name[nl]=GZip-filter
+Name[nn]=GZip-filter
+Name[nso]=Sesekodi sa GZip
+Name[pa]=GZip ਫਿਲਟਰ
+Name[pl]=Filtr GZipa
+Name[pt]=Filtro GZip
+Name[pt_BR]=Filtro GZip
+Name[ro]=Filtru GZip
+Name[ru]=Фильтр gzip
+Name[rw]=Muyunguruzi GZipu
+Name[se]=GZip-filter
+Name[sk]=GZip filter
+Name[sl]=Filter za gzip
+Name[sq]=Filteri GZip
+Name[sr]=GZip филтер
+Name[sr@Latn]=GZip filter
+Name[ss]=Sisefo se GZip
+Name[sv]=Gzip-filter
+Name[ta]=GZip வடிகட்டி
+Name[te]=జిజిప్ గలని
+Name[tg]=Таровиши GZip
+Name[th]=ตัวกรอง GZip
+Name[tr]=GZip Filtresi
+Name[tt]=GZip Sözgeçe
+Name[uk]=Фільтр GZip
+Name[uz]=GZip-filter
+Name[uz@cyrillic]=GZip-филтер
+Name[ven]=Filithara ya GZip
+Name[vi]=Bộ lọc GZip
+Name[wa]=Passete GZip
+Name[xh]=Isihluzi se GZip
+Name[zh_CN]=GZip 过滤程序
+Name[zh_HK]=GZip 過濾器
+Name[zh_TW]=GZip 過濾器
+Name[zu]=Ihluzo le-GZip
+X-KDE-Library=kgzipfilter
+ServiceTypes=KDECompressionFilter,application/x-gzip,application/x-tgz
diff --git a/kioslave/gzip/kgzipfilter.h b/kioslave/gzip/kgzipfilter.h
new file mode 100644
index 000000000..c103d774d
--- /dev/null
+++ b/kioslave/gzip/kgzipfilter.h
@@ -0,0 +1,52 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef __kgzipfilter__h
+#define __kgzipfilter__h
+
+#include "kfilterbase.h"
+
+class KGzipFilter : public KFilterBase
+{
+public:
+ KGzipFilter();
+ virtual ~KGzipFilter();
+
+ virtual void init( int mode );
+ virtual int mode() const { return m_mode; }
+ virtual void terminate();
+ virtual void reset();
+ virtual bool readHeader();
+ virtual bool writeHeader( const QCString & fileName );
+ void writeFooter();
+ virtual void setOutBuffer( char * data, uint maxlen );
+ virtual void setInBuffer( const char * data, uint size );
+ virtual int inBufferAvailable() const;
+ virtual int outBufferAvailable() const;
+ virtual Result uncompress();
+ virtual Result compress( bool finish );
+private:
+ Result uncompress_noop();
+ int m_mode;
+ ulong m_crc;
+ bool m_headerWritten;
+ class KGzipFilterPrivate;
+ KGzipFilterPrivate *d;
+};
+
+#endif
diff --git a/kioslave/http/Makefile.am b/kioslave/http/Makefile.am
new file mode 100644
index 000000000..a29e06e9e
--- /dev/null
+++ b/kioslave/http/Makefile.am
@@ -0,0 +1,31 @@
+# $Id$
+# Makefile.am of kdebase/kioslave/http
+
+SUBDIRS = kcookiejar
+
+INCLUDES= -I$(top_srcdir)/interfaces -I$(top_srcdir)/kio/httpfilter -I$(top_srcdir)/kdecore/network $(all_includes) $(GSSAPI_INCS)
+AM_LDFLAGS = $(all_libraries) $(GSSAPI_RPATH)
+
+####### Files
+
+bin_PROGRAMS=
+lib_LTLIBRARIES=
+kdeinit_LTLIBRARIES = kio_http_cache_cleaner.la
+kde_module_LTLIBRARIES = kio_http.la
+
+kio_http_la_SOURCES = http.cc
+kio_http_la_METASOURCES = AUTO
+kio_http_la_LIBADD = $(LIB_KIO) $(top_builddir)/kio/httpfilter/libhttpfilter.la $(top_builddir)/kio/misc/kntlm/libkntlm.la
+kio_http_la_LDFLAGS = $(all_libraries) $(GSSAPI_RPATH) -module $(KDE_PLUGIN) $(GSSAPI_LIBS)
+
+kio_http_cache_cleaner_la_SOURCES = http_cache_cleaner.cpp
+kio_http_cache_cleaner_la_LIBADD = $(LIB_KIO)
+kio_http_cache_cleaner_la_LDFLAGS = -module -avoid-version
+
+noinst_HEADERS = http.h
+
+kdelnkdir = $(kde_servicesdir)
+kdelnk_DATA = http_cache_cleaner.desktop http.protocol https.protocol \
+ webdav.protocol webdavs.protocol
+
+include $(top_srcdir)/admin/Doxyfile.am
diff --git a/kioslave/http/README.http_cache_cleaner b/kioslave/http/README.http_cache_cleaner
new file mode 100644
index 000000000..7714bfba6
--- /dev/null
+++ b/kioslave/http/README.http_cache_cleaner
@@ -0,0 +1,20 @@
+khttpcache README
+=================
+
+khttpcache checks the HTTP Cache of a user
+and throws out expired entries.
+
+TODO:
+
+* Skip entries which end in .new and are younger than
+30 minutes / delte entries which end in .new and are
+older than 30 minutes.
+
+* Let kio_http fill in expire dates other than 0.
+
+DONE:
+
+* Start khttpcache from kio_http if the file "cleaned"
+is older than 30(?) minutes.
+
+* Accept command line parameteres
diff --git a/kioslave/http/README.webdav b/kioslave/http/README.webdav
new file mode 100644
index 000000000..c7ee900bb
--- /dev/null
+++ b/kioslave/http/README.webdav
@@ -0,0 +1,184 @@
+This document describes how to add support for extended webdav features (locking,
+properties etc.) to your webdav-aware application.
+Author: Hamish Rodda, rodda@kde.org
+Version: 0.3
+
+Compatable with (tested on):
+Apache + mod_dav version 1 and 2
+Zope
+Silverstream webdav server
+
+Applications supporting extended webdav features
+ (include name and contact email, in case the interface has to change):
+[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.
+
+Extended information is transferred via kio's metadata system...
+
+=== MISCELLANEOUS ===
+Display Names (names suitable for presentation to the user) are passed as the metadata
+element davDisplayName.
+
+Source template locations (href, usually an absolute URL w/o host info)
+are passed as element davSource.
+
+Content languages are passed as element davContentLanguage.
+
+Extra webdav headers are passed as metadata element davHeader
+
+For doing a webdav SEARCH, use listDir() and set the metadata element
+davSearchQuery to the search query. The root element of this query should be like
+<d:basicsearch> or <d:sql>.
+
+For doing a generic webdav action, call a special request, with
+the following data:
+int, value 7 (WEBDAV generic)
+KURL url
+int method - the HTTP/WEBDAV method to call
+Send the xml request and receive the xml response in the usual way.
+
+=== CREATING A LOCK ===
+To create a lock, call a special request, with the following data:
+
+int, value 5 (LOCK request)
+KURL url - the location of the resource to lock
+QString scope - the scope of the lock, currently "exclusive" or "shared"
+QString type - the type of the lock, currently only "write"
+QString owner (optional) - owner contact details (url)
+
+Additionally, the lock timeout requested from the server may be altered from the default
+of Infinity by setting the metadata "davTimeout" to the number of seconds, or 0 for
+infinity.
+
+=== REMOVING A LOCK ===
+To remove a lock, call a special request, with the following data:
+
+int, value 5 (LOCK request)
+KURL url - the location of the resource to unlock
+
+metadata required:
+davLockToken - the lock token to remove
+
+and, of course, any other lock information as below required for the operation
+to suceed.
+
+=== SETTING LOCK INFORMATION ===
+To provide lock data so that urls can be accessed, you need to pass the following metadata:
+davLockCount: (uint) the number of locks you are providing
+davLockToken%1: (string) the token
+(optional) davLockURL%1: (string) the absolute URL specified by the lock token
+(optional) davLockNot%1: (value ignored) the presence of this meta key negates the lock
+ (ie. requires the lock to not be set)
+
+Example data:
+=============
+davLockCount: 2
+davLockToken1: opaquelocktoken:f81de2ad-7f3d-a1b2-4f3c-00a0c91a9d76A
+davLockNot1: (value ignored)
+davLockToken2: opaquelocktoken:f81de2ad-7f3d-a1b2-4f3c-00a0c91a9d76B
+davLockURL2: http://www.foo.bar/container2/
+
+
+=== RECEIVING LOCK INFORMATION ===
+For each file, stat/listdir always returns two pieces of information:
+
+davSupportedLockCount: (uint) the number of lock types discovered for this resource.
+davLockCount: (uint) the number of locks discovered on this resource.
+
+for each count, additional information is returned:
+
+===================
+Information about the locks on a resource:
+
+davLockCount: %1 (the number of locks to be described, as below)
+*** Required items ***
+davLockScope%1 - The scope of this lock. May be exclusive, shared, or a custom type.
+davLockType%1 - The type of the lock.
+davLockDepth%1 - The depth to which this lock applies
+ (0=only this resource, 1=this collection, infinity=applies recursively)
+
+*** Optional items ***
+davLockOwner%1 - The owner of this lock.
+davLockTimeout%1 - The timeout parameter. Possibilities: see section 9.8, rfc #2518
+davLockToken%1 - The token which iden
+
+===================
+Information about the lock types supported by the resource
+
+davSupportedLockCount: %1 (the number of locks types to be described, as below)
+
+davSupportedLockScope%1 - The scope of the lock (exclusive, shared, other custom type)
+davSupportedLockType%1 - The type of the lock (webdav 1.0 supports only the "write" type)
+===================
+
+Example Metadata which would be supplied if the response was the example XML below:
+
+davSupportedLockCount: 2
+davLockCount: 2
+davLockScope1: exclusive
+davLockType1: write
+davLockDepth1: 0
+davLockOwner1: Jane Smith
+davLockTimeout1: infinite
+davLockToken1: opaquelocktoken:f81de2ad-7f3d-a1b2-4f3c-00a0c91a9d76A
+davLockScope2: shared
+davLockType2: write
+davLockDepth2: 1
+davLockOwner2: John Doe
+davLockToken2: opaquelocktoken:f81de2ad-7f3d-a1b2-4f3c-00a0c91a9d76B
+davSupportedLockScope1: exclusive
+davSupportedLockType1: write
+davSupportedLockScope2: shared
+davSupportedLockType2: write
+
+
+(example XML:)
+
+ <?xml version="1.0" encoding="utf-8" ?>
+ <D:multistatus xmlns:D='DAV:'>
+ <D:response>
+ <D:href>http://www.foo.bar/container/</D:href>
+ <D:propstat>
+ <D:prop>
+ <D:lockdiscovery>
+ <D:activelock>
+ <D:locktype><D:write/></D:locktype>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:depth>0</D:depth>
+ <D:owner>Jane Smith</D:owner>
+ <D:timeout>Infinite</D:timeout>
+ <D:locktoken>
+ <D:href>
+ opaquelocktoken:f81de2ad-7f3d-a1b2-4f3c-00a0c91a9d76A
+ </D:href>
+ </D:locktoken>
+ </D:activelock>
+ <D:activelock>
+ <D:locktype><D:write/></D:locktype>
+ <D:lockscope><D:shared/></D:lockscope>
+ <D:depth>1</D:depth>
+ <D:owner>John Doe</D:owner>
+ <D:locktoken>
+ <D:href>
+ opaquelocktoken:f81de2ad-7f3d-a1b2-4f3c-00a0c91a9d76B
+ </D:href>
+ </D:locktoken>
+ </D:activelock>
+ </D:lockdiscovery>
+ <D:supportedlock>
+ <D:lockentry>
+ <D:lockscope><D:exclusive/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ </D:lockentry>
+ <D:lockentry>
+ <D:lockscope><D:shared/></D:lockscope>
+ <D:locktype><D:write/></D:locktype>
+ </D:lockentry>
+ </D:supportedlock>
+ </D:prop>
+ <D:status>HTTP/1.1 200 OK</D:status>
+ </D:propstat>
+ </D:response>
+ </D:multistatus>
diff --git a/kioslave/http/THOUGHTS b/kioslave/http/THOUGHTS
new file mode 100644
index 000000000..9715b5c2f
--- /dev/null
+++ b/kioslave/http/THOUGHTS
@@ -0,0 +1,28 @@
+Here's a few ideas for those with blistered hands and nothing better to
+do:
+
+SSL certificate verification:
+We do establish SSL connections, but we never actually verify a
+certificate!
+
+HTTP/1.1 Persistant Connections:
+The header often specifies the timeout value used for connections.
+Close the connection ourselves when the timeout has expired. That way
+we don't loose time sending stuff to an already closed connection.
+
+Rating(s) support. http://www.w3.org/PICS
+This might involve an external program to parse the labels, and something
+to configure access.
+
+WebDAV support. MSIE5 calls it web folders support, and a similar
+approach would probably be a good idea. Perhaps with an exists()
+function.. one could tell if an http url was part of a WebDAV collection..
+and this could be used for some kind of integration with kfile... to
+provide seamless integration. Uhm, also, this might entail an external
+program (xml parser and such).
+
+"Friendly" error messages. How often have you seen a useless 404 message?
+Again something I first notied in MSIE5, and that would be some sort of
+translation of what an error really means. Yes this would have to be
+i18n'd and easily turned off. But this could also be extended to all the
+slaves (ftp, pop3, etc, etc).
diff --git a/kioslave/http/TODO b/kioslave/http/TODO
new file mode 100644
index 000000000..6484f0284
--- /dev/null
+++ b/kioslave/http/TODO
@@ -0,0 +1,45 @@
+The following is a list of items that are currently missing or partially implemented
+in kio_http:
+
+- HTTP/1.1 Persistant Connections:
+The header often specifies the timeout value used for connections.
+Close the connection ourselves when the timeout has expired. That way
+we don't loose time sending stuff to an already closed connection.
+
+- HTTP/1.1 Pipelining support
+This more of an optimization of the http io-slave that is intended to make it
+faster while using as few resources as possible. Work on this is currently
+being done to add this support for KDE 3.x version.
+
+- WebDAV support:
+The majority of the work for this is done, see README.webdav. GUI integration
+into konqueror as a konqueror part would be nice, to add GUI support for
+features such as locking.
+
+- Rating(s) support. http://www.w3.org/PICS:
+This might involve an external program to parse the labels, and something to
+configure access accordingly. There is only some basic things that need to be
+added to kio_http to support this. The majority of the work has to be done at the
+application level. A khtml plugin in kdeaddons to do this might be a nice idea.
+
+- P3P support:
+This can also be implemented as a plugin to konqueror and does
+not need any speical support in HTTP except perhaps sending a
+flag that indicates that the web page provides some P3P information.
+This is something that can be added as a plugin to kdeaddons.
+
+
+Things that do not require programming
+============================
+
+- "Friendly" error message html page.
+We currently support the sending of error messages, but this is only done if the server
+sends back nicely formatted error messages. We do not have fall back HTML pages that
+describe these error messages in a non-technical manner! This of course also means that
+we will certainly need to have these files translated.
+
+
+Maintainers
+Waldo Bastian <bastian@kde.org>
+Dawit Alemayehu <adawit@kde.org>
+WebDAV support: Hamish Rodda <rodda@kde.org>
diff --git a/kioslave/http/configure.in.bot b/kioslave/http/configure.in.bot
new file mode 100644
index 000000000..56d051424
--- /dev/null
+++ b/kioslave/http/configure.in.bot
@@ -0,0 +1,10 @@
+dnl put here things which have to be done as very last part of configure
+
+if test "x$with_gssapi" = xNOTFOUND; then
+ echo ""
+ echo "You're missing GSSAPI/Kerberos."
+ echo "KDE can use GSSAPI/Kerberos to authenticate on certain secure websites."
+ echo "GSSAPI/Kerberos authentication is typically used on intranets."
+ echo ""
+ all_tests=bad
+fi
diff --git a/kioslave/http/configure.in.in b/kioslave/http/configure.in.in
new file mode 100644
index 000000000..14f79ddc6
--- /dev/null
+++ b/kioslave/http/configure.in.in
@@ -0,0 +1,110 @@
+AC_MSG_CHECKING(whether to enable GSSAPI support)
+AC_ARG_WITH(gssapi,
+[ --with-gssapi=PATH Set path for GSSAPI files [default=check]],
+[ case "$withval" in
+ yes)
+ with_gssapi=CHECK
+ ;;
+ esac ],
+[ with_gssapi=CHECK ]
+)dnl
+
+if test "x$with_gssapi" = "xCHECK" ; then
+ with_gssapi=NOTFOUND
+ KDE_FIND_PATH(krb5-config, KRB5_CONFIG, [${prefix}/bin ${exec_prefix}/bin /usr/bin /usr/local/bin /opt/local/bin /usr/lib/mit/bin], [
+ AC_MSG_WARN([Could not find krb5-config])
+ ])
+
+ if test -n "$KRB5_CONFIG"; then
+ kde_save_cflags="$CFLAGS"
+ unset CFLAGS
+ GSSAPI_INCS="`$KRB5_CONFIG --cflags gssapi`"
+ GSSAPI_LIBS="`$KRB5_CONFIG --libs gssapi`"
+ CFLAGS="$kde_save_cflags"
+ if test "$USE_RPATH" = yes; then
+ for args in $GSSAPI_LIBS; do
+ case $args in
+ -L/usr/lib) ;;
+ -L*)
+ GSSAPI_RPATH="$GSSAPI_RPATH $args"
+ ;;
+ esac
+ done
+ GSSAPI_RPATH=`echo $GSSAPI_RPATH | sed -e "s/-L/-R/g"`
+ fi
+ gssapi_incdir="$GSSAPI_INCS"
+ gssapi_libdir="$GSSAPI_LIBS"
+ with_gssapi=FOUND
+ if $KRB5_CONFIG --vendor | grep "Massachusetts" > /dev/null; then
+ gssapi_flavor=MIT
+ else
+ gssapi_flavor=HEIMDAL
+ fi
+ else
+ search_incs="$kde_includes /usr/include /usr/local/include"
+ AC_FIND_FILE(gssapi.h, $search_incs, gssapi_incdir)
+ if test -r $gssapi_incdir/gssapi.h ; then
+ test "x$gssapi_incdir" != "x/usr/include" && GSSAPI_INCS="-I$gssapi_incdir"
+ with_gssapi=FOUND
+ fi
+ if test $with_gssapi = FOUND ; then
+ with_gssapi=NOTFOUND
+ for ext in la so sl a dylib ; do
+ AC_FIND_FILE(libgssapi.$ext, $kde_libraries /usr/lib /usr/local/lib,
+ gssapi_libdir)
+ if test -r $gssapi_libdir/libgssapi.$ext ; then
+ if test "x$gssapi_libdir" != "x/usr/lib" ; then
+ GSSAPI_LIBS="-L$gssapi_libdir "
+ test "$USE_RPATH" = yes && GSSAPI_RPATH="-R $gssapi_libdir"
+ fi
+ GSSAPI_LIBS="${GSSAPI_LIBS}-lgssapi -lkrb5 -lasn1 -lcrypto -lroken -lcrypt ${LIBRESOLV}"
+ with_gssapi=FOUND
+ gssapi_flavor=HEIMDAL
+ break
+ fi
+ done
+ fi
+ fi
+fi
+
+case "$with_gssapi" in
+no) AC_MSG_RESULT(no) ;;
+framework)
+ GSSAPI_LIBS="-Xlinker -framework -Xlinker Kerberos"
+ AC_DEFINE_UNQUOTED(HAVE_LIBGSSAPI, 1, [Define if you have GSSAPI libraries])
+ GSSAPI_SUBDIR="gssapi"
+ AC_MSG_RESULT(Apple framework)
+ ;;
+NOTFOUND) AC_MSG_RESULT(searched but not found) ;;
+*)
+ if test "x$with_gssapi" = "xFOUND" ; then
+ msg="incs=$gssapi_incdir libs=$gssapi_libdir"
+ else
+ msg="$with_gssapi"
+ GSSAPI_ROOT="$with_gssapi"
+ if test "x$GSSAPI_ROOT" != "x/usr" ; then
+ GSSAPI_INCS="-I${GSSAPI_ROOT}/include"
+ GSSAPI_LIBS="-L${GSSAPI_ROOT}/lib "
+ if test "$USE_RPATH" = "yes" ; then
+ GSSAPI_RPATH="-R ${GSSAPI_ROOT}/lib"
+ fi
+ fi
+ if test -f ${GSSAPI_ROOT}/include/gssapi/gssapi.h ; then
+ gssapi_flavor=MIT
+ GSSAPI_LIBS="${GSSAPI_LIBS}-lgssapi_krb5 -lkrb5 -lk5crypto -lcom_err ${LIBRESOLV}"
+ else
+ gssapi_flavor=HEIMDAL
+ GSSAPI_LIBS="${GSSAPI_LIBS}-lgssapi -lkrb5 -lasn1 -lcrypto -lroken -lcrypt ${LIBRESOLV}"
+ fi
+ fi
+ if test "x$gssapi_flavor" = "xMIT" ; then
+ AC_DEFINE_UNQUOTED(GSSAPI_MIT, 1, [Define if you have the MIT Kerberos libraries])
+ fi
+ AC_DEFINE_UNQUOTED(HAVE_LIBGSSAPI, 1, [Define if you have GSSAPI libraries])
+ AC_MSG_RESULT($msg)
+ ;;
+esac
+
+AC_SUBST(GSSAPI_INCS)
+AC_SUBST(GSSAPI_LIBS)
+AC_SUBST(GSSAPI_RPATH)
diff --git a/kioslave/http/http.cc b/kioslave/http/http.cc
new file mode 100644
index 000000000..5d9fa2eb7
--- /dev/null
+++ b/kioslave/http/http.cc
@@ -0,0 +1,6095 @@
+/*
+ Copyright (C) 2000-2003 Waldo Bastian <bastian@kde.org>
+ Copyright (C) 2000-2002 George Staikos <staikos@kde.org>
+ Copyright (C) 2000-2002 Dawit Alemayehu <adawit@kde.org>
+ Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org>
+
+ 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 <config.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <utime.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h> // Required for AIX
+#include <netinet/tcp.h>
+#include <unistd.h> // must be explicitly included for MacOSX
+
+/*
+#include <netdb.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+*/
+
+#include <qdom.h>
+#include <qfile.h>
+#include <qregexp.h>
+#include <qdatetime.h>
+#include <qstringlist.h>
+
+#include <kurl.h>
+#include <kidna.h>
+#include <ksocks.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kextsock.h>
+#include <kservice.h>
+#include <krfcdate.h>
+#include <kmdcodec.h>
+#include <kinstance.h>
+#include <kresolver.h>
+#include <kmimemagic.h>
+#include <dcopclient.h>
+#include <kdatastream.h>
+#include <kapplication.h>
+#include <kstandarddirs.h>
+#include <kstringhandler.h>
+#include <kremoteencoding.h>
+
+#include "kio/ioslave_defaults.h"
+#include "kio/http_slave_defaults.h"
+
+#include "httpfilter.h"
+#include "http.h"
+
+#ifdef HAVE_LIBGSSAPI
+#ifdef GSSAPI_MIT
+#include <gssapi/gssapi.h>
+#else
+#include <gssapi.h>
+#endif /* GSSAPI_MIT */
+
+// Catch uncompatible crap (BR86019)
+#if defined(GSS_RFC_COMPLIANT_OIDS) && (GSS_RFC_COMPLIANT_OIDS == 0)
+#include <gssapi/gssapi_generic.h>
+#define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
+#endif
+
+#endif /* HAVE_LIBGSSAPI */
+
+#include <misc/kntlm/kntlm.h>
+
+using namespace KIO;
+
+extern "C" {
+ KDE_EXPORT int kdemain(int argc, char **argv);
+}
+
+int kdemain( int argc, char **argv )
+{
+ KLocale::setMainCatalogue("kdelibs");
+ KInstance instance( "kio_http" );
+ ( void ) KGlobal::locale();
+
+ if (argc != 4)
+ {
+ fprintf(stderr, "Usage: kio_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 QString& fqdn, const QString& originURL )
+{
+ if (originURL == "true") // Backwards compatibility
+ return true;
+
+ KURL url ( originURL );
+
+ // Document Origin domain
+ QString a = url.host();
+
+ // Current request domain
+ QString b = fqdn;
+
+ if (a == b)
+ return false;
+
+ QStringList l1 = QStringList::split('.', a);
+ QStringList l2 = QStringList::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 QString sanitizeCustomHTTPHeader(const QString& _header)
+{
+ QString sanitizedHeaders;
+ QStringList headers = QStringList::split(QRegExp("[\r\n]"), _header);
+
+ for(QStringList::Iterator it = headers.begin(); it != headers.end(); ++it)
+ {
+ QString 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();
+}
+
+
+#define NO_SIZE ((KIO::filesize_t) -1)
+
+#ifdef HAVE_STRTOLL
+#define STRTOLL strtoll
+#else
+#define STRTOLL strtol
+#endif
+
+
+/************************************** HTTPProtocol **********************************************/
+
+HTTPProtocol::HTTPProtocol( const QCString &protocol, const QCString &pool,
+ const QCString &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 = QString::null;
+ m_strProxyAuthorization = QString::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 = QString::null;
+ m_strMimeType = QString::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.url() <<
+ " 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 = QString::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
+ QString protocol = referrerURL.protocol();
+ if (protocol.startsWith("webdav"))
+ {
+ protocol.replace(0, 6, "http");
+ referrerURL.setProtocol(protocol);
+ }
+
+ if (protocol.startsWith("http"))
+ {
+ referrerURL.setRef(QString::null);
+ referrerURL.setUser(QString::null);
+ referrerURL.setPass(QString::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 = QString::null;
+ m_request.languages = QString::null;
+ }
+
+ // Adjust the offset value based on the "resume" meta-data.
+ QString 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 = QString::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( QString::null);
+ }
+
+ m_responseCode = 0;
+ m_prevResponseCode = 0;
+
+ m_strRealm = QString::null;
+ m_strAuthorization = QString::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 QString& host, int port,
+ const QString& user, const QString& 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 << ")" <<endl;
+}
+
+bool HTTPProtocol::checkRequestURL( const KURL& u )
+{
+ kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::checkRequestURL: " << u.url() << endl;
+
+ m_request.url = u;
+
+ if (m_request.hostname.isEmpty())
+ {
+ error( KIO::ERR_UNKNOWN_HOST, i18n("No host specified."));
+ return false;
+ }
+
+ if (u.path().isEmpty())
+ {
+ KURL newUrl(u);
+ newUrl.setPath("/");
+ redirection(newUrl);
+ finished();
+ return false;
+ }
+
+ if ( m_protocol != u.protocol().latin1() )
+ {
+ short unsigned int oldDefaultPort = m_iDefaultPort;
+ m_protocol = u.protocol().latin1();
+ reparseConfiguration();
+ if ( m_iDefaultPort != oldDefaultPort &&
+ m_request.port == oldDefaultPort )
+ m_request.port = m_iDefaultPort;
+ }
+
+ resetSessionSettings();
+ return true;
+}
+
+void HTTPProtocol::retrieveContent( bool dataInternal /* = false */ )
+{
+ kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveContent " << endl;
+ if ( !retrieveHeader( false ) )
+ {
+ if ( m_bError )
+ return;
+ }
+ else
+ {
+ if ( !readBody( dataInternal ) && m_bError )
+ return;
+ }
+
+ httpClose(m_bKeepAlive);
+
+ // if data is required internally, don't finish,
+ // it is processed before we finish()
+ if ( !dataInternal )
+ {
+ if ((m_responseCode == 204) &&
+ ((m_request.method == HTTP_GET) || (m_request.method == HTTP_POST)))
+ error(ERR_NO_CONTENT, "");
+ else
+ finished();
+ }
+}
+
+bool HTTPProtocol::retrieveHeader( bool close_connection )
+{
+ kdDebug (7113) << "(" << m_pid << ") HTTPProtocol::retrieveHeader " << endl;
+ while ( 1 )
+ {
+ if (!httpOpen())
+ return false;
+
+ resetResponseSettings();
+ if (!readHeader())
+ {
+ if ( m_bError )
+ return false;
+
+ if (m_bIsTunneled)
+ {
+ kdDebug(7113) << "(" << m_pid << ") Re-establishing SSL tunnel..." << endl;
+ httpCloseConnection();
+ }
+ }
+ else
+ {
+ // Do not save authorization if the current response code is
+ // 4xx (client error) or 5xx (server error).
+ kdDebug(7113) << "(" << m_pid << ") Previous Response: "
+ << m_prevResponseCode << endl;
+ kdDebug(7113) << "(" << m_pid << ") Current Response: "
+ << m_responseCode << endl;
+
+ if (isSSLTunnelEnabled() && m_bIsSSL && !m_bUnauthorized && !m_bError)
+ {
+ // If there is no error, disable tunneling
+ if ( m_responseCode < 400 )
+ {
+ kdDebug(7113) << "(" << m_pid << ") Unset tunneling flag!" << endl;
+ setEnableSSLTunnel( false );
+ m_bIsTunneled = true;
+ // Reset the CONNECT response code...
+ m_responseCode = m_prevResponseCode;
+ continue;
+ }
+ else
+ {
+ if ( !m_request.bErrorPage )
+ {
+ kdDebug(7113) << "(" << m_pid << ") Sending an error message!" << endl;
+ error( ERR_UNKNOWN_PROXY_HOST, m_proxyURL.host() );
+ return false;
+ }
+
+ kdDebug(7113) << "(" << m_pid << ") Sending an error page!" << endl;
+ }
+ }
+
+ if (m_responseCode < 400 && (m_prevResponseCode == 401 ||
+ m_prevResponseCode == 407))
+ saveAuthorization();
+ break;
+ }
+ }
+
+ // Clear of the temporary POST buffer if it is not empty...
+ if (!m_bufPOST.isEmpty())
+ {
+ m_bufPOST.resize(0);
+ kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
+ "buffer..." << endl;
+ }
+
+ if ( close_connection )
+ {
+ httpClose(m_bKeepAlive);
+ finished();
+ }
+
+ return true;
+}
+
+void HTTPProtocol::stat(const KURL& url)
+{
+ kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::stat " << url.prettyURL()
+ << endl;
+
+ if ( !checkRequestURL( url ) )
+ return;
+
+ if ( m_protocol != "webdav" && m_protocol != "webdavs" )
+ {
+ QString statSide = metaData(QString::fromLatin1("statSide"));
+ if ( statSide != "source" )
+ {
+ // When uploading we assume the file doesn't exit
+ error( ERR_DOES_NOT_EXIST, url.prettyURL() );
+ return;
+ }
+
+ // When downloading we assume it exists
+ UDSEntry entry;
+ UDSAtom atom;
+ atom.m_uds = KIO::UDS_NAME;
+ atom.m_str = url.fileName();
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_FILE_TYPE;
+ atom.m_long = S_IFREG; // a file
+ entry.append( atom );
+
+ atom.m_uds = KIO::UDS_ACCESS;
+ atom.m_long = S_IRUSR | S_IRGRP | S_IROTH; // readable by everybody
+ entry.append( atom );
+
+ statEntry( entry );
+ finished();
+ return;
+ }
+
+ davStatList( url );
+}
+
+void HTTPProtocol::listDir( const KURL& url )
+{
+ kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::listDir " << url.url()
+ << endl;
+
+ if ( !checkRequestURL( url ) )
+ return;
+
+ if (!url.protocol().startsWith("webdav")) {
+ error(ERR_UNSUPPORTED_ACTION, url.prettyURL());
+ return;
+ }
+
+ davStatList( url, false );
+}
+
+void HTTPProtocol::davSetRequest( const QCString& requestXML )
+{
+ // insert the document into the POST buffer, kill trailing zero byte
+ m_bufPOST = requestXML;
+
+ if (m_bufPOST.size())
+ m_bufPOST.truncate( m_bufPOST.size() - 1 );
+}
+
+void HTTPProtocol::davStatList( const KURL& url, bool stat )
+{
+ UDSEntry entry;
+ UDSAtom atom;
+
+ // check to make sure this host supports WebDAV
+ if ( !davHostOk() )
+ return;
+
+ // Maybe it's a disguised SEARCH...
+ QString query = metaData("davSearchQuery");
+ if ( !query.isEmpty() )
+ {
+ QCString request = "<?xml version=\"1.0\"?>\r\n";
+ request.append( "<D:searchrequest xmlns:D=\"DAV:\">\r\n" );
+ request.append( query.utf8() );
+ request.append( "</D:searchrequest>\r\n" );
+
+ davSetRequest( request );
+ } else {
+ // We are only after certain features...
+ QCString request;
+ request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+ "<D:propfind xmlns:D=\"DAV:\">";
+
+ // insert additional XML request from the davRequestResponse metadata
+ if ( hasMetaData( "davRequestResponse" ) )
+ request += metaData( "davRequestResponse" ).utf8();
+ else {
+ // No special request, ask for default properties
+ request += "<D:prop>"
+ "<D:creationdate/>"
+ "<D:getcontentlength/>"
+ "<D:displayname/>"
+ "<D:source/>"
+ "<D:getcontentlanguage/>"
+ "<D:getcontenttype/>"
+ "<D:executable/>"
+ "<D:getlastmodified/>"
+ "<D:getetag/>"
+ "<D:supportedlock/>"
+ "<D:lockdiscovery/>"
+ "<D:resourcetype/>"
+ "</D:prop>";
+ }
+ request += "</D:propfind>";
+
+ davSetRequest( request );
+ }
+
+ // WebDAV Stat or List...
+ m_request.method = query.isEmpty() ? DAV_PROPFIND : DAV_SEARCH;
+ m_request.query = QString::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;
+ }
+
+ QDomDocument multiResponse;
+ multiResponse.setContent( m_bufWebDavData, true );
+
+ bool hasResponse = false;
+
+ for ( QDomNode n = multiResponse.documentElement().firstChild();
+ !n.isNull(); n = n.nextSibling())
+ {
+ QDomElement thisResponse = n.toElement();
+ if (thisResponse.isNull())
+ continue;
+
+ hasResponse = true;
+
+ QDomElement href = thisResponse.namedItem( "href" ).toElement();
+ if ( !href.isNull() )
+ {
+ entry.clear();
+
+ QString urlStr = href.text();
+ 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
+
+ KURL thisURL ( urlStr, encoding );
+
+ atom.m_uds = KIO::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 );
+
+ QDomNodeList 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, KIO::HTTP_METHOD method )
+{
+ kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::davGeneric " << url.url()
+ << 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 = QString::null;
+ m_request.cache = CC_Reload;
+ m_request.doProxy = m_bUseProxy;
+
+ retrieveContent( false );
+}
+
+int HTTPProtocol::codeFromResponse( const QString& response )
+{
+ int firstSpace = response.find( ' ' );
+ int secondSpace = response.find( ' ', firstSpace + 1 );
+ return response.mid( firstSpace + 1, secondSpace - firstSpace - 1 ).toInt();
+}
+
+void HTTPProtocol::davParsePropstats( const QDomNodeList& propstats, UDSEntry& entry )
+{
+ QString mimeType;
+ UDSAtom atom;
+ bool foundExecutable = false;
+ bool isDirectory = false;
+ uint lockCount = 0;
+ uint supportedLockCount = 0;
+
+ for ( uint i = 0; i < propstats.count(); i++)
+ {
+ QDomElement propstat = propstats.item(i).toElement();
+
+ QDomElement 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;
+ }
+
+ QDomElement 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 = KIO::UDS_XML_PROPERTIES;
+ QDomDocument doc;
+ doc.appendChild(prop);
+ atom.m_str = doc.toString();
+ entry.append( atom );
+ }
+
+ for ( QDomNode n = prop.firstChild(); !n.isNull(); n = n.nextSibling() )
+ {
+ QDomElement 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 = KIO::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 = KIO::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
+ QDomElement 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 = KIO::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 ( QDomNode n2 = property.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
+ {
+ QDomElement lockEntry = n2.toElement();
+ if ( lockEntry.tagName() == "lockentry" )
+ {
+ QDomElement lockScope = lockEntry.namedItem( "lockscope" ).toElement();
+ QDomElement lockType = lockEntry.namedItem( "locktype" ).toElement();
+ if ( !lockScope.isNull() && !lockType.isNull() )
+ {
+ // Lock type was properly specified
+ supportedLockCount++;
+ QString scope = lockScope.firstChild().toElement().tagName();
+ QString type = lockType.firstChild().toElement().tagName();
+
+ setMetaData( QString("davSupportedLockScope%1").arg(supportedLockCount), scope );
+ setMetaData( QString("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", QString("%1").arg(lockCount) );
+ setMetaData( "davSupportedLockCount", QString("%1").arg(supportedLockCount) );
+
+ atom.m_uds = KIO::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 = KIO::UDS_ACCESS;
+ atom.m_long = 0700;
+ entry.append(atom);
+ }
+ else
+ {
+ atom.m_uds = KIO::UDS_ACCESS;
+ atom.m_long = 0600;
+ entry.append(atom);
+ }
+
+ if ( !isDirectory && !mimeType.isEmpty() )
+ {
+ atom.m_uds = KIO::UDS_MIME_TYPE;
+ atom.m_str = mimeType;
+ entry.append( atom );
+ }
+}
+
+void HTTPProtocol::davParseActiveLocks( const QDomNodeList& activeLocks,
+ uint& lockCount )
+{
+ for ( uint i = 0; i < activeLocks.count(); i++ )
+ {
+ QDomElement activeLock = activeLocks.item(i).toElement();
+
+ lockCount++;
+ // required
+ QDomElement lockScope = activeLock.namedItem( "lockscope" ).toElement();
+ QDomElement lockType = activeLock.namedItem( "locktype" ).toElement();
+ QDomElement lockDepth = activeLock.namedItem( "depth" ).toElement();
+ // optional
+ QDomElement lockOwner = activeLock.namedItem( "owner" ).toElement();
+ QDomElement lockTimeout = activeLock.namedItem( "timeout" ).toElement();
+ QDomElement lockToken = activeLock.namedItem( "locktoken" ).toElement();
+
+ if ( !lockScope.isNull() && !lockType.isNull() && !lockDepth.isNull() )
+ {
+ // lock was properly specified
+ lockCount++;
+ QString scope = lockScope.firstChild().toElement().tagName();
+ QString type = lockType.firstChild().toElement().tagName();
+ QString depth = lockDepth.text();
+
+ setMetaData( QString("davLockScope%1").arg( lockCount ), scope );
+ setMetaData( QString("davLockType%1").arg( lockCount ), type );
+ setMetaData( QString("davLockDepth%1").arg( lockCount ), depth );
+
+ if ( !lockOwner.isNull() )
+ setMetaData( QString("davLockOwner%1").arg( lockCount ), lockOwner.text() );
+
+ if ( !lockTimeout.isNull() )
+ setMetaData( QString("davLockTimeout%1").arg( lockCount ), lockTimeout.text() );
+
+ if ( !lockToken.isNull() )
+ {
+ QDomElement tokenVal = lockScope.namedItem( "href" ).toElement();
+ if ( !tokenVal.isNull() )
+ setMetaData( QString("davLockToken%1").arg( lockCount ), tokenVal.text() );
+ }
+ }
+ }
+}
+
+long HTTPProtocol::parseDateTime( const QString& input, const QString& 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 );
+}
+
+QString HTTPProtocol::davProcessLocks()
+{
+ if ( hasMetaData( "davLockCount" ) )
+ {
+ QString response("If:");
+ int numLocks;
+ numLocks = metaData( "davLockCount" ).toInt();
+ bool bracketsOpen = false;
+ for ( int i = 0; i < numLocks; i++ )
+ {
+ if ( hasMetaData( QString("davLockToken%1").arg(i) ) )
+ {
+ if ( hasMetaData( QString("davLockURL%1").arg(i) ) )
+ {
+ if ( bracketsOpen )
+ {
+ response += ")";
+ bracketsOpen = false;
+ }
+ response += " <" + metaData( QString("davLockURL%1").arg(i) ) + ">";
+ }
+
+ if ( !bracketsOpen )
+ {
+ response += " (";
+ bracketsOpen = true;
+ }
+ else
+ {
+ response += " ";
+ }
+
+ if ( hasMetaData( QString("davLockNot%1").arg(i) ) )
+ response += "Not ";
+
+ response += "<" + metaData( QString("davLockToken%1").arg(i) ) + ">";
+ }
+ }
+
+ if ( bracketsOpen )
+ response += ")";
+
+ response += "\r\n";
+ return response;
+ }
+
+ return QString::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 = QString::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.url()
+ << endl;
+
+ if ( !checkRequestURL( url ) )
+ return;
+
+ m_request.method = DAV_MKCOL;
+ m_request.path = url.path();
+ m_request.query = QString::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.url()
+ << endl;
+
+ if ( !checkRequestURL( url ) )
+ return;
+
+ m_request.method = HTTP_GET;
+ m_request.path = url.path();
+ m_request.query = url.query();
+
+ QString 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;
+
+ QCString request;
+ request = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+ "<D:propfind xmlns:D=\"DAV:\"><D:prop>"
+ "<D:creationdate/>"
+ "<D:getcontentlength/>"
+ "<D:displayname/>"
+ "<D:resourcetype/>"
+ "</D:prop></D:propfind>";
+
+ davSetRequest( request );
+
+ // WebDAV Stat or List...
+ m_request.method = DAV_PROPFIND;
+ m_request.query = QString::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, QString::null);
+ return;
+ }
+
+ m_bError = false;
+ }
+
+ m_request.method = HTTP_PUT;
+ m_request.path = url.path();
+ m_request.query = QString::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 = QString::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 = QString::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 = QString::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 = QString::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 QString& scope,
+ const QString& type, const QString& 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 = QString::null;
+ m_request.cache = CC_Reload;
+ m_request.doProxy = m_bUseProxy;
+
+ /* Create appropriate lock XML request. */
+ QDomDocument lockReq;
+
+ QDomElement lockInfo = lockReq.createElementNS( "DAV:", "lockinfo" );
+ lockReq.appendChild( lockInfo );
+
+ QDomElement lockScope = lockReq.createElement( "lockscope" );
+ lockInfo.appendChild( lockScope );
+
+ lockScope.appendChild( lockReq.createElement( scope ) );
+
+ QDomElement lockType = lockReq.createElement( "locktype" );
+ lockInfo.appendChild( lockType );
+
+ lockType.appendChild( lockReq.createElement( type ) );
+
+ if ( !owner.isNull() ) {
+ QDomElement ownerElement = lockReq.createElement( "owner" );
+ lockReq.appendChild( ownerElement );
+
+ QDomElement 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
+ QDomDocument multiResponse;
+ multiResponse.setContent( m_bufWebDavData, true );
+
+ QDomElement prop = multiResponse.documentElement().namedItem( "prop" ).toElement();
+
+ QDomElement lockdiscovery = prop.namedItem( "lockdiscovery" ).toElement();
+
+ uint lockCount = 0;
+ davParseActiveLocks( lockdiscovery.elementsByTagName( "activelock" ), lockCount );
+
+ setMetaData( "davLockCount", QString("%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 = QString::null;
+ m_request.cache = CC_Reload;
+ m_request.doProxy = m_bUseProxy;
+
+ retrieveContent( true );
+
+ if ( m_responseCode == 200 )
+ finished();
+ else
+ davError();
+}
+
+QString HTTPProtocol::davError( int code /* = -1 */, QString url )
+{
+ bool callError = false;
+ if ( code == -1 ) {
+ code = m_responseCode;
+ callError = true;
+ }
+ if ( code == -2 ) {
+ callError = true;
+ }
+
+ if ( !url.isNull() )
+ url = m_request.url.url();
+
+ QString action, errorString;
+ KIO::Error kError;
+
+ // for 412 Precondition Failed
+ QString 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 QString::null;
+
+ QStringList errors;
+ QDomDocument multiResponse;
+
+ multiResponse.setContent( m_bufWebDavData, true );
+
+ QDomElement multistatus = multiResponse.documentElement().namedItem( "multistatus" ).toElement();
+
+ QDomNodeList responses = multistatus.elementsByTagName( "response" );
+
+ for (uint i = 0; i < responses.count(); i++)
+ {
+ int errCode;
+ QString errUrl;
+
+ QDomElement response = responses.item(i).toElement();
+ QDomElement code = response.namedItem( "status" ).toElement();
+
+ if ( !code.isNull() )
+ {
+ errCode = codeFromResponse( code.text() );
+ QDomElement 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.<ul>").arg( action ).arg( url );
+
+ for ( QStringList::Iterator it = errors.begin(); it != errors.end(); ++it )
+ errorString += "<li>" + *it + "</li>";
+
+ errorString += "</ul>";
+ }
+ 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()
+{
+ QString action, errorString;
+ KIO::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;
+ QCString replyType;
+ QByteArray params;
+ QByteArray reply;
+
+ QDataStream stream(params, IO_WriteOnly);
+ stream << url.url();
+
+ if ( dcopClient()->call( "kded", "networkstatus", "status(QString)",
+ params, replyType, reply ) && (replyType == "int") )
+ {
+ int result;
+ QDataStream stream2( reply, IO_ReadOnly );
+ stream2 >> result;
+ kdDebug(7113) << "(" << m_pid << ") networkstatus status = " << result << endl;
+ return (result != NetWorkStatusUnknown) && (result != NetWorkStatusOnline);
+ }
+ kdDebug(7113) << "(" << m_pid << ") networkstatus <unreachable>" << endl;
+ return false; // On error, assume we are online
+}
+
+void HTTPProtocol::multiGet(const QByteArray &data)
+{
+ QDataStream stream(data, IO_ReadOnly);
+ Q_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.url() << endl;
+
+ m_request.method = HTTP_GET;
+ m_request.path = url.path();
+ m_request.query = url.query();
+ QString 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<const char*>(_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;
+ QString errMsg;
+
+ kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::httpOpenConnection" << endl;
+
+ setBlockConnection( true );
+ // kio_http uses its own proxying:
+ KSocks::self()->disableSocks();
+
+ if ( m_state.doProxy )
+ {
+ QString 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 KIOJob::put() or KIOJob::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 == KIO::CC_CacheOnly);
+ bool bOffline = isOffline(m_request.doProxy ? m_proxyURL : m_request.url);
+ if (bOffline && (m_request.cache != KIO::CC_Reload))
+ m_request.cache = KIO::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 == KIO::CC_CacheOnly) || (m_request.cache == KIO::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)
+ {
+ error( ERR_DOES_NOT_EXIST, m_request.url.url() );
+ return false;
+ }
+ if (bOffline)
+ {
+ error( ERR_COULD_NOT_CONNECT, m_request.url.url() );
+ return false;
+ }
+ }
+
+ QString header;
+ QString 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 = QString("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 += QString(":%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 += QString("%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 += QString("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, QString::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 += QString("Range: bytes=%1-\r\n").arg(KIO::number(m_request.offset));
+ kdDebug(7103) << "kio_http : Range = " << KIO::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: ";
+ QString 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 += QString(":%1").arg(m_state.port);
+ header += "\r\n";
+
+ QString cookieStr;
+ QString 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";
+
+ QString 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
+ QString 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;
+
+ QStringList headerOutput = QStringList::split("\r\n", header);
+ QStringList::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 = QString::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 = QString::fromUtf8( buffer).stripWhiteSpace().lower();
+ setMetaData("charset", m_request.strCharset);
+ if (!m_request.lastModified.isEmpty())
+ setMetaData("modified", m_request.lastModified);
+ QString 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;
+ }
+
+ QCString locationStr; // In case we get a redirect.
+ QCString cookieStr; // In case we get a cookie.
+
+ QString dispositionType; // In case we get a Content-Disposition type
+ QString dispositionFilename; // In case we get a Content-Disposition filename
+
+ QString mediaValue;
+ QString mediaAttribute;
+
+ QStringList 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 = QString::null;
+ m_request.lastModified = QString::null;
+ m_request.strCharset = QString::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(QString::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) << "kio_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 << QString::fromLatin1(buf);
+
+ if ((strncasecmp(buf, "HTTP", 4) == 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.url());
+ 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.url());
+ 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) {
+ QStringList options = QStringList::split(',',
+ QString::fromLatin1(trimLead(buf+11)));
+ for(QStringList::ConstIterator it = options.begin();
+ it != options.end();
+ it++)
+ {
+ QString option = (*it).stripWhiteSpace().lower();
+ if (option.startsWith("timeout="))
+ {
+ m_keepAliveTimeout = option.mid(8).toInt();
+ }
+ }
+ }
+
+ // Cache control
+ else if (strncasecmp(buf, "Cache-Control:", 14) == 0) {
+ QStringList cacheControls = QStringList::split(',',
+ QString::fromLatin1(trimLead(buf+14)));
+ for(QStringList::ConstIterator it = cacheControls.begin();
+ it != cacheControls.end();
+ it++)
+ {
+ QString 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)
+ {
+ QString 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",
+ QString::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 = QString::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 = QString::fromLatin1(start, pos-start).stripWhiteSpace().lower();
+ mediaValue = QString::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 = (QString::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) {
+ QCString pragma = QCString(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", QString::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 = QCString(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
+ QString offered = &(buf[8]);
+ upgradeOffers = QStringList::split(QRegExp("[ \n,\r\t]"), offered);
+ }
+
+ // content?
+ else if (strncasecmp(buf, "Content-Encoding:", 17) == 0) {
+ // This is so wrong !! No wonder kio_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 = QString::fromLatin1( bufStart, dispositionBuf-bufStart );
+
+ break;
+ }
+ }
+ else
+ {
+ char *bufStart = dispositionBuf;
+
+ while ( *dispositionBuf && *dispositionBuf != ';' )
+ dispositionBuf++;
+
+ if ( dispositionBuf > bufStart )
+ dispositionType = QString::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) {
+ QString language = QString::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: <url>; rel="type" so far
+ QStringList link = QStringList::split(";", QString(buf)
+ .replace(QRegExp("^Link:[ ]*"),
+ ""));
+ if (link.count() == 2) {
+ QString rel = link[1].stripWhiteSpace();
+ if (rel.startsWith("rel=\"")) {
+ rel = rel.mid(5, rel.length() - 6);
+ if (rel.lower() == "pageservices") {
+ QString url = link[0].replace(QRegExp("[<>]"),"").stripWhiteSpace();
+ setMetaData("PageServices", url);
+ }
+ }
+ }
+ }
+ else if (strncasecmp(buf, "P3P:", 4) == 0) {
+ QString p3pstr = buf;
+ p3pstr = p3pstr.mid(4).simplifyWhiteSpace();
+ QStringList policyrefs, compact;
+ QStringList policyfields = QStringList::split(QRegExp(",[ ]*"), p3pstr);
+ for (QStringList::Iterator it = policyfields.begin();
+ it != policyfields.end();
+ ++it) {
+ QStringList policy = QStringList::split("=", *it);
+
+ if (policy.count() == 2) {
+ if (policy[0].lower() == "policyref") {
+ policyrefs << policy[1].replace(QRegExp("[\"\']"), "")
+ .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.
+ QStringList cps = QStringList::split(" ",
+ policy[1].replace(QRegExp("[\"\']"), "")
+ .simplifyWhiteSpace());
+
+ for (QStringList::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 = QString::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 << QString::fromLatin1(trimLead(buf + 4));
+ }
+ else {
+ m_davCapabilities << QString::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
+ QStringList::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<long>(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.
+ QString 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.url());
+ 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.url());
+ 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.url()
+ << endl << "LocationStr: " << locationStr.data() << endl;
+
+ kdDebug(7113) << "(" << m_pid << ") Requesting redirection to: " << u.url()
+ << 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 = QString::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 = QString::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 = QString::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 = QString::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 = QString::fromLatin1("application/x-bzip2");
+ }
+
+ // Convert some common mimetypes to standard KDE mimetypes
+ if (m_strMimeType == "application/x-targz")
+ m_strMimeType = QString::fromLatin1("application/x-tgz");
+ else if (m_strMimeType == "application/zip")
+ m_strMimeType = QString::fromLatin1("application/x-zip");
+ else if (m_strMimeType == "image/x-png")
+ m_strMimeType = QString::fromLatin1("image/png");
+ else if (m_strMimeType == "image/bmp")
+ m_strMimeType = QString::fromLatin1("image/x-bmp");
+ else if (m_strMimeType == "audio/mpeg" || m_strMimeType == "audio/x-mpeg" || m_strMimeType == "audio/mp3")
+ m_strMimeType = QString::fromLatin1("audio/x-mp3");
+ else if (m_strMimeType == "audio/microsoft-wave")
+ m_strMimeType = QString::fromLatin1("audio/x-wav");
+ else if (m_strMimeType == "audio/midi")
+ m_strMimeType = QString::fromLatin1("audio/x-midi");
+ else if (m_strMimeType == "image/x-xpixmap")
+ m_strMimeType = QString::fromLatin1("image/x-xpm");
+ else if (m_strMimeType == "application/rtf")
+ m_strMimeType = QString::fromLatin1("text/rtf");
+
+ // Crypto ones....
+ else if (m_strMimeType == "application/pkix-cert" ||
+ m_strMimeType == "application/binary-certificate")
+ {
+ m_strMimeType = QString::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 = QString::fromLatin1("application/x-tgz");
+ if ((m_request.url.path().right(6) == ".ps.gz"))
+ m_strMimeType = QString::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"))
+ {
+ QString ext = m_request.url.path().right(4).upper();
+ if (ext == ".BZ2")
+ m_strMimeType = QString::fromLatin1("application/x-bzip2");
+ else if (ext == ".PEM")
+ m_strMimeType = QString::fromLatin1("application/x-x509-ca-cert");
+ else if (ext == ".SWF")
+ m_strMimeType = QString::fromLatin1("application/x-shockwave-flash");
+ else if (ext == ".PLS")
+ m_strMimeType = QString::fromLatin1("audio/x-scpls");
+ else if (ext == ".WMV")
+ m_strMimeType = QString::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
+ {
+ QString 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( QFile::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.url()<<"!\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.url() << "\"" << endl;
+ else if (m_request.bCachedWrite && m_strMimeType.isEmpty())
+ kdDebug(7113) << "(" << m_pid << ") Cache, pending \"" << m_request.url.url() << "\"" << endl;
+ else
+ kdDebug(7113) << "(" << m_pid << ") Cache, not adding \"" << m_request.url.url() << "\"" << endl;
+ return true;
+}
+
+
+void HTTPProtocol::addEncoding(QString encoding, QStringList &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(QString::fromLatin1("gzip"));
+ } else if ((encoding == "x-bzip2") || (encoding == "bzip2")) {
+ encs.append(QString::fromLatin1("bzip2")); // Not yet supported!
+ } else if ((encoding == "x-deflate") || (encoding == "deflate")) {
+ encs.append(QString::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;
+
+ QByteArray 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 ) );
+
+ QString size = QString ("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: " << QCString(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)
+ {
+ QString filename = m_request.cef + ".new";
+ ::unlink( QFile::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
+ // khtml.
+ 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;
+ QByteArray data;
+ QDataStream 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 QByteArray &data )
+{
+ kdDebug(7113) << "(" << m_pid << ") HTTPProtocol::special" << endl;
+
+ int tmp;
+ QDataStream 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;
+ QString 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, (KIO::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 QByteArray &_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;
+ }
+
+ QByteArray 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 = QString::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 QByteArray 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 KIOJob::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.
+ KIO::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(KIO::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( QByteArray() );
+ }
+
+ 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. "
+ << KIO::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;
+
+ QObject::connect(&chain, SIGNAL(output(const QByteArray &)),
+ this, SLOT(slotData(const QByteArray &)));
+ QObject::connect(&chain, SIGNAL(error(int, const QString &)),
+ this, SLOT(error(int, const QString &)));
+
+ // decode all of the transfer encodings
+ while (!m_qTransferEncodings.isEmpty())
+ {
+ QString 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())
+ {
+ QString 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) << "("<<m_pid<<") EOD received! Left = "<< KIO::number(m_iBytesLeft) << endl;
+ break;
+ }
+ }
+ chain.slotInput(QByteArray()); // Flush chain.
+
+ if ( useMD5 )
+ {
+ QString calculatedMD5 = md5Filter->md5();
+
+ if ( m_sContentMD5 == calculatedMD5 )
+ kdDebug(7113) << "(" << m_pid << ") MD5 checksum MATCHED!!" << endl;
+ else
+ kdDebug(7113) << "(" << m_pid << ") MD5 checksum MISMATCH! Expected: "
+ << calculatedMD5 << ", Got: " << m_sContentMD5 << endl;
+ }
+
+ // Close cache entry
+ if (m_iBytesLeft == 0)
+ {
+ if (m_request.bCachedWrite && m_request.fcache)
+ closeCacheEntry();
+ else if (m_request.bCachedWrite)
+ kdDebug(7113) << "(" << m_pid << ") no cache file!\n";
+ }
+ else
+ {
+ kdDebug(7113) << "(" << m_pid << ") still "<< KIO::number(m_iBytesLeft)
+ << " bytes left! can't close cache entry!\n";
+ }
+
+ if (sz <= 1)
+ {
+ /* kdDebug(7113) << "(" << m_pid << ") readBody: sz = " << KIO::number(sz)
+ << ", responseCode =" << m_responseCode << endl; */
+ if (m_responseCode >= 500 && m_responseCode <= 599)
+ error(ERR_INTERNAL_SERVER, m_state.hostname);
+ else if (m_responseCode >= 400 && m_responseCode <= 499)
+ error(ERR_DOES_NOT_EXIST, m_state.hostname);
+ }
+
+ if (!dataInternal)
+ data( QByteArray() );
+
+ return true;
+}
+
+
+void HTTPProtocol::error( int _err, const QString &_text )
+{
+ httpClose(false);
+
+ if (!m_request.id.isEmpty())
+ {
+ forwardHttpResponseHeader();
+ sendMetaData();
+ }
+
+ // Clear of the temporary POST buffer if it is not empty...
+ if (!m_bufPOST.isEmpty())
+ {
+ m_bufPOST.resize(0);
+ kdDebug(7113) << "(" << m_pid << ") HTTP::retreiveHeader: Cleared POST "
+ "buffer..." << endl;
+ }
+
+ SlaveBase::error( _err, _text );
+ m_bError = true;
+}
+
+
+void HTTPProtocol::addCookies( const QString &url, const QCString &cookieHeader )
+{
+ long windowId = m_request.window.toLong();
+ QByteArray params;
+ QDataStream stream(params, IO_WriteOnly);
+ stream << url << cookieHeader << windowId;
+
+ kdDebug(7113) << "(" << m_pid << ") " << cookieHeader << endl;
+ kdDebug(7113) << "(" << m_pid << ") " << "Window ID: "
+ << windowId << ", for host = " << url << endl;
+
+ if ( !dcopClient()->send( "kded", "kcookiejar", "addCookies(QString,QCString,long int)", params ) )
+ {
+ kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
+ }
+}
+
+QString HTTPProtocol::findCookies( const QString &url)
+{
+ QCString replyType;
+ QByteArray params;
+ QByteArray reply;
+ QString result;
+
+ long windowId = m_request.window.toLong();
+ result = QString::null;
+ QDataStream stream(params, IO_WriteOnly);
+ stream << url << windowId;
+
+ if ( !dcopClient()->call( "kded", "kcookiejar", "findCookies(QString,long int)",
+ params, replyType, reply ) )
+ {
+ kdWarning(7113) << "(" << m_pid << ") Can't communicate with kded_kcookiejar!" << endl;
+ return result;
+ }
+ if ( replyType == "QString" )
+ {
+ QDataStream stream2( reply, IO_ReadOnly );
+ stream2 >> result;
+ }
+ else
+ {
+ kdError(7113) << "(" << m_pid << ") DCOP function findCookies(...) returns "
+ << replyType << ", expected QString" << 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( QFile::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 QChar separator = '_';
+
+ QString CEF = m_request.path;
+
+ int p = CEF.find('/');
+
+ while(p != -1)
+ {
+ CEF[p] = separator;
+ p = CEF.find('/', p);
+ }
+
+ QString host = m_request.hostname.lower();
+ CEF = host + CEF + '_';
+
+ QString 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;
+ QCString u = m_request.url.url().latin1();
+ for(int i = u.length(); i--;)
+ {
+ hash = (hash * 12211 + u[i]) % 2147483563;
+ }
+
+ QString hashString;
+ hashString.sprintf("%08lx", hash);
+
+ CEF = CEF + hashString;
+
+ CEF = dir + "/" + CEF;
+
+ m_request.cef = CEF;
+
+ const char *mode = (readWrite ? "r+" : "r");
+
+ FILE *fs = fopen( QFile::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 = QString(buffer).stripWhiteSpace();
+ }
+
+ // Last-Modified
+ if (ok && (!fgets(buffer, 400, fs)))
+ ok = false;
+ if (ok)
+ {
+ m_request.lastModified = QString(buffer).stripWhiteSpace();
+ }
+
+ if (ok)
+ return fs;
+
+ fclose(fs);
+ unlink( QFile::encodeName(CEF));
+ return 0;
+}
+
+void HTTPProtocol::updateExpireDate(time_t expireDate, bool updateCreationDate)
+{
+ bool ok = true;
+
+ FILE *fs = checkCacheEntry(true);
+ if (fs)
+ {
+ QString 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;
+ QString 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
+ // <META http-equiv="Expires"> 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 QString &mimetype, time_t expireDate)
+{
+ QString dir = m_request.cef;
+ int p = dir.findRev('/');
+ if (p == -1) return; // Error.
+ dir.truncate(p);
+
+ // Create file
+ (void) ::mkdir( QFile::encodeName(dir), 0700 );
+
+ QString filename = m_request.cef + ".new"; // Create a new cache entryexpireDate
+
+// kdDebug( 7103 ) << "creating new cache entry: " << filename << endl;
+
+ m_request.fcache = fopen( QFile::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);
+
+ QString 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;
+ QString filename = m_request.cef + ".new";
+ ::unlink( QFile::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;
+ QString filename = m_request.cef + ".new";
+ ::unlink( QFile::encodeName(filename) );
+ return;
+ }
+}
+
+void HTTPProtocol::closeCacheEntry()
+{
+ QString filename = m_request.cef + ".new";
+ int result = fclose( m_request.fcache);
+ m_request.fcache = 0;
+ if (result == 0)
+ {
+ if (::rename( QFile::encodeName(filename), QFile::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;
+ QString cleanFile = m_strCacheDir;
+ if (cleanFile[cleanFile.length()-1] != '/')
+ cleanFile += "/";
+ cleanFile += "cleaned";
+
+ struct stat stat_buf;
+
+ int result = ::stat(QFile::encodeName(cleanFile), &stat_buf);
+ if (result == -1)
+ {
+ int fd = creat( QFile::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(QFile::encodeName(cleanFile), 0);
+ KApplication::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 kdesud 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
+ QTextCodec* oldCodec=QTextCodec::codecForCStrings();
+ if (KGlobal::locale()->language().contains("ru"))
+ QTextCodec::setCodecForCStrings(QTextCodec::codecForName("CP1251"));
+
+ p += 6;
+ if (*p == '"') p++;
+ while( p[i] && p[i] != '"' ) i++;
+ if( isForProxy )
+ m_strProxyRealm = QString::fromAscii( p, i );
+ else
+ m_strRealm = QString::fromAscii( p, i );
+
+ QTextCodec::setCodecForCStrings(oldCodec);
+
+ if (!p[i]) break;
+ }
+ p+=(i+1);
+ }
+
+ if( isForProxy )
+ {
+ ProxyAuthentication = f;
+ m_strProxyAuthorization = QString::fromLatin1( strAuth );
+ }
+ else
+ {
+ Authentication = f;
+ m_strAuthorization = QString::fromLatin1( strAuth );
+ }
+}
+
+
+bool HTTPProtocol::retryPrompt()
+{
+ QString 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("<b>%1</b> at <b>%2</b>").arg( 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("<b>%1</b> at <b>%2</b>").arg( 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);
+
+ QString errorMsg;
+
+ if (repeatFailure)
+ {
+ bool prompt = true;
+ if ( Authentication == AUTH_Digest || ProxyAuthentication == AUTH_Digest )
+ {
+ bool isStaleNonce = false;
+ QString 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 )
+ {
+ QString 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 )
+ {
+ QString 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, QString::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
+QCString 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;
+ QCString 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;
+}
+
+QString HTTPProtocol::createNegotiateAuth()
+{
+ QString auth;
+ QCString servicename;
+ QByteArray 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; i<mech_set->count && !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 = QString::null;
+ return QString::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 = QString::null;
+ return QString::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
+QCString HTTPProtocol::gssError( int, int )
+{
+ return "";
+}
+
+// Dummy
+QString HTTPProtocol::createNegotiateAuth()
+{
+ return QString::null;
+}
+#endif
+
+QString HTTPProtocol::createNTLMAuth( bool isForProxy )
+{
+ uint len;
+ QString auth, user, domain, passwd;
+ QCString strauth;
+ QByteArray 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 QString::null;
+
+ if ( len > 4 )
+ {
+ // create a response
+ QByteArray 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;
+}
+
+QString HTTPProtocol::createBasicAuth( bool isForProxy )
+{
+ QString auth;
+ QCString 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, QCString& Response )
+{
+ KMD5 md;
+ QCString HA1;
+ QCString HA2;
+
+ // Calculate H(A1)
+ QCString 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;
+}
+
+QString HTTPProtocol::createDigestAuth ( bool isForProxy )
+{
+ const char *p;
+
+ QString auth;
+ QCString opaque;
+ QCString 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 QString::null;
+
+ p += 6; // Skip "Digest"
+
+ if ( info.username.isEmpty() || info.password.isEmpty() || !p )
+ return QString::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 = KApplication::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 = QCString( 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 = QCString(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 = QCString(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;
+ QCString uri = QCString(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 = QCString(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 = QCString(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 = QCString(p,i+1);
+ }
+ p+=(i+1);
+ }
+
+ if (info.realm.isEmpty() || info.nonce.isEmpty())
+ return QString::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...
+ QString 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());
+
+ QString 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 QString::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;
+}
+
+QString HTTPProtocol::proxyAuthenticationHeader()
+{
+ QString 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/kioslave/http/http.h b/kioslave/http/http.h
new file mode 100644
index 000000000..ea2e68a8a
--- /dev/null
+++ b/kioslave/http/http.h
@@ -0,0 +1,577 @@
+/*
+ Copyright (C) 2000,2001 Dawit Alemayehu <adawit@kde.org>
+ Copyright (C) 2000,2001 Waldo Bastian <bastian@kde.org>
+ Copyright (C) 2000,2001 George Staikos <staikos@kde.org>
+ Copyright (C) 2001,2002 Hamish Rodda <rodda@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef HTTP_H_
+#define HTTP_H_
+
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <qptrlist.h>
+#include <qstrlist.h>
+#include <qstringlist.h>
+
+#include <kurl.h>
+#include "kio/tcpslavebase.h"
+#include "kio/http.h"
+
+class DCOPClient;
+class QDomElement;
+class QDomNodeList;
+
+namespace KIO {
+ class AuthInfo;
+}
+
+class HTTPProtocol : public QObject, public KIO::TCPSlaveBase
+{
+ Q_OBJECT
+public:
+ HTTPProtocol( const QCString &protocol, const QCString &pool,
+ const QCString &app );
+ virtual ~HTTPProtocol();
+
+ /** HTTP version **/
+ enum HTTP_REV {HTTP_None, HTTP_Unknown, HTTP_10, HTTP_11, SHOUTCAST};
+
+ /** Authorization method used **/
+ enum HTTP_AUTH {AUTH_None, AUTH_Basic, AUTH_NTLM, AUTH_Digest, AUTH_Negotiate};
+
+ /** HTTP / DAV method **/
+ // Removed to interfaces/kio/http.h
+ //enum HTTP_METHOD {HTTP_GET, HTTP_PUT, HTTP_POST, HTTP_HEAD, HTTP_DELETE,
+ // HTTP_OPTIONS, DAV_PROPFIND, DAV_PROPPATCH, DAV_MKCOL,
+ // DAV_COPY, DAV_MOVE, DAV_LOCK, DAV_UNLOCK, DAV_SEARCH };
+
+ /** State of the current Connection **/
+ struct HTTPState
+ {
+ HTTPState ()
+ {
+ port = 0;
+ doProxy = false;
+ }
+
+ QString hostname;
+ QString encoded_hostname;
+ short unsigned int port;
+ QString user;
+ QString passwd;
+ bool doProxy;
+ };
+
+ /** DAV-specific request elements for the current connection **/
+ struct DAVRequest
+ {
+ DAVRequest ()
+ {
+ overwrite = false;
+ depth = 0;
+ }
+
+ QString desturl;
+ bool overwrite;
+ int depth;
+ };
+
+ /** The request for the current connection **/
+ struct HTTPRequest
+ {
+ HTTPRequest ()
+ {
+ port = 0;
+ method = KIO::HTTP_UNKNOWN;
+ offset = 0;
+ doProxy = false;
+ allowCompressedPage = false;
+ disablePassDlg = false;
+ bNoAuth = false;
+ bUseCache = false;
+ bCachedRead = false;
+ bCachedWrite = false;
+ fcache = 0;
+ bMustRevalidate = false;
+ cacheExpireDateOffset = 0;
+ bErrorPage = false;
+ bUseCookiejar = false;
+ expireDate = 0;
+ creationDate = 0;
+ }
+
+ QString hostname;
+ QString encoded_hostname;
+ short unsigned int port;
+ QString user;
+ QString passwd;
+ QString path;
+ QString query;
+ KIO::HTTP_METHOD method;
+ KIO::CacheControl cache;
+ KIO::filesize_t offset;
+ bool doProxy;
+ KURL url;
+ QString window; // Window Id this request is related to.
+ QString referrer;
+ QString charsets;
+ QString languages;
+ bool allowCompressedPage;
+ bool disablePassDlg;
+ QString userAgent;
+ QString id;
+ DAVRequest davData;
+
+ bool bNoAuth; // Do not authenticate
+
+ // Cache related
+ QString cef; // Cache Entry File belonging to this URL.
+ bool bUseCache; // Whether the cache is active
+ bool bCachedRead; // Whether the file is to be read from m_fcache.
+ bool bCachedWrite; // Whether the file is to be written to m_fcache.
+ FILE* fcache; // File stream of a cache entry
+ QString etag; // ETag header.
+ QString lastModified; // Last modified.
+ bool bMustRevalidate; // Cache entry is expired.
+ long cacheExpireDateOffset; // Position in the cache entry where the
+ // 16 byte expire date is stored.
+ time_t expireDate; // Date when the cache entry will expire
+ time_t creationDate; // Date when the cache entry was created
+ QString strCharset; // Charset
+
+ // Indicates whether an error-page or error-msg should is preferred.
+ bool bErrorPage;
+
+ // Cookie flags
+ bool bUseCookiejar;
+ enum { CookiesAuto, CookiesManual, CookiesNone } cookieMode;
+ };
+
+ struct DigestAuthInfo
+ {
+ QCString nc;
+ QCString qop;
+ QCString realm;
+ QCString nonce;
+ QCString method;
+ QCString cnonce;
+ QCString username;
+ QCString password;
+ QStrList digestURI;
+ QCString algorithm;
+ QCString entityBody;
+ };
+
+//---------------------- Re-implemented methods ----------------
+ virtual void setHost(const QString& host, int port, const QString& user,
+ const QString& pass);
+
+ virtual void slave_status();
+
+ virtual void get( const KURL& url );
+ virtual void put( const KURL& url, int permissions, bool overwrite,
+ bool resume );
+
+//----------------- Re-implemented methods for WebDAV -----------
+ virtual void listDir( const KURL& url );
+ virtual void mkdir( const KURL& url, int permissions );
+
+ virtual void rename( const KURL& src, const KURL& dest, bool overwrite );
+ virtual void copy( const KURL& src, const KURL& dest, int permissions, bool overwrite );
+ virtual void del( const KURL& url, bool isfile );
+
+ // ask the host whether it supports WebDAV & cache this info
+ bool davHostOk();
+
+ // send generic DAV request
+ void davGeneric( const KURL& url, KIO::HTTP_METHOD method );
+
+ // Send requests to lock and unlock resources
+ void davLock( const KURL& url, const QString& scope,
+ const QString& type, const QString& owner );
+ void davUnlock( const KURL& url );
+
+ // Calls httpClose() and finished()
+ void davFinished();
+
+ // Handle error conditions
+ QString davError( int code = -1, QString url = QString::null );
+//---------------------------- End WebDAV -----------------------
+
+ /**
+ * Special commands supported by this slave :
+ * 1 - HTTP POST
+ * 2 - Cache has been updated
+ * 3 - SSL Certificate Cache has been updated
+ * 4 - HTTP multi get
+ * 5 - DAV LOCK (see
+ * 6 - DAV UNLOCK README.webdav)
+ */
+ virtual void special( const QByteArray &data );
+
+ virtual void mimetype( const KURL& url);
+
+ virtual void stat( const KURL& url );
+
+ virtual void reparseConfiguration();
+
+ virtual void closeConnection(); // Forced close of connection
+
+ void post( const KURL& url );
+ void multiGet(const QByteArray &data);
+ bool checkRequestURL( const KURL& );
+ void cacheUpdate( const KURL &url, bool nocache, time_t expireDate);
+
+ void httpError(); // Generate error message based on response code
+
+ bool isOffline(const KURL &url); // Check network status
+
+protected slots:
+ void slotData(const QByteArray &);
+ void error( int _errid, const QString &_text );
+
+protected:
+ int readChunked(); // Read a chunk
+ int readLimited(); // Read maximum m_iSize bytes.
+ int readUnlimited(); // Read as much as possible.
+
+ /**
+ * A "smart" wrapper around write that will use SSL_write or
+ * write(2) depending on whether you've got an SSL connection or not.
+ * The only shortcomming is that it uses the "global" file handles and
+ * soforth. So you can't really use this on individual files/sockets.
+ */
+ ssize_t write(const void *buf, size_t nbytes);
+
+ /**
+ * Another "smart" wrapper, this time around read that will
+ * use SSL_read or read(2) depending on whether you've got an
+ * SSL connection or not.
+ */
+ ssize_t read (void *b, size_t nbytes);
+
+ char *gets (char *str, int size);
+
+ void setRewindMarker();
+ void rewind();
+
+ /**
+ * Add an encoding on to the appropriate stack this
+ * is nececesary because transfer encodings and
+ * content encodings must be handled separately.
+ */
+ void addEncoding(QString, QStringList &);
+
+ void configAuth( char *, bool );
+
+ bool httpOpen(); // Open transfer
+ void httpClose(bool keepAlive); // Close transfer
+
+ bool httpOpenConnection(); // Open connection
+ void httpCloseConnection(); // Close connection
+ void httpCheckConnection(); // Check whether to keep connection.
+
+ void forwardHttpResponseHeader();
+
+ bool readHeader();
+
+ bool sendBody();
+
+ // where dataInternal == true, the content is to be made available
+ // to an internal function.
+ bool readBody( bool dataInternal = false );
+
+ /**
+ * Performs a WebDAV stat or list
+ */
+ void davSetRequest( const QCString& requestXML );
+ void davStatList( const KURL& url, bool stat = true );
+ void davParsePropstats( const QDomNodeList& propstats, KIO::UDSEntry& entry );
+ void davParseActiveLocks( const QDomNodeList& activeLocks,
+ uint& lockCount );
+
+ /**
+ * Parses a date & time string
+ */
+ long parseDateTime( const QString& input, const QString& type );
+
+ /**
+ * Returns the error code from a "HTTP/1.1 code Code Name" string
+ */
+ int codeFromResponse( const QString& response );
+
+ /**
+ * Extracts locks from metadata
+ * Returns the appropriate If: header
+ */
+ QString davProcessLocks();
+
+ /**
+ * Send a cookie to the cookiejar
+ */
+ void addCookies( const QString &url, const QCString &cookieHeader);
+
+ /**
+ * Look for cookies in the cookiejar
+ */
+ QString findCookies( const QString &url);
+
+ /**
+ * Do a cache lookup for the current url. (m_state.url)
+ *
+ * @param readWrite If true, file is opened read/write.
+ * If false, file is opened read-only.
+ *
+ * @return a file stream open for reading and at the start of
+ * the header section when the Cache entry exists and is valid.
+ * 0 if no cache entry could be found, or if the entry is not
+ * valid (any more).
+ */
+ FILE *checkCacheEntry(bool readWrite = false);
+
+ /**
+ * Create a cache entry for the current url. (m_state.url)
+ *
+ * Set the contents type of the cache entry to 'mimetype'.
+ */
+ void createCacheEntry(const QString &mimetype, time_t expireDate);
+
+ /**
+ * Write data to cache.
+ *
+ * Write 'nbytes' from 'buffer' to the Cache Entry File
+ */
+ void writeCacheEntry( const char *buffer, int nbytes);
+
+ /**
+ * Close cache entry
+ */
+ void closeCacheEntry();
+
+ /**
+ * Update expire time of current cache entry.
+ */
+ void updateExpireDate(time_t expireDate, bool updateCreationDate=false);
+
+ /**
+ * Quick check whether the cache needs cleaning.
+ */
+ void cleanCache();
+
+ /**
+ * Performs a GET HTTP request.
+ */
+ // where dataInternal == true, the content is to be made available
+ // to an internal function.
+ void retrieveContent( bool dataInternal = false );
+
+ /**
+ * Performs a HEAD HTTP request.
+ */
+ bool retrieveHeader(bool close_connection = true);
+
+ /**
+ * Resets any per session settings.
+ */
+ void resetSessionSettings();
+
+ /**
+ * Resets settings related to parsing a response.
+ */
+ void resetResponseSettings();
+
+ /**
+ * Resets any per connection settings. These are different from
+ * per-session settings in that they must be invalidates every time
+ * a request is made, e.g. a retry to re-send the header to the
+ * server, as compared to only when a new request arrives.
+ */
+ void resetConnectionSettings();
+
+ /**
+ * Returns any pre-cached proxy authentication info
+ * info in HTTP header format.
+ */
+ QString proxyAuthenticationHeader();
+
+ /**
+ * Retrieves authorization info from cache or user.
+ */
+ bool getAuthorization();
+
+ /**
+ * Saves valid authorization info in the cache daemon.
+ */
+ void saveAuthorization();
+
+ /**
+ * Creates the entity-header for Basic authentication.
+ */
+ QString createBasicAuth( bool isForProxy = false );
+
+ /**
+ * Creates the entity-header for Digest authentication.
+ */
+ QString createDigestAuth( bool isForProxy = false );
+
+ /**
+ * Creates the entity-header for NTLM authentication.
+ */
+ QString createNTLMAuth( bool isForProxy = false );
+
+ /**
+ * Creates the entity-header for Negotiate authentication.
+ */
+ QString createNegotiateAuth();
+
+ /**
+ * create GSS error string
+ */
+ QCString gssError( int major_status, int minor_status );
+
+ /**
+ * Calcualtes the message digest response based on RFC 2617.
+ */
+ void calculateResponse( DigestAuthInfo &info, QCString &Response );
+
+ /**
+ * Prompts the user for authorization retry.
+ */
+ bool retryPrompt();
+
+ /**
+ * Creates authorization prompt info.
+ */
+ void promptInfo( KIO::AuthInfo& info );
+
+protected:
+ HTTPState m_state;
+ HTTPRequest m_request;
+ QPtrList<HTTPRequest> m_requestQueue;
+
+ bool m_bBusy; // Busy handling request queue.
+ bool m_bEOF;
+ bool m_bEOD;
+
+//--- Settings related to a single response only
+ QStringList m_responseHeader; // All headers
+ KURL m_redirectLocation;
+ bool m_bRedirect; // Indicates current request is a redirection
+
+ // Processing related
+ bool m_bChunked; // Chunked tranfer encoding
+ KIO::filesize_t m_iSize; // Expected size of message
+ KIO::filesize_t m_iBytesLeft; // # of bytes left to receive in this message.
+ KIO::filesize_t m_iContentLeft; // # of content bytes left
+ QByteArray m_bufReceive; // Receive buffer
+ bool m_dataInternal; // Data is for internal consumption
+ char m_lineBuf[1024];
+ char m_rewindBuf[8192];
+ size_t m_rewindCount;
+ char *m_linePtr;
+ size_t m_lineCount;
+ char *m_lineBufUnget;
+ char *m_linePtrUnget;
+ size_t m_lineCountUnget;
+
+ // Mimetype determination
+ bool m_cpMimeBuffer;
+ QByteArray m_mimeTypeBuffer;
+
+ // Language/Encoding related
+ QStringList m_qTransferEncodings;
+ QStringList m_qContentEncodings;
+ QString m_sContentMD5;
+ QString m_strMimeType;
+
+
+//--- WebDAV
+ // Data structure to hold data which will be passed to an internal func.
+ QByteArray m_bufWebDavData;
+ QStringList m_davCapabilities;
+
+ bool m_davHostOk;
+ bool m_davHostUnsupported;
+//----------
+
+ // Holds the POST data so it won't get lost on if we
+ // happend to get a 401/407 response when submitting,
+ // a form.
+ QByteArray m_bufPOST;
+
+ // Cache related
+ int m_maxCacheAge; // Maximum age of a cache entry.
+ long m_maxCacheSize; // Maximum cache size in Kb.
+ QString m_strCacheDir; // Location of the cache.
+
+
+
+//--- Proxy related members
+ bool m_bUseProxy;
+ bool m_bNeedTunnel; // Whether we need to make a SSL tunnel
+ bool m_bIsTunneled; // Whether we have an active SSL tunnel
+ bool m_bProxyAuthValid;
+ int m_iProxyPort;
+ KURL m_proxyURL;
+ QString m_strProxyRealm;
+
+ // Operation mode
+ QCString m_protocol;
+
+ // Authentication
+ QString m_strRealm;
+ QString m_strAuthorization;
+ QString m_strProxyAuthorization;
+ HTTP_AUTH Authentication;
+ HTTP_AUTH ProxyAuthentication;
+ bool m_bUnauthorized;
+ short unsigned int m_iProxyAuthCount;
+ short unsigned int m_iWWWAuthCount;
+
+ // First request on a connection
+ bool m_bFirstRequest;
+
+ // Persistent connections
+ bool m_bKeepAlive;
+ int m_keepAliveTimeout; // Timeout in seconds.
+
+ // Persistent proxy connections
+ bool m_bPersistentProxyConnection;
+
+
+ // Indicates whether there was some connection error.
+ bool m_bError;
+
+ // Previous and current response codes
+ unsigned int m_responseCode;
+ unsigned int m_prevResponseCode;
+
+ // Values that determine the remote connection timeouts.
+ int m_proxyConnTimeout;
+ int m_remoteConnTimeout;
+ int m_remoteRespTimeout;
+
+ int m_pid;
+};
+#endif
diff --git a/kioslave/http/http.protocol b/kioslave/http/http.protocol
new file mode 100644
index 000000000..ea7b57869
--- /dev/null
+++ b/kioslave/http/http.protocol
@@ -0,0 +1,12 @@
+[Protocol]
+exec=kio_http
+protocol=http
+input=none
+output=filesystem
+reading=true
+defaultMimetype=application/octet-stream
+determineMimetypeFromExtension=false
+Icon=www
+maxInstances=3
+DocPath=kioslave/http.html
+Class=:internet
diff --git a/kioslave/http/http_cache_cleaner.cpp b/kioslave/http/http_cache_cleaner.cpp
new file mode 100644
index 000000000..f7406bcc1
--- /dev/null
+++ b/kioslave/http/http_cache_cleaner.cpp
@@ -0,0 +1,284 @@
+/*
+This file is part of KDE
+
+ Copyright (C) 1999-2000 Waldo Bastian (bastian@kde.org)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+//----------------------------------------------------------------------------
+//
+// KDE Http Cache cleanup tool
+// $Id$
+
+#include <time.h>
+#include <stdlib.h>
+
+#include <qdir.h>
+#include <qstring.h>
+#include <qptrlist.h>
+
+#include <kinstance.h>
+#include <klocale.h>
+#include <kcmdlineargs.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <dcopclient.h>
+#include <kprotocolmanager.h>
+
+#include <unistd.h>
+
+#include <kdebug.h>
+
+time_t currentDate;
+int m_maxCacheAge;
+int m_maxCacheSize;
+
+static const char appName[] = "kio_http_cache_cleaner";
+
+static const char description[] = I18N_NOOP("KDE HTTP cache maintenance tool");
+
+static const char version[] = "1.0.0";
+
+static const KCmdLineOptions options[] =
+{
+ {"clear-all", I18N_NOOP("Empty the cache"), 0},
+ KCmdLineLastOption
+};
+
+struct FileInfo {
+ QString name;
+ int size; // Size in Kb.
+ int age;
+};
+
+template class QPtrList<FileInfo>;
+
+class FileInfoList : public QPtrList<FileInfo>
+{
+public:
+ FileInfoList() : QPtrList<FileInfo>() { }
+ int compareItems(QPtrCollection::Item item1, QPtrCollection::Item item2)
+ { return ((FileInfo *)item1)->age - ((FileInfo *)item2)->age; }
+};
+
+// !START OF SYNC!
+// Keep the following in sync with the cache code in http.cc
+#define CACHE_REVISION "7\n"
+
+FileInfo *readEntry( const QString &filename)
+{
+ QCString CEF = QFile::encodeName(filename);
+ FILE *fs = fopen( CEF.data(), "r");
+ 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;
+
+ // Full URL
+ if (ok && (!fgets(buffer, 400, fs)))
+ ok = false;
+
+ time_t creationDate;
+ int age =0;
+
+ // Creation Date
+ if (ok && (!fgets(buffer, 400, fs)))
+ ok = false;
+ if (ok)
+ {
+ creationDate = (time_t) strtoul(buffer, 0, 10);
+ age = (int) difftime(currentDate, creationDate);
+ if ( m_maxCacheAge && ( age > m_maxCacheAge))
+ {
+ ok = false; // Expired
+ }
+ }
+
+ // Expiration Date
+ if (ok && (!fgets(buffer, 400, fs)))
+ ok = false;
+ if (ok)
+ {
+//WABA: It seems I slightly misunderstood the meaning of "Expire:" header.
+#if 0
+ time_t expireDate;
+ expireDate = (time_t) strtoul(buffer, 0, 10);
+ if (expireDate && (expireDate < currentDate))
+ ok = false; // Expired
+#endif
+ }
+
+ // ETag
+ if (ok && (!fgets(buffer, 400, fs)))
+ ok = false;
+ if (ok)
+ {
+ // Ignore ETag
+ }
+
+ // Last-Modified
+ if (ok && (!fgets(buffer, 400, fs)))
+ ok = false;
+ if (ok)
+ {
+ // Ignore Last-Modified
+ }
+
+
+ fclose(fs);
+ if (ok)
+ {
+ FileInfo *info = new FileInfo;
+ info->age = age;
+ return info;
+ }
+
+ unlink( CEF.data());
+ return 0;
+}
+// Keep the above in sync with the cache code in http.cc
+// !END OF SYNC!
+
+void scanDirectory(FileInfoList &fileEntries, const QString &name, const QString &strDir)
+{
+ QDir dir(strDir);
+ if (!dir.exists()) return;
+
+ QFileInfoList *newEntries = (QFileInfoList *) dir.entryInfoList();
+
+ if (!newEntries) return; // Directory not accessible ??
+
+ for(QFileInfo *qFileInfo = newEntries->first();
+ qFileInfo;
+ qFileInfo = newEntries->next())
+ {
+ if (qFileInfo->isFile())
+ {
+ FileInfo *fileInfo = readEntry( strDir + "/" + qFileInfo->fileName());
+ if (fileInfo)
+ {
+ fileInfo->name = name + "/" + qFileInfo->fileName();
+ fileInfo->size = (qFileInfo->size() + 1023) / 1024;
+ fileEntries.append(fileInfo);
+ }
+ }
+ }
+}
+
+extern "C" KDE_EXPORT int kdemain(int argc, char **argv)
+{
+ KLocale::setMainCatalogue("kdelibs");
+ KCmdLineArgs::init( argc, argv, appName,
+ I18N_NOOP("KDE HTTP cache maintenance tool"),
+ description, version, true);
+
+ KCmdLineArgs::addCmdLineOptions( options );
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+
+ bool deleteAll = args->isSet("clear-all");
+
+ KInstance ins( appName );
+
+ if (!deleteAll)
+ {
+ DCOPClient *dcop = new DCOPClient();
+ QCString name = dcop->registerAs(appName, false);
+ if (!name.isEmpty() && (name != appName))
+ {
+ fprintf(stderr, "%s: Already running! (%s)\n", appName, name.data());
+ return 0;
+ }
+ }
+
+ currentDate = time(0);
+ m_maxCacheAge = KProtocolManager::maxCacheAge();
+ m_maxCacheSize = KProtocolManager::maxCacheSize();
+
+ if (deleteAll)
+ m_maxCacheSize = -1;
+
+ QString strCacheDir = KGlobal::dirs()->saveLocation("cache", "http");
+
+ QDir cacheDir( strCacheDir );
+ if (!cacheDir.exists())
+ {
+ fprintf(stderr, "%s: '%s' does not exist.\n", appName, strCacheDir.ascii());
+ return 0;
+ }
+
+ QStringList dirs = cacheDir.entryList( );
+
+ FileInfoList cachedEntries;
+
+ for(QStringList::Iterator it = dirs.begin();
+ it != dirs.end();
+ it++)
+ {
+ if ((*it)[0] != '.')
+ {
+ scanDirectory( cachedEntries, *it, strCacheDir + "/" + *it);
+ }
+ }
+
+ cachedEntries.sort();
+
+ int maxCachedSize = m_maxCacheSize / 2;
+
+ for(FileInfo *fileInfo = cachedEntries.first();
+ fileInfo;
+ fileInfo = cachedEntries.next())
+ {
+ if (fileInfo->size > maxCachedSize)
+ {
+ QCString filename = QFile::encodeName( strCacheDir + "/" + fileInfo->name);
+ unlink(filename.data());
+// kdDebug () << appName << ": Object too big, deleting '" << filename.data() << "' (" << result<< ")" << endl;
+ }
+ }
+
+ int totalSize = 0;
+
+ for(FileInfo *fileInfo = cachedEntries.first();
+ fileInfo;
+ fileInfo = cachedEntries.next())
+ {
+ if ((totalSize + fileInfo->size) > m_maxCacheSize)
+ {
+ QCString filename = QFile::encodeName( strCacheDir + "/" + fileInfo->name);
+ unlink(filename.data());
+// kdDebug () << appName << ": Cache too big, deleting '" << filename.data() << "' (" << fileInfo->size << ")" << endl;
+ }
+ else
+ {
+ totalSize += fileInfo->size;
+// fprintf(stderr, "Keep in cache: %s %d %d total = %d\n", fileInfo->name.ascii(), fileInfo->size, fileInfo->age, totalSize);
+ }
+ }
+ kdDebug () << appName << ": Current size of cache = " << totalSize << " kB." << endl;
+ return 0;
+}
+
+
diff --git a/kioslave/http/http_cache_cleaner.desktop b/kioslave/http/http_cache_cleaner.desktop
new file mode 100644
index 000000000..a2e129977
--- /dev/null
+++ b/kioslave/http/http_cache_cleaner.desktop
@@ -0,0 +1,168 @@
+[Desktop Entry]
+Type=Service
+Name=HTTP Cache Cleaner
+Name[af]=Http Kas Skoonmaker
+Name[ar]=مزيل كاش HTTP
+Name[az]=HTTP Ön Yaddaş Təmizləyici
+Name[be]=Ачыстка кэшу HTTP
+Name[bg]=Изчистване на кеш-паметта на HTTP
+Name[bn]=এইচ-টি-টি-পি ক্যাশ ক্লীনার
+Name[br]=Naeter Krubuilh HTTP
+Name[bs]=Čistač HTTP cache-a
+Name[ca]=Neteja la memòria cau del HTTP
+Name[cs]=Nástroj pro vyprázdnění cache protokolu HTTP
+Name[csb]=Czëszczenié cache HTTP
+Name[cy]=Glanhauwr Storfa HTTP
+Name[da]=HTTP-cache-rydder
+Name[de]=Aufräumprogramm für den HTTP-Zwischenspeicher
+Name[el]=Καθαριστής λανθάνουσας μνήμης HTTP
+Name[eo]=HTTP-Tenejpurigilo
+Name[es]=Limpiador del caché de HTTP
+Name[et]=HTTP vahemälu puhastaja
+Name[eu]=HTTP cache-garbitzailea
+Name[fa]=پاک‌کنندۀ نهانگاه قام
+Name[fi]=HTTP-välimuistin tyhjentäjä
+Name[fr]=Nettoyage du cache HTTP
+Name[fy]=HTTP Cache oprommer
+Name[ga]=Glantóir Taisce HTTP
+Name[gl]=Limpador da caché de HTTP
+Name[he]=מנקה מטמון ה־HTTP
+Name[hi]=HTTP कैश साफ करने वाला
+Name[hr]=Brisanje HTTP pohrane
+Name[hu]=HTTP gyorstártisztító
+Name[id]=Pembersih Cache HTTP
+Name[is]=Hreinsiforrit HTTP skyndiminnis
+Name[it]=Ripulitore della cache HTTP
+Name[ja]=HTTP キャッシュマネージャ
+Name[ka]=HTTP ბუფერის გასუფთავება
+Name[kk]=HTTP бүркемесін босату
+Name[km]=កម្មវិធី​សម្អាត​ឃ្លាំង​សម្ងាត់ HTTP
+Name[ko]=HTTP 캐시 정리
+Name[lb]=Opraumer fir den HTTP-Zwëschespäicher
+Name[lt]=HTTP krepšio ištuštintojas
+Name[lv]=HTTP Kešatmiņas tīrītājs
+Name[mk]=Бришење на HTTP-кешот
+Name[mn]=HTTP-завсрын хадгалагчийн цэвэрлэгээ
+Name[ms]=Pembersih Penyimpan HTTP
+Name[mt]=Tindif tal-cache HTTP
+Name[nb]=HTTP Mellomlagerrenser
+Name[nds]=Reenmaker för HTTP-Twischenspieker
+Name[ne]=HTTP क्यास क्लीनर
+Name[nl]=HTTP Cache opschonen
+Name[nn]=HTTP-mellomlageropprensking
+Name[nso]=Sehlwekisi sa Polokelo ya HTTP
+Name[oc]=Netejador de cabia HTTP
+Name[pa]=HTTP ਕੈਂਚੇ ਸਾਫ਼
+Name[pl]=Czyszczenie bufora HTTP
+Name[pt]=Limpeza da Cache de HTTP
+Name[pt_BR]=Limpador de cache HTTP
+Name[ro]=Curăţător cache HTTP
+Name[ru]=Очистка кэша HTTP
+Name[rw]=Musukura Ubwihisho HTTP
+Name[se]=HTTP gaskarádjosa buhtisteaddji
+Name[sk]=Čistič vyrovnávacej pamäti HTTP
+Name[sl]=Čistilnik predpomnilnika HTTP
+Name[sq]=Pastrues për Depon e Fshehtësitëve të HTTP
+Name[sr]=Чистач HTTP кеша
+Name[sr@Latn]=Čistač HTTP keša
+Name[sv]=HTTP-cacherensare
+Name[ta]=HTTP தற்காலிக நினைவகத்தை சுத்தம் செய்தல்
+Name[te]=హెచ్ టిటిపి కోశం శుభ్రంచేసేది
+Name[tg]=HTTP Софкунаки Махфӣ
+Name[th]=ตัวล้างแคช HTTP
+Name[tr]=HTTP Önbellek Temizleyici
+Name[tt]=HTTP Alxäteren Buşatqıç
+Name[uk]=Очищувач кешу HTTP
+Name[uz]=HTTP kesh boʻshatgich
+Name[uz@cyrillic]=HTTP кэш бўшатгич
+Name[ven]=Tshikulumagi tsha HTTP Cache
+Name[vi]=Bộ làm sạch bộ nhớ tạm HTTP
+Name[xh]=Umcoci wendawo efihlakeleyo yokugcina we HTTP
+Name[zh_CN]=HTTP 缓存清除程序
+Name[zh_HK]=HTTP 快取清除程式
+Name[zh_TW]=HTTP 快取清除程式
+Name[zu]=Umhlanzi we-Cache ye-HTTP
+Exec=kio_http_cache_cleaner
+Comment=Cleans up old entries from the HTTP cache
+Comment[af]=Skoonmaak begin ou inskrywings van die Http kas
+Comment[ar]=يزيل المداخل القديمة من كاش HTTP
+Comment[az]=HTTP ön yaddaşından köhnə girişləri silər
+Comment[be]=Выдаляе старыя запісы з кэшу HTTP
+Comment[bg]=Изчистване на старите данни в кеш-паметта на HTTP
+Comment[bn]=HTTP ক্যাশ থেকে পুরনো তথ্য মুছে ফেলে
+Comment[br]=Skarañ enmontoù kozh diwar ar grubuilh HTTP
+Comment[bs]=Čisti stare datoteke iz HTTP cache-a
+Comment[ca]=Neteja les entrades antigues de la memòria cau del HTTP
+Comment[cs]=Odstraňuje staré položky z HTTP cache
+Comment[csb]=Rëmô stôré wpisënczi z cache HTTP
+Comment[cy]=Glanhau'r hen gofnodion o'r storfa HTTP
+Comment[da]=Rydder op i gamle indgange fra HTTP-cachen
+Comment[de]=Löscht alte Einträge aus dem HTTP-Zwischenspeicher
+Comment[el]=Καθαρίζει παλιές καταχωρήσεις από τη λανθάνουσα μνήμη HTTP
+Comment[eo]=Forigas malnovajn erojn el HTTP-tenejo
+Comment[es]=Elimina entradas antiguas del caché de HTTP
+Comment[et]=Puhastab HTTP vahemälu vanadest kirjetest
+Comment[eu]=HTTP cachearen sarrera zaharrak garbitzen ditu
+Comment[fa]=مدخلهای قدیمی را از نهانگاه قام پاک می‌کند
+Comment[fi]=Puhdistaa vanhat tiedot HTTP-välimuistista
+Comment[fr]=Efface les anciennes entrées du cache HTTP
+Comment[fy]=Ferwidert âlde items út de HTTP-cache
+Comment[ga]=Glanann seaniontrálacha ón taisce HTTP
+Comment[gl]=Elimina as entradas antigas da caché de HTTP
+Comment[he]=מנקה רשומות ישנות ממטמון ה־HTTP
+Comment[hi]=HTTP कैश से पुरानी प्रविष्टि साफ करे
+Comment[hr]=Uklanjanje starih datoteka iz HTTP privremene lokalne pohrane
+Comment[hu]=Kitörli a régi bejegyzéseket a HTTP gyorstárból
+Comment[id]=Membersihkan entri lama dari cache HTTP
+Comment[is]=Hreinsar gamlar færslur úr HTTP skyndiminninu
+Comment[it]=Ripulisce la cache HTTP dalle voci vecchie
+Comment[ja]=HTTP キャッシュから古いエントリを削除します
+Comment[ka]=HTTP ბუფერის მოძველებელი ელემენტების
+Comment[kk]=HTTP бүркемесін ескі жазулардан тазалау
+Comment[km]=សម្អាត​ធាតុ​ចាស់ៗ​ពី​ឃ្លាំង​សម្ងាត់ HTTP
+Comment[ko]=HTTP 캐시에서 오래된 것들을 정리합니다
+Comment[lb]=Entfernt al Entréen aus dem HTTP-Zwëschespäicher
+Comment[lt]=Išvalo senus įrašus iš HTTP krepšio
+Comment[lv]=Iztīra vecos ierakstus no HTTP kešatmiņas
+Comment[mk]=Ги брише старите работи од HTTP кешот
+Comment[mn]=HTTP-завсрын хадгалагчаас хуучин бичлэгийг устгах
+Comment[ms]=Membersihkan masukan lama daripada penyimpan HTTP
+Comment[mt]=Ineħħi fajls antiki mill-cache tal-HTTP
+Comment[nb]=Fjerner gamle oppføringer fra hurtiglageret for HTTP
+Comment[nds]=Smitt ole Indrääg ut den HTTP-Twischenspieker rut
+Comment[ne]=HTTP क्यासबाट पुराना प्रविष्टिहरू सफा गर्दछ
+Comment[nl]=Verwijdert oude items uit de HTTP-cache
+Comment[nn]=Reinskar opp i gamle oppføringar i HTTP-mellomlageret
+Comment[nso]=E hlwekisa ditsenyo tsa kgale gotswa polokelong ya HTTP
+Comment[oc]=Neteja les entrades antigues dèu cabia HTTP
+Comment[pa]=HTTP ਕੈਂਚੇ ਤੋਂ ਪੁਰਾਣੀਆਂ ਇਕਾਈਆਂ ਸਾਫ
+Comment[pl]=Usuwa stare wpisy z bufora HTTP
+Comment[pt]=Limpa o conteúdo desactualizado da cache do HTTP
+Comment[pt_BR]=Limpa itens velhos do cache HTTP
+Comment[ro]=Elimină înregistrările vechi din cache-ul HTTP
+Comment[ru]=Удаление устаревших элементов из кэша HTTP
+Comment[rw]=Isukura ibyinjijwe bishaje biri mu bwihisho HTTP
+Comment[se]=Buhtista boares merko3/4iid HTTP gaskarádjosis
+Comment[sk]=Vyčistiť staré záznamy z vyrovnávacej pamäti HTTP
+Comment[sl]=Zbriše stare vnose iz pomnilnika HTTP
+Comment[sq]=I pastron hyrjet e vjetra nga depoja e fshehtësive të HTTP
+Comment[sr]=Чисти старе ставке из HTTP кеша
+Comment[sr@Latn]=Čisti stare stavke iz HTTP keša
+Comment[sv]=Rensar bort gamla poster från HTTP-cachen
+Comment[ta]=HTTP நினைவத்திலிருந்து பழைய உள்ளீடுகளை சுத்தம் செய்கிறது
+Comment[te]=హెచ్ టిటిపి కోశం నుంచి పాత ఆరొపములను శుభ్రం చేసేది
+Comment[tg]=Ёддоштҳои Кӯҳна аз HTTP Махфӣ Тоза Кунед
+Comment[th]=ล้างรายการเก่าๆ จากแคช HTTP
+Comment[tr]=HTTP önbelleğinden eski girişleri siler
+Comment[tt]=HTTP alxäterendä bulğan iske keremnär beterä
+Comment[uk]=Вичищає старі елементи з кешу HTTP
+Comment[uz]=HTTP keshidagi eski elementlarni oʻchiradi
+Comment[uz@cyrillic]=HTTP кэшидаги эски элементларни ўчиради
+Comment[ven]=I kulumaga zwithu zwakale u bva kha HTTP cache
+Comment[vi]=Xoá sạch các mục nhập cũ ra bộ nhớ tạm HTTP.
+Comment[xh]=Icoca amangeno amadala asuka kwindawo efihlakeleyo yokugcina ye HTTP
+Comment[zh_CN]=从 HTTP 缓存中清除旧条目
+Comment[zh_HK]=從 HTTP 快取中清除舊的項目
+Comment[zh_TW]=從 HTTP 快取中清除舊的項目
+Comment[zu]=Ihlanza izingeniso ezindalam ezisuka kwi-cache ye-HTTP
+X-KDE-StartupNotify=false
diff --git a/kioslave/http/https.protocol b/kioslave/http/https.protocol
new file mode 100644
index 000000000..8a9c2f0da
--- /dev/null
+++ b/kioslave/http/https.protocol
@@ -0,0 +1,12 @@
+[Protocol]
+exec=kio_http
+protocol=https
+input=none
+output=filesystem
+reading=true
+defaultMimetype=application/octet-stream
+determineMimetypeFromExtension=false
+Icon=www
+config=http
+DocPath=kioslave/https.html
+Class=:internet
diff --git a/kioslave/http/kcookiejar/Makefile.am b/kioslave/http/kcookiejar/Makefile.am
new file mode 100644
index 000000000..933de5e13
--- /dev/null
+++ b/kioslave/http/kcookiejar/Makefile.am
@@ -0,0 +1,31 @@
+# Makefile.am of kdebase/kioslave/http
+
+SUBDIRS=tests
+INCLUDES= $(all_includes)
+
+####### Files
+
+bin_PROGRAMS =
+lib_LTLIBRARIES =
+kdeinit_LTLIBRARIES = kcookiejar.la
+kde_module_LTLIBRARIES = kded_kcookiejar.la
+
+kcookiejar_la_SOURCES = main.cpp
+METASOURCES = AUTO
+kcookiejar_la_LDFLAGS = $(all_libraries) -module -avoid-version
+kcookiejar_la_LIBADD = $(LIB_KDECORE)
+
+kded_kcookiejar_la_SOURCES = kcookiejar.cpp kcookieserver.cpp \
+ kcookieserver.skel kcookiewin.cpp
+kded_kcookiejar_la_LDFLAGS = $(all_libraries) -module -avoid-version
+kded_kcookiejar_la_LIBADD = $(LIB_KIO) $(LIB_KDED)
+
+kded_DATA = kcookiejar.desktop
+kdeddir = $(kde_servicesdir)/kded
+
+update_DATA = kcookiescfg.upd
+updatedir = $(kde_datadir)/kconf_update
+
+cookie_DATA = domain_info
+cookiedir = $(kde_datadir)/khtml
+
diff --git a/kioslave/http/kcookiejar/domain_info b/kioslave/http/kcookiejar/domain_info
new file mode 100644
index 000000000..94baf8dae
--- /dev/null
+++ b/kioslave/http/kcookiejar/domain_info
@@ -0,0 +1 @@
+twoLevelTLD=name,ai,au,bd,bh,ck,eg,et,fk,il,in,kh,kr,mk,mt,na,np,nz,pg,pk,qa,sa,sb,sg,sv,ua,ug,uk,uy,vn,za,zw
diff --git a/kioslave/http/kcookiejar/kcookiejar.cpp b/kioslave/http/kcookiejar/kcookiejar.cpp
new file mode 100644
index 000000000..5b5f78f6b
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookiejar.cpp
@@ -0,0 +1,1558 @@
+/* This file is part of the KDE File Manager
+
+ Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org)
+ Copyright (C) 2000,2001 Dawit Alemayehu (adawit@kde.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, and/or sell copies of the
+ Software, and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+//----------------------------------------------------------------------------
+//
+// KDE File Manager -- HTTP Cookies
+// $Id$
+
+//
+// The cookie protocol is a mess. RFC2109 is a joke since nobody seems to
+// use it. Apart from that it is badly written.
+// We try to implement Netscape Cookies and try to behave us according to
+// RFC2109 as much as we can.
+//
+// We assume cookies do not contain any spaces (Netscape spec.)
+// According to RFC2109 this is allowed though.
+//
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef USE_SOLARIS
+#include <strings.h>
+#endif
+
+#include <stdlib.h>
+
+//#include <netinet/in.h>
+//#include <arpa/inet.h>
+
+#include <qstring.h>
+#include <qstrlist.h>
+#include <qptrlist.h>
+#include <qptrdict.h>
+#include <qfile.h>
+#include <qdir.h>
+#include <qregexp.h>
+
+#include <kurl.h>
+#include <krfcdate.h>
+#include <kconfig.h>
+#include <ksavefile.h>
+#include <kdebug.h>
+
+#include "kcookiejar.h"
+
+
+// BR87227
+// Waba: Should the number of cookies be limited?
+// I am not convinced of the need of such limit
+// Mozilla seems to limit to 20 cookies / domain
+// but it is unclear which policy it uses to expire
+// cookies when it exceeds that amount
+#undef MAX_COOKIE_LIMIT
+
+#define MAX_COOKIES_PER_HOST 25
+#define READ_BUFFER_SIZE 8192
+#define IP_ADDRESS_EXPRESSION "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
+
+// Note with respect to QString::fromLatin1( )
+// Cookies are stored as 8 bit data and passed to kio_http as
+// latin1 regardless of their actual encoding.
+
+// L1 is used to indicate latin1 constants
+#define L1(x) QString::fromLatin1(x)
+
+template class QPtrList<KHttpCookie>;
+template class QPtrDict<KHttpCookieList>;
+
+QString KCookieJar::adviceToStr(KCookieAdvice _advice)
+{
+ switch( _advice )
+ {
+ case KCookieAccept: return L1("Accept");
+ case KCookieReject: return L1("Reject");
+ case KCookieAsk: return L1("Ask");
+ default: return L1("Dunno");
+ }
+}
+
+KCookieAdvice KCookieJar::strToAdvice(const QString &_str)
+{
+ if (_str.isEmpty())
+ return KCookieDunno;
+
+ QCString advice = _str.lower().latin1();
+
+ if (advice == "accept")
+ return KCookieAccept;
+ else if (advice == "reject")
+ return KCookieReject;
+ else if (advice == "ask")
+ return KCookieAsk;
+
+ return KCookieDunno;
+}
+
+// KHttpCookie
+///////////////////////////////////////////////////////////////////////////
+
+//
+// Cookie constructor
+//
+KHttpCookie::KHttpCookie(const QString &_host,
+ const QString &_domain,
+ const QString &_path,
+ const QString &_name,
+ const QString &_value,
+ time_t _expireDate,
+ int _protocolVersion,
+ bool _secure,
+ bool _httpOnly,
+ bool _explicitPath) :
+ mHost(_host),
+ mDomain(_domain),
+ mPath(_path.isEmpty() ? QString::null : _path),
+ mName(_name),
+ mValue(_value),
+ mExpireDate(_expireDate),
+ mProtocolVersion(_protocolVersion),
+ mSecure(_secure),
+ mHttpOnly(_httpOnly),
+ mExplicitPath(_explicitPath)
+{
+}
+
+//
+// Checks if a cookie has been expired
+//
+bool KHttpCookie::isExpired(time_t currentDate)
+{
+ return (mExpireDate != 0) && (mExpireDate < currentDate);
+}
+
+//
+// Returns a string for a HTTP-header
+//
+QString KHttpCookie::cookieStr(bool useDOMFormat)
+{
+ QString result;
+
+ if (useDOMFormat || (mProtocolVersion == 0))
+ {
+ if ( !mName.isEmpty() )
+ result = mName + '=';
+ result += mValue;
+ }
+ else
+ {
+ result = mName + '=' + mValue;
+ if (mExplicitPath)
+ result += L1("; $Path=\"") + mPath + L1("\"");
+ if (!mDomain.isEmpty())
+ result += L1("; $Domain=\"") + mDomain + L1("\"");
+ }
+ return result;
+}
+
+//
+// Returns whether this cookie should be send to this location.
+bool KHttpCookie::match(const QString &fqdn, const QStringList &domains,
+ const QString &path)
+{
+ // Cookie domain match check
+ if (mDomain.isEmpty())
+ {
+ if (fqdn != mHost)
+ return false;
+ }
+ else if (!domains.contains(mDomain))
+ {
+ if (mDomain[0] == '.')
+ return false;
+
+ // Maybe the domain needs an extra dot.
+ QString domain = '.' + mDomain;
+ if ( !domains.contains( domain ) )
+ if ( fqdn != mDomain )
+ return false;
+ }
+
+ // Cookie path match check
+ if (mPath.isEmpty())
+ return true;
+
+ // According to the netscape spec both http://www.acme.com/foobar,
+ // http://www.acme.com/foo.bar and http://www.acme.com/foo/bar
+ // match http://www.acme.com/foo.
+ // We only match http://www.acme.com/foo/bar
+
+ if( path.startsWith(mPath) &&
+ (
+ (path.length() == mPath.length() ) || // Paths are exact match
+ (path[mPath.length()-1] == '/') || // mPath ended with a slash
+ (path[mPath.length()] == '/') // A slash follows.
+ ))
+ return true; // Path of URL starts with cookie-path
+
+ return false;
+}
+
+// KHttpCookieList
+///////////////////////////////////////////////////////////////////////////
+
+int KHttpCookieList::compareItems( void * item1, void * item2)
+{
+ int pathLen1 = ((KHttpCookie *)item1)->path().length();
+ int pathLen2 = ((KHttpCookie *)item2)->path().length();
+ if (pathLen1 > pathLen2)
+ return -1;
+ if (pathLen1 < pathLen2)
+ return 1;
+ return 0;
+}
+
+
+// KCookieJar
+///////////////////////////////////////////////////////////////////////////
+
+//
+// Constructs a new cookie jar
+//
+// One jar should be enough for all cookies.
+//
+KCookieJar::KCookieJar()
+{
+ m_cookieDomains.setAutoDelete( true );
+ m_globalAdvice = KCookieDunno;
+ m_configChanged = false;
+ m_cookiesChanged = false;
+
+ KConfig cfg("khtml/domain_info", true, false, "data");
+ QStringList countries = cfg.readListEntry("twoLevelTLD");
+ for(QStringList::ConstIterator it = countries.begin();
+ it != countries.end(); ++it)
+ {
+ m_twoLevelTLD.replace(*it, (int *) 1);
+ }
+}
+
+//
+// Destructs the cookie jar
+//
+// Poor little cookies, they will all be eaten by the cookie monster!
+//
+KCookieJar::~KCookieJar()
+{
+ // Not much to do here
+}
+
+static void removeDuplicateFromList(KHttpCookieList *list, KHttpCookie *cookiePtr, bool nameMatchOnly=false, bool updateWindowId=false)
+{
+ QString domain1 = cookiePtr->domain();
+ if (domain1.isEmpty())
+ domain1 = cookiePtr->host();
+
+ for ( KHttpCookiePtr cookie=list->first(); cookie != 0; )
+ {
+ QString domain2 = cookie->domain();
+ if (domain2.isEmpty())
+ domain2 = cookie->host();
+
+ if (
+ (cookiePtr->name() == cookie->name()) &&
+ (
+ nameMatchOnly ||
+ ( (domain1 == domain2) && (cookiePtr->path() == cookie->path()) )
+ )
+ )
+ {
+ if (updateWindowId)
+ {
+ for(QValueList<long>::ConstIterator it = cookie->windowIds().begin();
+ it != cookie->windowIds().end(); ++it)
+ {
+ long windowId = *it;
+ if (windowId && (cookiePtr->windowIds().find(windowId) == cookiePtr->windowIds().end()))
+ {
+ cookiePtr->windowIds().append(windowId);
+ }
+ }
+ }
+ KHttpCookiePtr old_cookie = cookie;
+ cookie = list->next();
+ list->removeRef( old_cookie );
+ break;
+ }
+ else
+ {
+ cookie = list->next();
+ }
+ }
+}
+
+
+//
+// Looks for cookies in the cookie jar which are appropriate for _url.
+// Returned is a string containing all appropriate cookies in a format
+// which can be added to a HTTP-header without any additional processing.
+//
+QString KCookieJar::findCookies(const QString &_url, bool useDOMFormat, long windowId, KHttpCookieList *pendingCookies)
+{
+ QString cookieStr;
+ QStringList domains;
+ QString fqdn;
+ QString path;
+ KHttpCookiePtr cookie;
+ KCookieAdvice advice = m_globalAdvice;
+
+ if (!parseURL(_url, fqdn, path))
+ return cookieStr;
+
+ bool secureRequest = (_url.find( L1("https://"), 0, false) == 0 ||
+ _url.find( L1("webdavs://"), 0, false) == 0);
+
+ // kdDebug(7104) << "findCookies: URL= " << _url << ", secure = " << secureRequest << endl;
+
+ extractDomains(fqdn, domains);
+
+ KHttpCookieList allCookies;
+
+ for(QStringList::ConstIterator it = domains.begin();
+ true;
+ ++it)
+ {
+ KHttpCookieList *cookieList;
+ if (it == domains.end())
+ {
+ cookieList = pendingCookies; // Add pending cookies
+ pendingCookies = 0;
+ if (!cookieList)
+ break;
+ }
+ else
+ {
+ QString key = (*it).isNull() ? L1("") : (*it);
+ cookieList = m_cookieDomains[key];
+ if (!cookieList)
+ continue; // No cookies for this domain
+ }
+
+ if (cookieList->getAdvice() != KCookieDunno)
+ advice = cookieList->getAdvice();
+
+ for ( cookie=cookieList->first(); cookie != 0; cookie=cookieList->next() )
+ {
+ // If the we are setup to automatically accept all session cookies and to
+ // treat all cookies as session cookies or the current cookie is a session
+ // cookie, then send the cookie back regardless of either policy.
+ if (advice == KCookieReject &&
+ !(m_autoAcceptSessionCookies &&
+ (m_ignoreCookieExpirationDate || cookie->expireDate() == 0)))
+ continue;
+
+ if (!cookie->match(fqdn, domains, path))
+ continue;
+
+ if( cookie->isSecure() && !secureRequest )
+ continue;
+
+ if( cookie->isHttpOnly() && useDOMFormat )
+ continue;
+
+ // Do not send expired cookies.
+ if ( cookie->isExpired (time(0)) )
+ {
+ // Note there is no need to actually delete the cookie here
+ // since the cookieserver will invoke ::saveCookieJar because
+ // of the state change below. This will then do the job of
+ // deleting the cookie for us.
+ m_cookiesChanged = true;
+ continue;
+ }
+
+ if (windowId && (cookie->windowIds().find(windowId) == cookie->windowIds().end()))
+ {
+ cookie->windowIds().append(windowId);
+ }
+
+ if (it == domains.end()) // Only needed when processing pending cookies
+ removeDuplicateFromList(&allCookies, cookie);
+
+ allCookies.append(cookie);
+ }
+ if (it == domains.end())
+ break; // Finished.
+ }
+
+ int cookieCount = 0;
+
+ int protVersion=0;
+ for ( cookie=allCookies.first(); cookie != 0; cookie=allCookies.next() )
+ {
+ if (cookie->protocolVersion() > protVersion)
+ protVersion = cookie->protocolVersion();
+ }
+
+ for ( cookie=allCookies.first(); cookie != 0; cookie=allCookies.next() )
+ {
+ if (useDOMFormat)
+ {
+ if (cookieCount > 0)
+ cookieStr += L1("; ");
+ cookieStr += cookie->cookieStr(true);
+ }
+ else
+ {
+ if (cookieCount == 0)
+ {
+ cookieStr += L1("Cookie: ");
+ if (protVersion > 0)
+ {
+ QString version;
+ version.sprintf("$Version=%d; ", protVersion); // Without quotes
+ cookieStr += version;
+ }
+ }
+ else
+ {
+ cookieStr += L1("; ");
+ }
+ cookieStr += cookie->cookieStr(false);
+ }
+ cookieCount++;
+ }
+
+ return cookieStr;
+}
+
+//
+// This function parses a string like 'my_name="my_value";' and returns
+// 'my_name' in Name and 'my_value' in Value.
+//
+// A pointer to the end of the parsed part is returned.
+// This pointer points either to:
+// '\0' - The end of the string has reached.
+// ';' - Another my_name="my_value" pair follows
+// ',' - Another cookie follows
+// '\n' - Another header follows
+static const char * parseNameValue(const char *header,
+ QString &Name,
+ QString &Value,
+ bool keepQuotes=false,
+ bool rfcQuotes=false)
+{
+ const char *s = header;
+ // Parse 'my_name' part
+ for(; (*s != '='); s++)
+ {
+ if ((*s=='\0') || (*s==';') || (*s=='\n'))
+ {
+ // No '=' sign -> use string as the value, name is empty
+ // (behavior found in Mozilla and IE)
+ Name = "";
+ Value = QString::fromLatin1(header);
+ Value.truncate( s - header );
+ Value = Value.stripWhiteSpace();
+ return (s);
+ }
+ }
+
+ Name = header;
+ Name.truncate( s - header );
+ Name = Name.stripWhiteSpace();
+
+ // *s == '='
+ s++;
+
+ // Skip any whitespace
+ for(; (*s == ' ') || (*s == '\t'); s++)
+ {
+ if ((*s=='\0') || (*s==';') || (*s=='\n'))
+ {
+ // End of Name
+ Value = "";
+ return (s);
+ }
+ }
+
+ if ((rfcQuotes || !keepQuotes) && (*s == '\"'))
+ {
+ // Parse '"my_value"' part (quoted value)
+ if (keepQuotes)
+ header = s++;
+ else
+ header = ++s; // skip "
+ for(;(*s != '\"');s++)
+ {
+ if ((*s=='\0') || (*s=='\n'))
+ {
+ // End of Name
+ Value = QString::fromLatin1(header);
+ Value.truncate(s - header);
+ return (s);
+ }
+ }
+ Value = QString::fromLatin1(header);
+ // *s == '\"';
+ if (keepQuotes)
+ Value.truncate( ++s - header );
+ else
+ Value.truncate( s++ - header );
+
+ // Skip any remaining garbage
+ for(;; s++)
+ {
+ if ((*s=='\0') || (*s==';') || (*s=='\n'))
+ break;
+ }
+ }
+ else
+ {
+ // Parse 'my_value' part (unquoted value)
+ header = s;
+ while ((*s != '\0') && (*s != ';') && (*s != '\n'))
+ s++;
+ // End of Name
+ Value = QString::fromLatin1(header);
+ Value.truncate( s - header );
+ Value = Value.stripWhiteSpace();
+ }
+ return (s);
+
+}
+
+void KCookieJar::stripDomain(const QString &_fqdn, QString &_domain)
+{
+ QStringList domains;
+ extractDomains(_fqdn, domains);
+ if (domains.count() > 3)
+ _domain = domains[3];
+ else
+ _domain = domains[0];
+}
+
+QString KCookieJar::stripDomain( KHttpCookiePtr cookiePtr)
+{
+ QString domain; // We file the cookie under this domain.
+ if (cookiePtr->domain().isEmpty())
+ stripDomain( cookiePtr->host(), domain);
+ else
+ stripDomain (cookiePtr->domain(), domain);
+ return domain;
+}
+
+bool KCookieJar::parseURL(const QString &_url,
+ QString &_fqdn,
+ QString &_path)
+{
+ KURL kurl(_url);
+ if (!kurl.isValid())
+ return false;
+
+ _fqdn = kurl.host().lower();
+ if (kurl.port())
+ {
+ if (((kurl.protocol() == L1("http")) && (kurl.port() != 80)) ||
+ ((kurl.protocol() == L1("https")) && (kurl.port() != 443)))
+ {
+ _fqdn = L1("%1:%2").arg(kurl.port()).arg(_fqdn);
+ }
+ }
+
+ // Cookie spoofing protection. Since there is no way a path separator
+ // or escape encoded character is allowed in the hostname according
+ // to RFC 2396, reject attempts to include such things there!
+ if(_fqdn.find('/') > -1 || _fqdn.find('%') > -1)
+ {
+ return false; // deny everything!!
+ }
+
+ _path = kurl.path();
+ if (_path.isEmpty())
+ _path = L1("/");
+
+ QRegExp exp(L1("[\\\\/]\\.\\.[\\\\/]"));
+ // Weird path, cookie stealing attempt?
+ if (exp.search(_path) != -1)
+ return false; // Deny everything!!
+
+ return true;
+}
+
+void KCookieJar::extractDomains(const QString &_fqdn,
+ QStringList &_domains)
+{
+ // Return numeric IPv6 addresses as is...
+ if (_fqdn[0] == '[')
+ {
+ _domains.append( _fqdn );
+ return;
+ }
+ // Return numeric IPv4 addresses as is...
+ if ((_fqdn[0] >= '0') && (_fqdn[0] <= '9'))
+ {
+ if (_fqdn.find(QRegExp(IP_ADDRESS_EXPRESSION)) > -1)
+ {
+ _domains.append( _fqdn );
+ return;
+ }
+ }
+
+ QStringList partList = QStringList::split('.', _fqdn, false);
+
+ if (partList.count())
+ partList.remove(partList.begin()); // Remove hostname
+
+ while(partList.count())
+ {
+
+ if (partList.count() == 1)
+ break; // We only have a TLD left.
+
+ if ((partList.count() == 2) && (m_twoLevelTLD[partList[1].lower()]))
+ {
+ // This domain uses two-level TLDs in the form xxxx.yy
+ break;
+ }
+
+ if ((partList.count() == 2) && (partList[1].length() == 2))
+ {
+ // If this is a TLD, we should stop. (e.g. co.uk)
+ // We assume this is a TLD if it ends with .xx.yy or .x.yy
+ if (partList[0].length() <= 2)
+ break; // This is a TLD.
+
+ // Catch some TLDs that we miss with the previous check
+ // e.g. com.au, org.uk, mil.co
+ QCString t = partList[0].lower().utf8();
+ if ((t == "com") || (t == "net") || (t == "org") || (t == "gov") || (t == "edu") || (t == "mil") || (t == "int"))
+ break;
+ }
+
+ QString domain = partList.join(L1("."));
+ _domains.append(domain);
+ _domains.append('.' + domain);
+ partList.remove(partList.begin()); // Remove part
+ }
+
+ // Always add the FQDN at the start of the list for
+ // hostname == cookie-domainname checks!
+ _domains.prepend( '.' + _fqdn );
+ _domains.prepend( _fqdn );
+}
+
+
+/*
+ Changes dates in from the following format
+
+ Wed Sep 12 07:00:00 2007 GMT
+ to
+ Wed Sep 12 2007 07:00:00 GMT
+
+ to allow KRFCDate::parseDate to properly parse expiration date formats
+ used in cookies by some servers such as amazon.com. See BR# 145244.
+*/
+static QString fixupDateTime(const QString& dt)
+{
+ const int index = dt.find(QRegExp("[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}"));
+
+ if (index > -1)
+ {
+ QStringList dateStrList = QStringList::split(' ', dt.mid(index));
+ if (dateStrList.count() > 1)
+ {
+ QString date = dateStrList[0];
+ dateStrList[0] = dateStrList[1];
+ dateStrList[1] = date;
+ date = dt;
+ return date.replace(index, date.length(), dateStrList.join(" "));
+ }
+ }
+
+ return dt;
+}
+
+//
+// This function parses cookie_headers and returns a linked list of
+// KHttpCookie objects for all cookies found in cookie_headers.
+// If no cookies could be found 0 is returned.
+//
+// cookie_headers should be a concatenation of all lines of a HTTP-header
+// which start with "Set-Cookie". The lines should be separated by '\n's.
+//
+KHttpCookieList KCookieJar::makeCookies(const QString &_url,
+ const QCString &cookie_headers,
+ long windowId)
+{
+ KHttpCookieList cookieList;
+ KHttpCookieList cookieList2;
+ KHttpCookiePtr lastCookie = 0;
+ const char *cookieStr = cookie_headers.data();
+ QString Name;
+ QString Value;
+ QString fqdn;
+ QString path;
+ bool crossDomain = false;
+
+ if (!parseURL(_url, fqdn, path))
+ {
+ // Error parsing _url
+ return KHttpCookieList();
+ }
+ QString defaultPath;
+ int i = path.findRev('/');
+ if (i > 0)
+ defaultPath = path.left(i);
+
+ // The hard stuff :)
+ for(;;)
+ {
+ // check for "Set-Cookie"
+ if (strncmp(cookieStr, "Cross-Domain\n", 13) == 0)
+ {
+ cookieStr += 13;
+ crossDomain = true;
+ }
+ else if (strncasecmp(cookieStr, "Set-Cookie:", 11) == 0)
+ {
+ cookieStr = parseNameValue(cookieStr+11, Name, Value, true);
+
+ // Host = FQDN
+ // Default domain = ""
+ // Default path according to rfc2109
+
+ KHttpCookie *cookie = new KHttpCookie(fqdn, L1(""), defaultPath, Name, Value);
+ if (windowId)
+ cookie->mWindowIds.append(windowId);
+ cookie->mCrossDomain = crossDomain;
+
+ // Insert cookie in chain
+ cookieList.append(cookie);
+ lastCookie = cookie;
+ }
+ else if (strncasecmp(cookieStr, "Set-Cookie2:", 12) == 0)
+ {
+ // Attempt to follow rfc2965
+ cookieStr = parseNameValue(cookieStr+12, Name, Value, true, true);
+
+ // Host = FQDN
+ // Default domain = ""
+ // Default path according to rfc2965
+
+ KHttpCookie *cookie = new KHttpCookie(fqdn, L1(""), defaultPath, Name, Value);
+ if (windowId)
+ cookie->mWindowIds.append(windowId);
+ cookie->mCrossDomain = crossDomain;
+
+ // Insert cookie in chain
+ cookieList2.append(cookie);
+ lastCookie = cookie;
+ }
+ else
+ {
+ // This is not the start of a cookie header, skip till next line.
+ while (*cookieStr && *cookieStr != '\n')
+ cookieStr++;
+
+ if (*cookieStr == '\n')
+ cookieStr++;
+
+ if (!*cookieStr)
+ break; // End of cookie_headers
+ else
+ continue; // end of this header, continue with next.
+ }
+
+ while ((*cookieStr == ';') || (*cookieStr == ' '))
+ {
+ cookieStr++;
+
+ // Name-Value pair follows
+ cookieStr = parseNameValue(cookieStr, Name, Value);
+
+ QCString cName = Name.lower().latin1();
+ if (cName == "domain")
+ {
+ QString dom = Value.lower();
+ // RFC2965 3.2.2: If an explicitly specified value does not
+ // start with a dot, the user agent supplies a leading dot
+ if(dom.length() && dom[0] != '.')
+ dom.prepend(".");
+ // remove a trailing dot
+ if(dom.length() > 2 && dom[dom.length()-1] == '.')
+ dom = dom.left(dom.length()-1);
+
+ if(dom.contains('.') > 1 || dom == ".local")
+ lastCookie->mDomain = dom;
+ }
+ else if (cName == "max-age")
+ {
+ int max_age = Value.toInt();
+ if (max_age == 0)
+ lastCookie->mExpireDate = 1;
+ else
+ lastCookie->mExpireDate = time(0)+max_age;
+ }
+ else if (cName == "expires")
+ {
+ // Parse brain-dead netscape cookie-format
+ lastCookie->mExpireDate = KRFCDate::parseDate(Value);
+
+ // Workaround for servers that send the expiration date in
+ // 'Wed Sep 12 07:00:00 2007 GMT' format. See BR# 145244.
+ if (lastCookie->mExpireDate == 0)
+ lastCookie->mExpireDate = KRFCDate::parseDate(fixupDateTime(Value));
+ }
+ else if (cName == "path")
+ {
+ if (Value.isEmpty())
+ lastCookie->mPath = QString::null; // Catch "" <> QString::null
+ else
+ lastCookie->mPath = KURL::decode_string(Value);
+ lastCookie->mExplicitPath = true;
+ }
+ else if (cName == "version")
+ {
+ lastCookie->mProtocolVersion = Value.toInt();
+ }
+ else if ((cName == "secure") ||
+ (cName.isEmpty() && Value.lower() == L1("secure")))
+ {
+ lastCookie->mSecure = true;
+ }
+ else if ((cName == "httponly") ||
+ (cName.isEmpty() && Value.lower() == L1("httponly")))
+ {
+ lastCookie->mHttpOnly = true;
+ }
+ }
+
+ if (*cookieStr == '\0')
+ break; // End of header
+
+ // Skip ';' or '\n'
+ cookieStr++;
+ }
+
+ // RFC2965 cookies come last so that they override netscape cookies.
+ while( !cookieList2.isEmpty() && (lastCookie = cookieList2.take(0)) )
+ {
+ removeDuplicateFromList(&cookieList, lastCookie, true);
+ cookieList.append(lastCookie);
+ }
+
+ return cookieList;
+}
+
+/**
+* Parses cookie_domstr and returns a linked list of KHttpCookie objects.
+* cookie_domstr should be a semicolon-delimited list of "name=value"
+* pairs. Any whitespace before "name" or around '=' is discarded.
+* If no cookies are found, 0 is returned.
+*/
+KHttpCookieList KCookieJar::makeDOMCookies(const QString &_url,
+ const QCString &cookie_domstring,
+ long windowId)
+{
+ // A lot copied from above
+ KHttpCookieList cookieList;
+ KHttpCookiePtr lastCookie = 0;
+
+ const char *cookieStr = cookie_domstring.data();
+ QString Name;
+ QString Value;
+ QString fqdn;
+ QString path;
+
+ if (!parseURL(_url, fqdn, path))
+ {
+ // Error parsing _url
+ return KHttpCookieList();
+ }
+
+ // This time it's easy
+ while(*cookieStr)
+ {
+ cookieStr = parseNameValue(cookieStr, Name, Value);
+
+ // Host = FQDN
+ // Default domain = ""
+ // Default path = ""
+ KHttpCookie *cookie = new KHttpCookie(fqdn, QString::null, QString::null,
+ Name, Value );
+ if (windowId)
+ cookie->mWindowIds.append(windowId);
+
+ cookieList.append(cookie);
+ lastCookie = cookie;
+
+ if (*cookieStr != '\0')
+ cookieStr++; // Skip ';' or '\n'
+ }
+
+ return cookieList;
+}
+
+#ifdef MAX_COOKIE_LIMIT
+static void makeRoom(KHttpCookieList *cookieList, KHttpCookiePtr &cookiePtr)
+{
+ // Too much cookies: throw one away, try to be somewhat clever
+ KHttpCookiePtr lastCookie = 0;
+ for(KHttpCookiePtr cookie = cookieList->first(); cookie; cookie = cookieList->next())
+ {
+ if (cookieList->compareItems(cookie, cookiePtr) < 0)
+ break;
+ lastCookie = cookie;
+ }
+ if (!lastCookie)
+ lastCookie = cookieList->first();
+ cookieList->removeRef(lastCookie);
+}
+#endif
+
+//
+// This function hands a KHttpCookie object over to the cookie jar.
+//
+// On return cookiePtr is set to 0.
+//
+void KCookieJar::addCookie(KHttpCookiePtr &cookiePtr)
+{
+ QStringList domains;
+ KHttpCookieList *cookieList = 0L;
+
+ // We always need to do this to make sure that the
+ // that cookies of type hostname == cookie-domainname
+ // are properly removed and/or updated as necessary!
+ extractDomains( cookiePtr->host(), domains );
+ for ( QStringList::ConstIterator it = domains.begin();
+ (it != domains.end() && !cookieList);
+ ++it )
+ {
+ QString key = (*it).isNull() ? L1("") : (*it);
+ KHttpCookieList *list= m_cookieDomains[key];
+ if ( !list ) continue;
+
+ removeDuplicateFromList(list, cookiePtr, false, true);
+ }
+
+ QString domain = stripDomain( cookiePtr );
+ QString key = domain.isNull() ? L1("") : domain;
+ cookieList = m_cookieDomains[ key ];
+ if (!cookieList)
+ {
+ // Make a new cookie list
+ cookieList = new KHttpCookieList();
+ cookieList->setAutoDelete(true);
+
+ // All cookies whose domain is not already
+ // known to us should be added with KCookieDunno.
+ // KCookieDunno means that we use the global policy.
+ cookieList->setAdvice( KCookieDunno );
+
+ m_cookieDomains.insert( domain, cookieList);
+
+ // Update the list of domains
+ m_domainList.append(domain);
+ }
+
+ // Add the cookie to the cookie list
+ // The cookie list is sorted 'longest path first'
+ if (!cookiePtr->isExpired(time(0)))
+ {
+#ifdef MAX_COOKIE_LIMIT
+ if (cookieList->count() >= MAX_COOKIES_PER_HOST)
+ makeRoom(cookieList, cookiePtr); // Delete a cookie
+#endif
+ cookieList->inSort( cookiePtr );
+ m_cookiesChanged = true;
+ }
+ else
+ {
+ delete cookiePtr;
+ }
+ cookiePtr = 0;
+}
+
+//
+// This function advices whether a single KHttpCookie object should
+// be added to the cookie jar.
+//
+KCookieAdvice KCookieJar::cookieAdvice(KHttpCookiePtr cookiePtr)
+{
+ if (m_rejectCrossDomainCookies && cookiePtr->isCrossDomain())
+ return KCookieReject;
+
+ QStringList domains;
+
+ extractDomains(cookiePtr->host(), domains);
+
+ // If the cookie specifies a domain, check whether it is valid. Otherwise,
+ // accept the cookie anyways but remove the domain="" value to prevent
+ // cross-site cookie injection.
+ if (!cookiePtr->domain().isEmpty())
+ {
+ if (!domains.contains(cookiePtr->domain()) &&
+ !cookiePtr->domain().endsWith("."+cookiePtr->host()))
+ cookiePtr->fixDomain(QString::null);
+ }
+
+ if (m_autoAcceptSessionCookies && (cookiePtr->expireDate() == 0 ||
+ m_ignoreCookieExpirationDate))
+ return KCookieAccept;
+
+ KCookieAdvice advice = KCookieDunno;
+ bool isFQDN = true; // First is FQDN
+ QStringList::Iterator it = domains.begin(); // Start with FQDN which first in the list.
+ while( (advice == KCookieDunno) && (it != domains.end()))
+ {
+ QString domain = *it;
+ // Check if a policy for the FQDN/domain is set.
+ if ( domain[0] == '.' || isFQDN )
+ {
+ isFQDN = false;
+ KHttpCookieList *cookieList = m_cookieDomains[domain];
+ if (cookieList)
+ advice = cookieList->getAdvice();
+ }
+ domains.remove(it);
+ it = domains.begin(); // Continue from begin of remaining list
+ }
+
+ if (advice == KCookieDunno)
+ advice = m_globalAdvice;
+
+ return advice;
+}
+
+//
+// This function gets the advice for all cookies originating from
+// _domain.
+//
+KCookieAdvice KCookieJar::getDomainAdvice(const QString &_domain)
+{
+ KHttpCookieList *cookieList = m_cookieDomains[_domain];
+ KCookieAdvice advice;
+
+ if (cookieList)
+ {
+ advice = cookieList->getAdvice();
+ }
+ else
+ {
+ advice = KCookieDunno;
+ }
+
+ return advice;
+}
+
+//
+// This function sets the advice for all cookies originating from
+// _domain.
+//
+void KCookieJar::setDomainAdvice(const QString &_domain, KCookieAdvice _advice)
+{
+ QString domain(_domain);
+ KHttpCookieList *cookieList = m_cookieDomains[domain];
+
+ if (cookieList)
+ {
+ if (cookieList->getAdvice() != _advice)
+ {
+ m_configChanged = true;
+ // domain is already known
+ cookieList->setAdvice( _advice);
+ }
+
+ if ((cookieList->isEmpty()) &&
+ (_advice == KCookieDunno))
+ {
+ // This deletes cookieList!
+ m_cookieDomains.remove(domain);
+ m_domainList.remove(domain);
+ }
+ }
+ else
+ {
+ // domain is not yet known
+ if (_advice != KCookieDunno)
+ {
+ // We should create a domain entry
+ m_configChanged = true;
+ // Make a new cookie list
+ cookieList = new KHttpCookieList();
+ cookieList->setAutoDelete(true);
+ cookieList->setAdvice( _advice);
+ m_cookieDomains.insert( domain, cookieList);
+ // Update the list of domains
+ m_domainList.append( domain);
+ }
+ }
+}
+
+//
+// This function sets the advice for all cookies originating from
+// the same domain as _cookie
+//
+void KCookieJar::setDomainAdvice(KHttpCookiePtr cookiePtr, KCookieAdvice _advice)
+{
+ QString domain;
+ stripDomain(cookiePtr->host(), domain); // We file the cookie under this domain.
+
+ setDomainAdvice(domain, _advice);
+}
+
+//
+// This function sets the global advice for cookies
+//
+void KCookieJar::setGlobalAdvice(KCookieAdvice _advice)
+{
+ if (m_globalAdvice != _advice)
+ m_configChanged = true;
+ m_globalAdvice = _advice;
+}
+
+//
+// Get a list of all domains known to the cookie jar.
+//
+const QStringList& KCookieJar::getDomainList()
+{
+ return m_domainList;
+}
+
+//
+// Get a list of all cookies in the cookie jar originating from _domain.
+//
+const KHttpCookieList *KCookieJar::getCookieList(const QString & _domain,
+ const QString & _fqdn )
+{
+ QString domain;
+
+ if (_domain.isEmpty())
+ stripDomain( _fqdn, domain );
+ else
+ domain = _domain;
+
+ return m_cookieDomains[domain];
+}
+
+//
+// Eat a cookie out of the jar.
+// cookiePtr should be one of the cookies returned by getCookieList()
+//
+void KCookieJar::eatCookie(KHttpCookiePtr cookiePtr)
+{
+ QString domain = stripDomain(cookiePtr); // We file the cookie under this domain.
+ KHttpCookieList *cookieList = m_cookieDomains[domain];
+
+ if (cookieList)
+ {
+ // This deletes cookiePtr!
+ if (cookieList->removeRef( cookiePtr ))
+ m_cookiesChanged = true;
+
+ if ((cookieList->isEmpty()) &&
+ (cookieList->getAdvice() == KCookieDunno))
+ {
+ // This deletes cookieList!
+ m_cookieDomains.remove(domain);
+
+ m_domainList.remove(domain);
+ }
+ }
+}
+
+void KCookieJar::eatCookiesForDomain(const QString &domain)
+{
+ KHttpCookieList *cookieList = m_cookieDomains[domain];
+ if (!cookieList || cookieList->isEmpty()) return;
+
+ cookieList->clear();
+ if (cookieList->getAdvice() == KCookieDunno)
+ {
+ // This deletes cookieList!
+ m_cookieDomains.remove(domain);
+ m_domainList.remove(domain);
+ }
+ m_cookiesChanged = true;
+}
+
+void KCookieJar::eatSessionCookies( long windowId )
+{
+ if (!windowId)
+ return;
+
+ QStringList::Iterator it=m_domainList.begin();
+ for ( ; it != m_domainList.end(); ++it )
+ eatSessionCookies( *it, windowId, false );
+}
+
+void KCookieJar::eatAllCookies()
+{
+ for ( QStringList::Iterator it=m_domainList.begin();
+ it != m_domainList.end();)
+ {
+ QString domain = *it++;
+ // This might remove domain from domainList!
+ eatCookiesForDomain(domain);
+ }
+}
+
+void KCookieJar::eatSessionCookies( const QString& fqdn, long windowId,
+ bool isFQDN )
+{
+ KHttpCookieList* cookieList;
+ if ( !isFQDN )
+ cookieList = m_cookieDomains[fqdn];
+ else
+ {
+ QString domain;
+ stripDomain( fqdn, domain );
+ cookieList = m_cookieDomains[domain];
+ }
+
+ if ( cookieList )
+ {
+ KHttpCookiePtr cookie=cookieList->first();
+ for (; cookie != 0;)
+ {
+ if ((cookie->expireDate() != 0) && !m_ignoreCookieExpirationDate)
+ {
+ cookie = cookieList->next();
+ continue;
+ }
+
+ QValueList<long> &ids = cookie->windowIds();
+ if (!ids.remove(windowId) || !ids.isEmpty())
+ {
+ cookie = cookieList->next();
+ continue;
+ }
+ KHttpCookiePtr old_cookie = cookie;
+ cookie = cookieList->next();
+ cookieList->removeRef( old_cookie );
+ }
+ }
+}
+
+//
+// Saves all cookies to the file '_filename'.
+// On succes 'true' is returned.
+// On failure 'false' is returned.
+bool KCookieJar::saveCookies(const QString &_filename)
+{
+ KSaveFile saveFile(_filename, 0600);
+
+ if (saveFile.status() != 0)
+ return false;
+
+ FILE *fStream = saveFile.fstream();
+
+ time_t curTime = time(0);
+
+ fprintf(fStream, "# KDE Cookie File v2\n#\n");
+
+ fprintf(fStream, "%-20s %-20s %-12s %-10s %-4s %-20s %-4s %s\n",
+ "# Host", "Domain", "Path", "Exp.date", "Prot",
+ "Name", "Sec", "Value");
+
+ for ( QStringList::Iterator it=m_domainList.begin(); it != m_domainList.end();
+ it++ )
+ {
+ const QString &domain = *it;
+ bool domainPrinted = false;
+
+ KHttpCookieList *cookieList = m_cookieDomains[domain];
+ KHttpCookiePtr cookie=cookieList->last();
+
+ for (; cookie != 0;)
+ {
+ if (cookie->isExpired(curTime))
+ {
+ // Delete expired cookies
+ KHttpCookiePtr old_cookie = cookie;
+ cookie = cookieList->prev();
+ cookieList->removeRef( old_cookie );
+ }
+ else if (cookie->expireDate() != 0 && !m_ignoreCookieExpirationDate)
+ {
+ if (!domainPrinted)
+ {
+ domainPrinted = true;
+ fprintf(fStream, "[%s]\n", domain.local8Bit().data());
+ }
+ // Store persistent cookies
+ QString path = L1("\"");
+ path += cookie->path();
+ path += '"';
+ QString domain = L1("\"");
+ domain += cookie->domain();
+ domain += '"';
+ fprintf(fStream, "%-20s %-20s %-12s %10lu %3d %-20s %-4i %s\n",
+ cookie->host().latin1(), domain.latin1(),
+ path.latin1(), (unsigned long) cookie->expireDate(),
+ cookie->protocolVersion(),
+ cookie->name().isEmpty() ? cookie->value().latin1() : cookie->name().latin1(),
+ (cookie->isSecure() ? 1 : 0) + (cookie->isHttpOnly() ? 2 : 0) +
+ (cookie->hasExplicitPath() ? 4 : 0) + (cookie->name().isEmpty() ? 8 : 0),
+ cookie->value().latin1());
+ cookie = cookieList->prev();
+ }
+ else
+ {
+ // Skip session-only cookies
+ cookie = cookieList->prev();
+ }
+ }
+ }
+
+ return saveFile.close();
+}
+
+typedef char *charPtr;
+
+static const char *parseField(charPtr &buffer, bool keepQuotes=false)
+{
+ char *result;
+ if (!keepQuotes && (*buffer == '\"'))
+ {
+ // Find terminating "
+ buffer++;
+ result = buffer;
+ while((*buffer != '\"') && (*buffer))
+ buffer++;
+ }
+ else
+ {
+ // Find first white space
+ result = buffer;
+ while((*buffer != ' ') && (*buffer != '\t') && (*buffer != '\n') && (*buffer))
+ buffer++;
+ }
+
+ if (!*buffer)
+ return result; //
+ *buffer++ = '\0';
+
+ // Skip white-space
+ while((*buffer == ' ') || (*buffer == '\t') || (*buffer == '\n'))
+ buffer++;
+
+ return result;
+}
+
+
+//
+// Reloads all cookies from the file '_filename'.
+// On succes 'true' is returned.
+// On failure 'false' is returned.
+bool KCookieJar::loadCookies(const QString &_filename)
+{
+ FILE *fStream = fopen( QFile::encodeName(_filename), "r");
+ if (fStream == 0)
+ {
+ return false;
+ }
+
+ time_t curTime = time(0);
+
+ char *buffer = new char[READ_BUFFER_SIZE];
+
+ bool err = false;
+ err = (fgets(buffer, READ_BUFFER_SIZE, fStream) == 0);
+
+ int version = 1;
+ if (!err)
+ {
+ if (strcmp(buffer, "# KDE Cookie File\n") == 0)
+ {
+ // version 1
+ }
+ else if (sscanf(buffer, "# KDE Cookie File v%d\n", &version) != 1)
+ {
+ err = true;
+ }
+ }
+
+ if (!err)
+ {
+ while(fgets(buffer, READ_BUFFER_SIZE, fStream) != 0)
+ {
+ char *line = buffer;
+ // Skip lines which begin with '#' or '['
+ if ((line[0] == '#') || (line[0] == '['))
+ continue;
+
+ const char *host( parseField(line) );
+ const char *domain( parseField(line) );
+ const char *path( parseField(line) );
+ const char *expStr( parseField(line) );
+ if (!expStr) continue;
+ int expDate = (time_t) strtoul(expStr, 0, 10);
+ const char *verStr( parseField(line) );
+ if (!verStr) continue;
+ int protVer = (time_t) strtoul(verStr, 0, 10);
+ const char *name( parseField(line) );
+ bool keepQuotes = false;
+ bool secure = false;
+ bool httpOnly = false;
+ bool explicitPath = false;
+ const char *value = 0;
+ if ((version == 2) || (protVer >= 200))
+ {
+ if (protVer >= 200)
+ protVer -= 200;
+ int i = atoi( parseField(line) );
+ secure = i & 1;
+ httpOnly = i & 2;
+ explicitPath = i & 4;
+ if (i & 8)
+ name = "";
+ line[strlen(line)-1] = '\0'; // Strip LF.
+ value = line;
+ }
+ else
+ {
+ if (protVer >= 100)
+ {
+ protVer -= 100;
+ keepQuotes = true;
+ }
+ value = parseField(line, keepQuotes);
+ secure = atoi( parseField(line) );
+ }
+
+ // Parse error
+ if (!value) continue;
+
+ // Expired or parse error
+ if ((expDate == 0) || (expDate < curTime))
+ continue;
+
+ KHttpCookie *cookie = new KHttpCookie(QString::fromLatin1(host),
+ QString::fromLatin1(domain),
+ QString::fromLatin1(path),
+ QString::fromLatin1(name),
+ QString::fromLatin1(value),
+ expDate, protVer,
+ secure, httpOnly, explicitPath);
+ addCookie(cookie);
+ }
+ }
+ delete [] buffer;
+ m_cookiesChanged = false;
+
+ fclose( fStream);
+ return err;
+}
+
+//
+// Save the cookie configuration
+//
+
+void KCookieJar::saveConfig(KConfig *_config)
+{
+ if (!m_configChanged)
+ return;
+
+ _config->setGroup("Cookie Dialog");
+ _config->writeEntry("PreferredPolicy", m_preferredPolicy);
+ _config->writeEntry("ShowCookieDetails", m_showCookieDetails );
+ _config->setGroup("Cookie Policy");
+ _config->writeEntry("CookieGlobalAdvice", adviceToStr( m_globalAdvice));
+
+ QStringList domainSettings;
+ for ( QStringList::Iterator it=m_domainList.begin();
+ it != m_domainList.end();
+ it++ )
+ {
+ const QString &domain = *it;
+ KCookieAdvice advice = getDomainAdvice( domain);
+ if (advice != KCookieDunno)
+ {
+ QString value(domain);
+ value += ':';
+ value += adviceToStr(advice);
+ domainSettings.append(value);
+ }
+ }
+ _config->writeEntry("CookieDomainAdvice", domainSettings);
+ _config->sync();
+ m_configChanged = false;
+}
+
+
+//
+// Load the cookie configuration
+//
+
+void KCookieJar::loadConfig(KConfig *_config, bool reparse )
+{
+ if ( reparse )
+ _config->reparseConfiguration();
+
+ _config->setGroup("Cookie Dialog");
+ m_showCookieDetails = _config->readBoolEntry( "ShowCookieDetails" );
+ m_preferredPolicy = _config->readNumEntry( "PreferredPolicy", 0 );
+
+ _config->setGroup("Cookie Policy");
+ QStringList domainSettings = _config->readListEntry("CookieDomainAdvice");
+ m_rejectCrossDomainCookies = _config->readBoolEntry( "RejectCrossDomainCookies", true );
+ m_autoAcceptSessionCookies = _config->readBoolEntry( "AcceptSessionCookies", true );
+ m_ignoreCookieExpirationDate = _config->readBoolEntry( "IgnoreExpirationDate", false );
+ QString value = _config->readEntry("CookieGlobalAdvice", L1("Ask"));
+ m_globalAdvice = strToAdvice(value);
+
+ // Reset current domain settings first.
+ for ( QStringList::Iterator it=m_domainList.begin(); it != m_domainList.end(); )
+ {
+ // Make sure to update iterator before calling setDomainAdvice()
+ // setDomainAdvice() might delete the domain from domainList.
+ QString domain = *it++;
+ setDomainAdvice(domain, KCookieDunno);
+ }
+
+ // Now apply the domain settings read from config file...
+ for ( QStringList::Iterator it=domainSettings.begin();
+ it != domainSettings.end(); )
+ {
+ const QString &value = *it++;
+
+ int sepPos = value.findRev(':');
+
+ if (sepPos <= 0)
+ continue;
+
+ QString domain(value.left(sepPos));
+ KCookieAdvice advice = strToAdvice( value.mid(sepPos + 1) );
+ setDomainAdvice(domain, advice);
+ }
+}
diff --git a/kioslave/http/kcookiejar/kcookiejar.desktop b/kioslave/http/kcookiejar/kcookiejar.desktop
new file mode 100644
index 000000000..54421225a
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookiejar.desktop
@@ -0,0 +1,157 @@
+[Desktop Entry]
+Type=Service
+Name=KDED Cookie Jar Module
+Name[af]=Kded Koekie Houer Module
+Name[ar]=وحدة Jar لكعكة KDED
+Name[az]=KDED Kökə Jar Modulu
+Name[be]=Модуль "печыва" KDED
+Name[bg]=Модул KDED Cookie Jar
+Name[bn]=KDED কুকি জার মডিউল
+Name[bs]=KDED modul "Tegla sa keksima"
+Name[ca]=Mòdul Jar de cookies per a KDED
+Name[cs]=KDED modul pro cookies
+Name[csb]=Sprôwianié kùszkama
+Name[cy]=Modiwl Jar Cwci KDED
+Name[da]=KDED-cookie-jar-modul
+Name[de]=Cookie-Verwaltung
+Name[el]=Άρθρωμα Cookie Jar του KDED
+Name[eo]=KDED-kuketotraktila modulo
+Name[es]=Módulo Jar de cookies de KDED
+Name[et]=KDED Cookie Jar moodul
+Name[eu]=KDED Cookie Jar modulua
+Name[fa]=پیمانۀ ظرف کوکی KDED
+Name[fi]=KDED-evästemoduuli
+Name[fr]=Module Jar de cookie KDED
+Name[fy]=KDED-module foar it bewarjen fan Koekjes
+Name[gl]=Módulo Jar de cookies de KDED
+Name[he]=מודול צנצנת העוגיות של KDED
+Name[hi]=KDED कुकी जार मॉड्यूल
+Name[hr]=KDED modul za čuvanje kolačića
+Name[hu]=KDED cookie-modul
+Name[id]=Modul Penyimpanan Cookies KDED
+Name[is]=KDED smákökukrukka
+Name[it]=Modulo Jar dei cookie per KDED
+Name[ja]=KDED クッキー Jar モジュール
+Name[ka]=KDED-ის ბმულების Jar მოდული
+Name[kk]=KDE cookie модулі
+Name[km]=ម៉ូឌុល Jar នៃ​ខូគី KDED
+Name[ko]=KDED 쿠키 JAR 모듈
+Name[lb]=KDED-Modul fir d'Verwaltung vun de Cookien
+Name[lt]=KDED slapukų rinkinio modulis
+Name[lv]=KDED Cepumu Jar modulis
+Name[mk]=KDED модул Тегла со колачиња
+Name[ms]=Modul Balang Cecikut KDED
+Name[mt]=Modulu tal-"cookies" KDED
+Name[nb]=KDEDs modul for informasjonskapsler (Cookie Jar)
+Name[nds]=KDED-Kookjepleeg
+Name[ne]=KDED कुकी जार मोड्युल
+Name[nl]=KDED-module voor het opslaan van cookies
+Name[nn]=KDED-informasjonskapselmodul
+Name[nso]=Seripa sa Jar ya Cookie ya KDED
+Name[pa]=KDED ਕੂਕੀਜ਼ Jar ਮੈਡੀਊਲ
+Name[pl]=Zarządzanie ciasteczkami
+Name[pt]=Módulo de 'Cookies' do KDED
+Name[pt_BR]=Módulo de Cookie Jar do KDE
+Name[ro]=Modul Cookie JAR pentru KDED
+Name[ru]=Служба cookie
+Name[rw]=Igice Jar Inyandikonyakwirema KDED
+Name[se]=KDED gáhkošlihtti-moduvla
+Name[sk]=Modul pre cookies KDED
+Name[sl]=Modul posode za piškotke KDED
+Name[sq]=Modul i KDED-it për Qyp të keksave nga KDED
+Name[sr]=KDED модул тегле за колачиће
+Name[sr@Latn]=KDED modul tegle za kolačiće
+Name[sv]=KDED-kakburksmodul
+Name[ta]=KDED தற்காலிக நினைவக சாடி பகுதி
+Name[te]=కెడిఈడి కుకీ జాడి మాడ్యూల్
+Name[tg]=Модули KDED Cookie Jar
+Name[th]=โมดูลโถคุกกี KDED
+Name[tr]=KDED Cookie Jar Modülü
+Name[tt]=KDED'nıñ Cookie Modulı
+Name[uk]=Модуль глечика з куками KDED
+Name[uz]=KDED kuki idish moduli
+Name[uz@cyrillic]=KDED куки идиш модули
+Name[ven]=Modulu wa Jar wa Cookie ya KDED
+Name[vi]=Mô-đun Cookie Jar của KDED
+Name[xh]=Isicatshulwa se KDED Cookie Jar
+Name[zh_CN]=KDED Cookie Jar 模块
+Name[zh_HK]=KDED Cookie Jar 模組
+Name[zh_TW]=KDED Cookie Jar 模組
+Name[zu]=Ingxenye Yojeke ye-Cookie ye-KDED
+Comment=Keeps track of all cookies in the system
+Comment[af]=Hou tred van al die koekies in die stelsel
+Comment[ar]=يراقب جميع الكعكات الموجودة على النظام
+Comment[be]=Захоўвае звесткі пра "печыва"
+Comment[bg]=Контрол над всички бисквитки в системата
+Comment[bn]=সিস্টেমে সমস্ত কুকি-র খোঁজখবর রাখে
+Comment[bs]=Prati sve kolačiće (cookije) na sistemu
+Comment[ca]=Segueix totes les galetes en el sistema
+Comment[cs]=Spravuje Cookies v počítači
+Comment[csb]=Trzëmô wszëtczé kùszczi w systemie
+Comment[da]=Holder styr på alle cookier på systemet
+Comment[de]=Verwaltet die Cookies in KDE
+Comment[el]=Διατηρεί αρχείο από όλα τα cookies στο σύστημα
+Comment[eo]=Registras ĉiujn kuketojn en la sistemo
+Comment[es]=Mantiene registro todas las cookies en el sistema
+Comment[et]=Hoiab silma peal kõigil süsteemi küpsistel
+Comment[eu]=Sistemaren cookie guztien jarraipena egiten du
+Comment[fa]=رد همۀ کوکیها را در سیستم نگه می‌دارد
+Comment[fi]=Seuraa järjestelmän evästeitä
+Comment[fr]=Conserve une trace de tous les cookies dans le système
+Comment[fy]=Hâld by wer alle koekjes binne
+Comment[gl]=Manter as pegadas de todas as Cookies no sistema
+Comment[he]=מבצע מעקב אחרי כל העוגיות במערכת
+Comment[hi]=तंत्र की सभी कुकी की जानकारी रखता है
+Comment[hr]=Vođenje evidencije o svim kolačićima na sustavu
+Comment[hu]=Nyomon követi a rendszerben létrejövő cookie-kat
+Comment[id]=Menyimpan semua cookies pada sistem
+Comment[is]=Heldur utanum allar smákökur í kerfinu
+Comment[it]=Tiene traccia di tutti i cookie del sistema
+Comment[ja]=システムのすべてのクッキーを管理します
+Comment[ka]=სისტემის ყველა ბმულის თვალმიდევნება
+Comment[kk]=Жүйедегі бүкіл cookie файлдарды бақылау
+Comment[km]=រក្សា​ការតាមដាន​ខូគី​ទាំងអស់​ក្នុង​ប្រព័ន្ធ
+Comment[lb]=Iwwerwaacht all d'Cookie vum System
+Comment[lt]=Seka visus slapukus sistemoje
+Comment[lv]=Seko visiem sistēmā esošajiem cepumiem
+Comment[mk]=Води сметка за сите колачиња во системот
+Comment[ms]=Memerhati semua cecikut dalam sistem
+Comment[nb]=Holder rede på alle informasjonskapsler i systemet
+Comment[nds]=Passt all Kookjes in't Systeem
+Comment[ne]=प्रणालीमा सबै कुकीहरूको पदचिन्ह राख्दछ
+Comment[nl]=Houdt alle cookies in het systeem bij
+Comment[nn]=Held greie på informasjonskapslane
+Comment[pa]=ਸਿਸਟਮ ਦੇ ਸਾਰੇ ਕੂਕੀਜ਼ ਦਾ ਰਿਕਾਰਡ ਰੱਖੋ
+Comment[pl]=Przechowuje wszystkie ciasteczka w systemie
+Comment[pt]=Mantém um registo de todos os 'cookies' no sistema
+Comment[pt_BR]=Mantém informações sobre todos os cookies do sistema
+Comment[ro]=Administrează toate "cookie"-urile din sistem
+Comment[ru]=Управление закладками-cookie в KDE
+Comment[rw]=Iguma inzira y'inyandikonyakwirema zose muri sisitemu
+Comment[se]=Halddaša buot diehtočoahkuid
+Comment[sk]=Sleduje všetky cookie v systéme
+Comment[sl]=Opazuje vse piškotke v sistemu
+Comment[sr]=Води евиденцију о свим колачићима на систему
+Comment[sr@Latn]=Vodi evidenciju o svim kolačićima na sistemu
+Comment[sv]=Håller ordning på alla kakor i systemet
+Comment[ta]=கணினியின் எல்லா தற்காலிக நினைவகங்களையும் கண்காணிக்கிறது
+Comment[te]=వ్యవస్థలోని అన్ని కుకీల జాడని వుంచుకుంటుంది
+Comment[tg]=Гузаргоҳи ҳамаша Cookies дар система муҳофизат кунед
+Comment[th]=ใช้ติดตามคุกกีทั้งหมดในระบบ
+Comment[tr]=Sistemdeki tüm çerezleri izler
+Comment[tt]=Sistemdäge bar cookie'larnı küz astında tota
+Comment[uk]=Стежить за всіма куками в системі
+Comment[uz]=Tizimdagi hamma kukilarni kuzatadi
+Comment[uz@cyrillic]=Тизимдаги ҳамма кукиларни кузатади
+Comment[vi]=Theo dõi các tập tin cookie trong hệ thống.
+Comment[zh_CN]=将全部 cookies 的记录保存在系统中
+Comment[zh_TW]=追蹤系統所有的 cookies
+ServiceTypes=KDEDModule
+Exec=kcookiejar
+X-DCOP-ServiceType=Unique
+X-KDE-StartupNotify=false
+X-KDE-ModuleType=Library
+X-KDE-Library=kcookiejar
+X-KDE-FactoryName=kcookiejar
+X-KDE-Kded-autoload=false
+X-KDE-Kded-load-on-demand=true
diff --git a/kioslave/http/kcookiejar/kcookiejar.h b/kioslave/http/kcookiejar/kcookiejar.h
new file mode 100644
index 000000000..c73708bea
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookiejar.h
@@ -0,0 +1,365 @@
+/*
+ This file is part of the KDE File Manager
+
+ Copyright (C) 1998 Waldo Bastian (bastian@kde.org)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2 as published by the Free Software Foundation.
+
+ This software 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this library; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+//----------------------------------------------------------------------------
+//
+// KDE File Manager -- HTTP Cookies
+// $Id$
+
+#ifndef KCOOKIEJAR_H
+#define KCOOKIEJAR_H
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qdict.h>
+#include <qptrlist.h>
+#include <time.h>
+
+class KConfig;
+class KCookieJar;
+class KHttpCookie;
+class KHttpCookieList;
+
+typedef KHttpCookie *KHttpCookiePtr;
+
+enum KCookieAdvice
+{
+ KCookieDunno=0,
+ KCookieAccept,
+ KCookieReject,
+ KCookieAsk
+};
+
+class KHttpCookie
+{
+ friend class KCookieJar;
+ friend class KHttpCookieList;
+
+protected:
+ QString mHost;
+ QString mDomain;
+ QString mPath;
+ QString mName;
+ QString mValue;
+ time_t mExpireDate;
+ int mProtocolVersion;
+ bool mSecure;
+ bool mCrossDomain;
+ bool mHttpOnly;
+ bool mExplicitPath;
+ QValueList<long> mWindowIds;
+
+ QString cookieStr(bool useDOMFormat);
+
+public:
+ KHttpCookie(const QString &_host=QString::null,
+ const QString &_domain=QString::null,
+ const QString &_path=QString::null,
+ const QString &_name=QString::null,
+ const QString &_value=QString::null,
+ time_t _expireDate=0,
+ int _protocolVersion=0,
+ bool _secure = false,
+ bool _httpOnly = false,
+ bool _explicitPath = false);
+
+ QString domain(void) { return mDomain; }
+ QString host(void) { return mHost; }
+ QString path(void) { return mPath; }
+ QString name(void) { return mName; }
+ QString value(void) { return mValue; }
+ QValueList<long> &windowIds(void) { return mWindowIds; }
+ void fixDomain(const QString &domain) { mDomain = domain; }
+ time_t expireDate(void) { return mExpireDate; }
+ int protocolVersion(void) { return mProtocolVersion; }
+ bool isSecure(void) { return mSecure; }
+ bool isExpired(time_t currentDate);
+ bool isCrossDomain(void) { return mCrossDomain; }
+ bool isHttpOnly(void) { return mHttpOnly; }
+ bool hasExplicitPath(void) { return mExplicitPath; }
+ bool match(const QString &fqdn, const QStringList &domainList, const QString &path);
+};
+
+class KHttpCookieList : public QPtrList<KHttpCookie>
+{
+public:
+ KHttpCookieList() : QPtrList<KHttpCookie>(), advice( KCookieDunno )
+ { }
+ virtual ~KHttpCookieList() { }
+
+ virtual int compareItems( void * item1, void * item2);
+ KCookieAdvice getAdvice(void) { return advice; }
+ void setAdvice(KCookieAdvice _advice) { advice = _advice; }
+
+private:
+ KCookieAdvice advice;
+};
+
+class KCookieJar
+{
+public:
+ /**
+ * Constructs a new cookie jar
+ *
+ * One jar should be enough for all cookies.
+ */
+ KCookieJar();
+
+ /**
+ * Destructs the cookie jar
+ *
+ * Poor little cookies, they will all be eaten by the cookie monster!
+ */
+ ~KCookieJar();
+
+ /**
+ * Returns whether the cookiejar has been changed
+ */
+ bool changed() const { return m_cookiesChanged || m_configChanged; }
+
+ /**
+ * Store all the cookies in a safe(?) place
+ */
+ bool saveCookies(const QString &_filename);
+
+ /**
+ * Load all the cookies from file and add them to the cookie jar.
+ */
+ bool loadCookies(const QString &_filename);
+
+ /**
+ * Save the cookie configuration
+ */
+ void saveConfig(KConfig *_config);
+
+ /**
+ * Load the cookie configuration
+ */
+ void loadConfig(KConfig *_config, bool reparse = false);
+
+ /**
+ * Looks for cookies in the cookie jar which are appropriate for _url.
+ * Returned is a string containing all appropriate cookies in a format
+ * which can be added to a HTTP-header without any additional processing.
+ *
+ * If @p useDOMFormat is true, the string is formatted in a format
+ * in compliance with the DOM standard.
+ * @p pendingCookies contains a list of cookies that have not been
+ * approved yet by the user but that will be included in the result
+ * none the less.
+ */
+ QString findCookies(const QString &_url, bool useDOMFormat, long windowId, KHttpCookieList *pendingCookies=0);
+
+ /**
+ * This function parses cookie_headers and returns a linked list of
+ * valid KHttpCookie objects for all cookies found in cookie_headers.
+ * If no cookies could be found 0 is returned.
+ *
+ * cookie_headers should be a concatenation of all lines of a HTTP-header
+ * which start with "Set-Cookie". The lines should be separated by '\n's.
+ */
+ KHttpCookieList makeCookies(const QString &_url, const QCString &cookie_headers, long windowId);
+
+ /**
+ * This function parses cookie_headers and returns a linked list of
+ * valid KHttpCookie objects for all cookies found in cookie_headers.
+ * If no cookies could be found 0 is returned.
+ *
+ * cookie_domstr should be a concatenation of "name=value" pairs, separated
+ * by a semicolon ';'.
+ */
+ KHttpCookieList makeDOMCookies(const QString &_url, const QCString &cookie_domstr, long windowId);
+
+ /**
+ * This function hands a KHttpCookie object over to the cookie jar.
+ *
+ * On return cookiePtr is set to 0.
+ */
+ void addCookie(KHttpCookiePtr &cookiePtr);
+
+ /**
+ * This function advices whether a single KHttpCookie object should
+ * be added to the cookie jar.
+ *
+ * Possible return values are:
+ * - KCookieAccept, the cookie should be added
+ * - KCookieReject, the cookie should not be added
+ * - KCookieAsk, the user should decide what to do
+ */
+ KCookieAdvice cookieAdvice(KHttpCookiePtr cookiePtr);
+
+ /**
+ * This function gets the advice for all cookies originating from
+ * _domain.
+ *
+ * - KCookieDunno, no specific advice for _domain
+ * - KCookieAccept, accept all cookies for _domain
+ * - KCookieReject, reject all cookies for _domain
+ * - KCookieAsk, the user decides what to do with cookies for _domain
+ */
+ KCookieAdvice getDomainAdvice(const QString &_domain);
+
+ /**
+ * This function sets the advice for all cookies originating from
+ * _domain.
+ *
+ * _advice can have the following values:
+ * - KCookieDunno, no specific advice for _domain
+ * - KCookieAccept, accept all cookies for _domain
+ * - KCookieReject, reject all cookies for _domain
+ * - KCookieAsk, the user decides what to do with cookies for _domain
+ */
+ void setDomainAdvice(const QString &_domain, KCookieAdvice _advice);
+
+ /**
+ * This function sets the advice for all cookies originating from
+ * the same domain as _cookie
+ *
+ * _advice can have the following values:
+ * - KCookieDunno, no specific advice for _domain
+ * - KCookieAccept, accept all cookies for _domain
+ * - KCookieReject, reject all cookies for _domain
+ * - KCookieAsk, the user decides what to do with cookies for _domain
+ */
+ void setDomainAdvice(KHttpCookiePtr _cookie, KCookieAdvice _advice);
+
+ /**
+ * Get the global advice for cookies
+ *
+ * The returned advice can have the following values:
+ * - KCookieAccept, accept cookies
+ * - KCookieReject, reject cookies
+ * - KCookieAsk, the user decides what to do with cookies
+ *
+ * The global advice is used if the domain has no advice set.
+ */
+ KCookieAdvice getGlobalAdvice() { return m_globalAdvice; }
+
+ /**
+ * This function sets the global advice for cookies
+ *
+ * _advice can have the following values:
+ * - KCookieAccept, accept cookies
+ * - KCookieReject, reject cookies
+ * - KCookieAsk, the user decides what to do with cookies
+ *
+ * The global advice is used if the domain has no advice set.
+ */
+ void setGlobalAdvice(KCookieAdvice _advice);
+
+ /**
+ * Get a list of all domains known to the cookie jar.
+ * A domain is known to the cookie jar if:
+ * - It has a cookie originating from the domain
+ * - It has a specific advice set for the domain
+ */
+ const QStringList& getDomainList();
+
+ /**
+ * Get a list of all cookies in the cookie jar originating from _domain.
+ */
+ const KHttpCookieList *getCookieList(const QString & _domain,
+ const QString& _fqdn );
+
+ /**
+ * Remove & delete a cookie from the jar.
+ *
+ * cookiePtr should be one of the entries in a KHttpCookieList.
+ * Update your KHttpCookieList by calling getCookieList after
+ * calling this function.
+ */
+ void eatCookie(KHttpCookiePtr cookiePtr);
+
+ /**
+ * Remove & delete all cookies for @p domain.
+ */
+ void eatCookiesForDomain(const QString &domain);
+
+ /**
+ * Remove & delete all cookies
+ */
+ void eatAllCookies();
+
+ /**
+ * Removes all end of session cookies set by the
+ * session @p windId.
+ */
+ void eatSessionCookies( long windowId );
+
+ /**
+ * Removes all end of session cookies set by the
+ * session @p windId.
+ */
+ void eatSessionCookies( const QString& fqdn, long windowId, bool isFQDN = true );
+
+ /**
+ * Parses _url and returns the FQDN (_fqdn) and path (_path).
+ */
+ static bool parseURL(const QString &_url,
+ QString &_fqdn,
+ QString &_path);
+
+ /**
+ * Returns a list of domains in @p _domainList relevant for this host.
+ * The list is sorted with the FQDN listed first and the top-most
+ * domain listed last
+ */
+ void extractDomains(const QString &_fqdn,
+ QStringList &_domainList);
+
+ static QString adviceToStr(KCookieAdvice _advice);
+ static KCookieAdvice strToAdvice(const QString &_str);
+
+ /** Returns the */
+ int preferredDefaultPolicy() const { return m_preferredPolicy; }
+
+ /** Returns the */
+ bool showCookieDetails () const { return m_showCookieDetails; }
+
+ /**
+ * Sets the user's default preference cookie policy.
+ */
+ void setPreferredDefaultPolicy (int value) { m_preferredPolicy = value; }
+
+ /**
+ * Sets the user's preference of level of detail displayed
+ * by the cookie dialog.
+ */
+ void setShowCookieDetails (bool value) { m_showCookieDetails = value; }
+
+protected:
+ void stripDomain(const QString &_fqdn, QString &_domain);
+ QString stripDomain( KHttpCookiePtr cookiePtr);
+
+protected:
+ QStringList m_domainList;
+ KCookieAdvice m_globalAdvice;
+ QDict<KHttpCookieList> m_cookieDomains;
+ QDict<int> m_twoLevelTLD;
+
+ bool m_configChanged;
+ bool m_cookiesChanged;
+ bool m_showCookieDetails;
+ bool m_rejectCrossDomainCookies;
+ bool m_autoAcceptSessionCookies;
+ bool m_ignoreCookieExpirationDate;
+
+ int m_preferredPolicy;
+};
+#endif
diff --git a/kioslave/http/kcookiejar/kcookiescfg.upd b/kioslave/http/kcookiejar/kcookiescfg.upd
new file mode 100644
index 000000000..3c1cd028d
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookiescfg.upd
@@ -0,0 +1,16 @@
+# Update for old cookie config files, if present
+Id=kde2.2/b1
+File=kcookiejarrc
+Group=Browser Settings/HTTP,Cookie Policy
+
+# Update cookies config file...
+Id=kde3.1/cvs
+File=kcookiejarrc
+Group=<default>,Cookie Dialog
+Key=DefaultRadioButton,PreferredPolicy
+Key=ShowCookieDetails
+Group=Cookie Policy
+Key=AcceptTempCookies,AcceptSessionCookies
+Key=AutoAcceptSessionCookies,AcceptSessionCookies
+Key=RejectCrossDomain,RejectCrossDomainCookies
+Key=IgnoreCookieExpirationDate,IgnoreExpirationDate
diff --git a/kioslave/http/kcookiejar/kcookieserver.cpp b/kioslave/http/kcookiejar/kcookieserver.cpp
new file mode 100644
index 000000000..365f15e79
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookieserver.cpp
@@ -0,0 +1,606 @@
+/*
+This file is part of KDE
+
+ Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+//----------------------------------------------------------------------------
+//
+// KDE Cookie Server
+// $Id$
+
+#define SAVE_DELAY 3 // Save after 3 minutes
+
+#include <unistd.h>
+
+#include <qtimer.h>
+#include <qptrlist.h>
+#include <qfile.h>
+
+#include <dcopclient.h>
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <kstandarddirs.h>
+
+#include "kcookiejar.h"
+#include "kcookiewin.h"
+#include "kcookieserver.h"
+
+extern "C" {
+ KDE_EXPORT KDEDModule *create_kcookiejar(const QCString &name)
+ {
+ return new KCookieServer(name);
+ }
+}
+
+
+// Cookie field indexes
+enum CookieDetails { CF_DOMAIN=0, CF_PATH, CF_NAME, CF_HOST,
+ CF_VALUE, CF_EXPIRE, CF_PROVER, CF_SECURE };
+
+
+class CookieRequest {
+public:
+ DCOPClient *client;
+ DCOPClientTransaction *transaction;
+ QString url;
+ bool DOM;
+ long windowId;
+};
+
+template class QPtrList<CookieRequest>;
+
+class RequestList : public QPtrList<CookieRequest>
+{
+public:
+ RequestList() : QPtrList<CookieRequest>() { }
+};
+
+KCookieServer::KCookieServer(const QCString &name)
+ :KDEDModule(name)
+{
+ mOldCookieServer = new DCOPClient(); // backwards compatibility.
+ mOldCookieServer->registerAs("kcookiejar", false);
+ mOldCookieServer->setDaemonMode( true );
+ mCookieJar = new KCookieJar;
+ mPendingCookies = new KHttpCookieList;
+ mPendingCookies->setAutoDelete(true);
+ mRequestList = new RequestList;
+ mAdvicePending = false;
+ mTimer = new QTimer();
+ connect( mTimer, SIGNAL( timeout()), SLOT( slotSave()));
+ mConfig = new KConfig("kcookiejarrc");
+ mCookieJar->loadConfig( mConfig );
+
+ QString filename = locateLocal("data", "kcookiejar/cookies");
+
+ // Stay backwards compatible!
+ QString filenameOld = locate("data", "kfm/cookies");
+ if (!filenameOld.isEmpty())
+ {
+ mCookieJar->loadCookies( filenameOld );
+ if (mCookieJar->saveCookies( filename))
+ {
+ unlink(QFile::encodeName(filenameOld)); // Remove old kfm cookie file
+ }
+ }
+ else
+ {
+ mCookieJar->loadCookies( filename);
+ }
+ connect(this, SIGNAL(windowUnregistered(long)),
+ this, SLOT(slotDeleteSessionCookies(long)));
+}
+
+KCookieServer::~KCookieServer()
+{
+ if (mCookieJar->changed())
+ slotSave();
+ delete mOldCookieServer;
+ delete mCookieJar;
+ delete mTimer;
+ delete mPendingCookies;
+ delete mConfig;
+}
+
+bool KCookieServer::cookiesPending( const QString &url, KHttpCookieList *cookieList )
+{
+ QString fqdn;
+ QStringList domains;
+ QString path;
+ // Check whether 'url' has cookies on the pending list
+ if (mPendingCookies->isEmpty())
+ return false;
+ if (!KCookieJar::parseURL(url, fqdn, path))
+ return false;
+
+ mCookieJar->extractDomains( fqdn, domains );
+ for( KHttpCookie *cookie = mPendingCookies->first();
+ cookie != 0L;
+ cookie = mPendingCookies->next())
+ {
+ if (cookie->match( fqdn, domains, path))
+ {
+ if (!cookieList)
+ return true;
+ cookieList->append(cookie);
+ }
+ }
+ if (!cookieList)
+ return false;
+ return cookieList->isEmpty();
+}
+
+void KCookieServer::addCookies( const QString &url, const QCString &cookieHeader,
+ long windowId, bool useDOMFormat )
+{
+ KHttpCookieList cookieList;
+ if (useDOMFormat)
+ cookieList = mCookieJar->makeDOMCookies(url, cookieHeader, windowId);
+ else
+ cookieList = mCookieJar->makeCookies(url, cookieHeader, windowId);
+
+ checkCookies(&cookieList);
+
+ for(KHttpCookiePtr cookie = cookieList.first(); cookie; cookie = cookieList.first())
+ mPendingCookies->append(cookieList.take());
+
+ if (!mAdvicePending)
+ {
+ mAdvicePending = true;
+ while (!mPendingCookies->isEmpty())
+ {
+ checkCookies(0);
+ }
+ mAdvicePending = false;
+ }
+}
+
+void KCookieServer::checkCookies( KHttpCookieList *cookieList)
+{
+ KHttpCookieList *list;
+
+ if (cookieList)
+ list = cookieList;
+ else
+ list = mPendingCookies;
+
+ KHttpCookiePtr cookie = list->first();
+ while (cookie)
+ {
+ kdDebug(7104) << "checkCookies: Asking cookie advice for " << cookie->host() << endl;
+ KCookieAdvice advice = mCookieJar->cookieAdvice(cookie);
+ switch(advice)
+ {
+ case KCookieAccept:
+ list->take();
+ mCookieJar->addCookie(cookie);
+ cookie = list->current();
+ break;
+
+ case KCookieReject:
+ list->take();
+ delete cookie;
+ cookie = list->current();
+ break;
+
+ default:
+ cookie = list->next();
+ break;
+ }
+ }
+
+ if (cookieList || list->isEmpty())
+ return;
+
+ KHttpCookiePtr currentCookie = mPendingCookies->first();
+
+ KHttpCookieList currentList;
+ currentList.append(currentCookie);
+ QString currentHost = currentCookie->host();
+
+ cookie = mPendingCookies->next();
+ while (cookie)
+ {
+ if (cookie->host() == currentHost)
+ {
+ currentList.append(cookie);
+ }
+ cookie = mPendingCookies->next();
+ }
+
+ KCookieWin *kw = new KCookieWin( 0L, currentList,
+ mCookieJar->preferredDefaultPolicy(),
+ mCookieJar->showCookieDetails() );
+ KCookieAdvice userAdvice = kw->advice(mCookieJar, currentCookie);
+ delete kw;
+ // Save the cookie config if it has changed
+ mCookieJar->saveConfig( mConfig );
+
+ // Apply the user's choice to all cookies that are currently
+ // queued for this host.
+ cookie = mPendingCookies->first();
+ while (cookie)
+ {
+ if (cookie->host() == currentHost)
+ {
+ switch(userAdvice)
+ {
+ case KCookieAccept:
+ mPendingCookies->take();
+ mCookieJar->addCookie(cookie);
+ cookie = mPendingCookies->current();
+ break;
+
+ case KCookieReject:
+ mPendingCookies->take();
+ delete cookie;
+ cookie = mPendingCookies->current();
+ break;
+
+ default:
+ qWarning(__FILE__":%d Problem!", __LINE__);
+ cookie = mPendingCookies->next();
+ break;
+ }
+ }
+ else
+ {
+ cookie = mPendingCookies->next();
+ }
+ }
+
+
+ // Check if we can handle any request
+ for ( CookieRequest *request = mRequestList->first(); request;)
+ {
+ if (!cookiesPending( request->url ))
+ {
+ QCString replyType;
+ QByteArray replyData;
+ QString res = mCookieJar->findCookies( request->url, request->DOM, request->windowId );
+
+ QDataStream stream2(replyData, IO_WriteOnly);
+ stream2 << res;
+ replyType = "QString";
+ request->client->endTransaction( request->transaction,
+ replyType, replyData);
+ CookieRequest *tmp = request;
+ request = mRequestList->next();
+ mRequestList->removeRef( tmp );
+ delete tmp;
+ }
+ else
+ {
+ request = mRequestList->next();
+ }
+ }
+ if (mCookieJar->changed())
+ saveCookieJar();
+}
+
+void KCookieServer::slotSave()
+{
+ QString filename = locateLocal("data", "kcookiejar/cookies");
+ mCookieJar->saveCookies(filename);
+}
+
+void KCookieServer::saveCookieJar()
+{
+ if( mTimer->isActive() )
+ return;
+
+ mTimer->start( 1000*60*SAVE_DELAY, true );
+}
+
+void KCookieServer::putCookie( QStringList& out, KHttpCookie *cookie,
+ const QValueList<int>& fields )
+{
+ QValueList<int>::ConstIterator i = fields.begin();
+ for ( ; i != fields.end(); ++i )
+ {
+ switch(*i)
+ {
+ case CF_DOMAIN :
+ out << cookie->domain();
+ break;
+ case CF_NAME :
+ out << cookie->name();
+ break;
+ case CF_PATH :
+ out << cookie->path();
+ break;
+ case CF_HOST :
+ out << cookie->host();
+ break;
+ case CF_VALUE :
+ out << cookie->value();
+ break;
+ case CF_EXPIRE :
+ out << QString::number(cookie->expireDate());
+ break;
+ case CF_PROVER :
+ out << QString::number(cookie->protocolVersion());
+ break;
+ case CF_SECURE :
+ out << QString::number( cookie->isSecure() ? 1 : 0 );
+ break;
+ default :
+ out << QString::null;
+ }
+ }
+}
+
+bool KCookieServer::cookieMatches( KHttpCookiePtr c,
+ QString domain, QString fqdn,
+ QString path, QString name )
+{
+ if( c )
+ {
+ bool hasDomain = !domain.isEmpty();
+ return
+ ((hasDomain && c->domain() == domain) ||
+ fqdn == c->host()) &&
+ (c->path() == path) &&
+ (c->name() == name) &&
+ (!c->isExpired(time(0)));
+ }
+ return false;
+}
+
+// DCOP function
+QString
+KCookieServer::findCookies(QString url)
+{
+ return findCookies(url, 0);
+}
+
+// DCOP function
+QString
+KCookieServer::findCookies(QString url, long windowId)
+{
+ if (cookiesPending(url))
+ {
+ CookieRequest *request = new CookieRequest;
+ request->client = callingDcopClient();
+ request->transaction = request->client->beginTransaction();
+ request->url = url;
+ request->DOM = false;
+ request->windowId = windowId;
+ mRequestList->append( request );
+ return QString::null; // Talk to you later :-)
+ }
+
+ QString cookies = mCookieJar->findCookies(url, false, windowId);
+
+ if (mCookieJar->changed())
+ saveCookieJar();
+
+ return cookies;
+}
+
+// DCOP function
+QStringList
+KCookieServer::findDomains()
+{
+ QStringList result;
+ const QStringList domains = mCookieJar->getDomainList();
+ for ( QStringList::ConstIterator domIt = domains.begin();
+ domIt != domains.end(); ++domIt )
+ {
+ // Ignore domains that have policy set for but contain
+ // no cookies whatsoever...
+ const KHttpCookieList* list = mCookieJar->getCookieList(*domIt, "");
+ if ( list && !list->isEmpty() )
+ result << *domIt;
+ }
+ return result;
+}
+
+// DCOP function
+QStringList
+KCookieServer::findCookies(QValueList<int> fields,
+ QString domain,
+ QString fqdn,
+ QString path,
+ QString name)
+{
+ QStringList result;
+ bool allDomCookies = name.isEmpty();
+
+ const KHttpCookieList* list = mCookieJar->getCookieList(domain, fqdn);
+ if ( list && !list->isEmpty() )
+ {
+ QPtrListIterator<KHttpCookie>it( *list );
+ for ( ; it.current(); ++it )
+ {
+ if ( !allDomCookies )
+ {
+ if ( cookieMatches(it.current(), domain, fqdn, path, name) )
+ {
+ putCookie(result, it.current(), fields);
+ break;
+ }
+ }
+ else
+ putCookie(result, it.current(), fields);
+ }
+ }
+ return result;
+}
+
+// DCOP function
+QString
+KCookieServer::findDOMCookies(QString url)
+{
+ return findDOMCookies(url, 0);
+}
+
+// DCOP function
+QString
+KCookieServer::findDOMCookies(QString url, long windowId)
+{
+ // We don't wait for pending cookies because it locks up konqueror
+ // which can cause a deadlock if it happens to have a popup-menu up.
+ // Instead we just return pending cookies as if they had been accepted already.
+ KHttpCookieList pendingCookies;
+ cookiesPending(url, &pendingCookies);
+
+ return mCookieJar->findCookies(url, true, windowId, &pendingCookies);
+}
+
+// DCOP function
+void
+KCookieServer::addCookies(QString arg1, QCString arg2, long arg3)
+{
+ addCookies(arg1, arg2, arg3, false);
+}
+
+// DCOP function
+void
+KCookieServer::deleteCookie(QString domain, QString fqdn,
+ QString path, QString name)
+{
+ const KHttpCookieList* list = mCookieJar->getCookieList( domain, fqdn );
+ if ( list && !list->isEmpty() )
+ {
+ QPtrListIterator<KHttpCookie>it (*list);
+ for ( ; it.current(); ++it )
+ {
+ if( cookieMatches(it.current(), domain, fqdn, path, name) )
+ {
+ mCookieJar->eatCookie( it.current() );
+ saveCookieJar();
+ break;
+ }
+ }
+ }
+}
+
+// DCOP function
+void
+KCookieServer::deleteCookiesFromDomain(QString domain)
+{
+ mCookieJar->eatCookiesForDomain(domain);
+ saveCookieJar();
+}
+
+
+// Qt function
+void
+KCookieServer::slotDeleteSessionCookies( long windowId )
+{
+ deleteSessionCookies(windowId);
+}
+
+// DCOP function
+void
+KCookieServer::deleteSessionCookies( long windowId )
+{
+ mCookieJar->eatSessionCookies( windowId );
+ saveCookieJar();
+}
+
+void
+KCookieServer::deleteSessionCookiesFor(QString fqdn, long windowId)
+{
+ mCookieJar->eatSessionCookies( fqdn, windowId );
+ saveCookieJar();
+}
+
+// DCOP function
+void
+KCookieServer::deleteAllCookies()
+{
+ mCookieJar->eatAllCookies();
+ saveCookieJar();
+}
+
+// DCOP function
+void
+KCookieServer::addDOMCookies(QString arg1, QCString arg2, long arg3)
+{
+ addCookies(arg1, arg2, arg3, true);
+}
+
+// DCOP function
+void
+KCookieServer::setDomainAdvice(QString url, QString advice)
+{
+ QString fqdn;
+ QString dummy;
+ if (KCookieJar::parseURL(url, fqdn, dummy))
+ {
+ QStringList domains;
+ mCookieJar->extractDomains(fqdn, domains);
+
+ mCookieJar->setDomainAdvice(domains[domains.count() > 3 ? 3 : 0],
+ KCookieJar::strToAdvice(advice));
+ // Save the cookie config if it has changed
+ mCookieJar->saveConfig( mConfig );
+ }
+}
+
+// DCOP function
+QString
+KCookieServer::getDomainAdvice(QString url)
+{
+ KCookieAdvice advice = KCookieDunno;
+ QString fqdn;
+ QString dummy;
+ if (KCookieJar::parseURL(url, fqdn, dummy))
+ {
+ QStringList domains;
+ mCookieJar->extractDomains(fqdn, domains);
+
+ QStringList::ConstIterator it = domains.begin();
+ while ( (advice == KCookieDunno) && (it != domains.end()) )
+ {
+ // Always check advice in both ".domain" and "domain". Note
+ // that we only want to check "domain" if it matches the
+ // fqdn of the requested URL.
+ if ( (*it)[0] == '.' || (*it) == fqdn )
+ advice = mCookieJar->getDomainAdvice(*it);
+ ++it;
+ }
+ if (advice == KCookieDunno)
+ advice = mCookieJar->getGlobalAdvice();
+ }
+ return KCookieJar::adviceToStr(advice);
+}
+
+// DCOP function
+void
+KCookieServer::reloadPolicy()
+{
+ mCookieJar->loadConfig( mConfig, true );
+}
+
+// DCOP function
+void
+KCookieServer::shutdown()
+{
+ deleteLater();
+}
+
+#include "kcookieserver.moc"
+
diff --git a/kioslave/http/kcookiejar/kcookieserver.h b/kioslave/http/kcookiejar/kcookieserver.h
new file mode 100644
index 000000000..bcd7fa530
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookieserver.h
@@ -0,0 +1,98 @@
+/*
+ This file is part of the KDE File Manager
+
+ Copyright (C) 1998 Waldo Bastian (bastian@kde.org)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2 as published by the Free Software Foundation.
+
+ This software 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this library; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+//----------------------------------------------------------------------------
+//
+// KDE Cookie Server
+// $Id$
+
+#ifndef KCOOKIESERVER_H
+#define KCOOKIESERVER_H
+
+#include <qstringlist.h>
+#include <kded/kdedmodule.h>
+
+class KHttpCookieList;
+class KCookieJar;
+class KHttpCookie;
+class QTimer;
+class RequestList;
+class DCOPClient;
+class KConfig;
+
+class KCookieServer : public KDEDModule
+{
+ Q_OBJECT
+ K_DCOP
+public:
+ KCookieServer(const QCString &);
+ ~KCookieServer();
+
+k_dcop:
+ QString findCookies(QString);
+ QString findCookies(QString, long);
+ QStringList findDomains();
+ QStringList findCookies(QValueList<int>,QString,QString,QString,QString);
+ QString findDOMCookies(QString);
+ QString findDOMCookies(QString, long);
+ void addCookies(QString, QCString, long);
+ void deleteCookie(QString, QString, QString, QString);
+ void deleteCookiesFromDomain(QString);
+ void deleteSessionCookies(long);
+ void deleteSessionCookiesFor(QString, long);
+ void deleteAllCookies();
+ void addDOMCookies(QString, QCString, long);
+ /**
+ * Sets the cookie policy for the domain associated with the specified URL.
+ */
+ void setDomainAdvice(QString url, QString advice);
+ /**
+ * Returns the cookie policy in effect for the specified URL.
+ */
+ QString getDomainAdvice(QString url);
+ void reloadPolicy();
+ void shutdown();
+
+public:
+ bool cookiesPending(const QString &url, KHttpCookieList *cookieList=0);
+ void addCookies(const QString &url, const QCString &cookieHeader,
+ long windowId, bool useDOMFormat);
+ void checkCookies(KHttpCookieList *cookieList);
+
+public slots:
+ void slotSave();
+ void slotDeleteSessionCookies(long);
+
+protected:
+ KCookieJar *mCookieJar;
+ KHttpCookieList *mPendingCookies;
+ RequestList *mRequestList;
+ QTimer *mTimer;
+ bool mAdvicePending;
+ DCOPClient *mOldCookieServer;
+ KConfig *mConfig;
+
+private:
+ virtual int newInstance(QValueList<QCString>) { return 0; }
+ bool cookieMatches(KHttpCookie*, QString, QString, QString, QString);
+ void putCookie(QStringList&, KHttpCookie*, const QValueList<int>&);
+ void saveCookieJar();
+};
+
+#endif
diff --git a/kioslave/http/kcookiejar/kcookiewin.cpp b/kioslave/http/kcookiejar/kcookiewin.cpp
new file mode 100644
index 000000000..5c68f8c1e
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookiewin.cpp
@@ -0,0 +1,382 @@
+/*
+This file is part of KDE
+
+ Copyright (C) 2000- Waldo Bastian <bastian@kde.org>
+ Copyright (C) 2000- Dawit Alemayehu <adawit@kde.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+//----------------------------------------------------------------------------
+//
+// KDE File Manager -- HTTP Cookie Dialogs
+// $Id$
+
+// The purpose of the QT_NO_TOOLTIP and QT_NO_WHATSTHIS ifdefs is because
+// this file is also used in Konqueror/Embedded. One of the aims of
+// Konqueror/Embedded is to be a small as possible to fit on embedded
+// devices. For this it's also useful to strip out unneeded features of
+// Qt, like for example QToolTip or QWhatsThis. The availability (or the
+// lack thereof) can be determined using these preprocessor defines.
+// The same applies to the QT_NO_ACCEL ifdef below. I hope it doesn't make
+// too much trouble... (Simon)
+
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qaccel.h>
+#include <qlabel.h>
+#include <qwidget.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qdatetime.h>
+#include <qmessagebox.h>
+#include <qpushbutton.h>
+#include <qradiobutton.h>
+#include <qvbuttongroup.h>
+
+#ifndef QT_NO_TOOLTIP
+#include <qtooltip.h>
+#endif
+
+#ifndef QT_NO_WHATSTHIS
+#include <qwhatsthis.h>
+#endif
+
+#include <kidna.h>
+#include <kwin.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kurllabel.h>
+#include <klineedit.h>
+#include <kiconloader.h>
+#include <kapplication.h>
+
+#ifdef Q_WS_X11
+#include <X11/Xlib.h>
+#endif
+
+#include "kcookiejar.h"
+#include "kcookiewin.h"
+
+KCookieWin::KCookieWin( QWidget *parent, KHttpCookieList cookieList,
+ int defaultButton, bool showDetails )
+ :KDialog( parent, "cookiealert", true )
+{
+#ifndef Q_WS_QWS //FIXME(E): Implement for Qt Embedded
+ setCaption( i18n("Cookie Alert") );
+ setIcon( SmallIcon("cookie") );
+ // all cookies in the list should have the same window at this time, so let's take the first
+# ifdef Q_WS_X11
+ if( cookieList.first()->windowIds().count() > 0 )
+ {
+ XSetTransientForHint( qt_xdisplay(), winId(), cookieList.first()->windowIds().first());
+ }
+ else
+ {
+ // No window associated... make sure the user notices our dialog.
+ KWin::setState( winId(), NET::KeepAbove );
+ kapp->updateUserTimestamp();
+ }
+# endif
+#endif
+ // Main widget's layout manager...
+ QVBoxLayout* vlayout = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+ vlayout->setResizeMode( QLayout::Fixed );
+
+ // Cookie image and message to user
+ QHBox* hBox = new QHBox( this );
+ hBox->setSpacing( KDialog::spacingHint() );
+ QLabel* icon = new QLabel( hBox );
+ icon->setPixmap( QMessageBox::standardIcon(QMessageBox::Warning) );
+ icon->setAlignment( Qt::AlignCenter );
+ icon->setFixedSize( 2*icon->sizeHint() );
+
+ int count = cookieList.count();
+
+ QVBox* vBox = new QVBox( hBox );
+ QString txt = i18n("You received a cookie from",
+ "You received %n cookies from", count);
+ QLabel* lbl = new QLabel( txt, vBox );
+ lbl->setAlignment( Qt::AlignCenter );
+ KHttpCookiePtr cookie = cookieList.first();
+
+ QString host (cookie->host());
+ int pos = host.find(':');
+ if ( pos > 0 )
+ {
+ QString portNum = host.left(pos);
+ host.remove(0, pos+1);
+ host += ':';
+ host += portNum;
+ }
+
+ txt = QString("<b>%1</b>").arg( KIDNA::toUnicode(host) );
+ if (cookie->isCrossDomain())
+ txt += i18n(" <b>[Cross Domain!]</b>");
+ lbl = new QLabel( txt, vBox );
+ lbl->setAlignment( Qt::AlignCenter );
+ lbl = new QLabel( i18n("Do you want to accept or reject?"), vBox );
+ lbl->setAlignment( Qt::AlignCenter );
+ vlayout->addWidget( hBox, 0, Qt::AlignLeft );
+
+ // Cookie Details dialog...
+ m_detailView = new KCookieDetail( cookieList, count, this );
+ vlayout->addWidget( m_detailView );
+ m_showDetails = showDetails;
+ m_showDetails ? m_detailView->show():m_detailView->hide();
+
+ // Cookie policy choice...
+ m_btnGrp = new QVButtonGroup( i18n("Apply Choice To"), this );
+ m_btnGrp->setRadioButtonExclusive( true );
+
+ txt = (count == 1)? i18n("&Only this cookie") : i18n("&Only these cookies");
+ QRadioButton* rb = new QRadioButton( txt, m_btnGrp );
+#ifndef QT_NO_WHATSTHIS
+ QWhatsThis::add( rb, i18n("Select this option to accept/reject only this cookie. "
+ "You will be prompted if another cookie is received. "
+ "<em>(see WebBrowsing/Cookies in the Control Center)</em>." ) );
+#endif
+ m_btnGrp->insert( rb );
+ rb = new QRadioButton( i18n("All cookies from this do&main"), m_btnGrp );
+#ifndef QT_NO_WHATSTHIS
+ QWhatsThis::add( rb, i18n("Select this option to accept/reject all cookies from "
+ "this site. Choosing this option will add a new policy for "
+ "the site this cookie originated from. This policy will be "
+ "permanent until you manually change it from the Control Center "
+ "<em>(see WebBrowsing/Cookies in the Control Center)</em>.") );
+#endif
+ m_btnGrp->insert( rb );
+ rb = new QRadioButton( i18n("All &cookies"), m_btnGrp );
+#ifndef QT_NO_WHATSTHIS
+ QWhatsThis::add( rb, i18n("Select this option to accept/reject all cookies from "
+ "anywhere. Choosing this option will change the global "
+ "cookie policy set in the Control Center for all cookies "
+ "<em>(see WebBrowsing/Cookies in the Control Center)</em>.") );
+#endif
+ m_btnGrp->insert( rb );
+ vlayout->addWidget( m_btnGrp );
+
+ if ( defaultButton > -1 && defaultButton < 3 )
+ m_btnGrp->setButton( defaultButton );
+ else
+ m_btnGrp->setButton( 1 );
+
+ // Accept/Reject buttons
+ QWidget* bbox = new QWidget( this );
+ QBoxLayout* bbLay = new QHBoxLayout( bbox );
+ bbLay->setSpacing( KDialog::spacingHint() );
+ QPushButton* btn = new QPushButton( i18n("&Accept"), bbox );
+ btn->setDefault( true );
+ btn->setFocus();
+ connect( btn, SIGNAL(clicked()), SLOT(accept()) );
+ bbLay->addWidget( btn );
+ btn = new QPushButton( i18n("&Reject"), bbox );
+ connect( btn, SIGNAL(clicked()), SLOT(reject()) );
+ bbLay->addWidget( btn );
+ bbLay->addStretch( 1 );
+#ifndef QT_NO_ACCEL
+ QAccel* a = new QAccel( this );
+ a->connectItem( a->insertItem(Qt::Key_Escape), btn, SLOT(animateClick()) );
+#endif
+
+ m_button = new QPushButton( bbox );
+ m_button->setText( m_showDetails ? i18n("&Details <<"):i18n("&Details >>") );
+ connect( m_button, SIGNAL(clicked()), SLOT(slotCookieDetails()) );
+ bbLay->addWidget( m_button );
+#ifndef QT_NO_WHATSTHIS
+ QWhatsThis::add( m_button, i18n("See or modify the cookie information") );
+#endif
+
+
+ vlayout->addWidget( bbox );
+ setFixedSize( sizeHint() );
+}
+
+KCookieWin::~KCookieWin()
+{
+}
+
+void KCookieWin::slotCookieDetails()
+{
+ if ( m_detailView->isVisible() )
+ {
+ m_detailView->setMaximumSize( 0, 0 );
+ m_detailView->adjustSize();
+ m_detailView->hide();
+ m_button->setText( i18n( "&Details >>" ) );
+ m_showDetails = false;
+ }
+ else
+ {
+ m_detailView->setMaximumSize( 1000, 1000 );
+ m_detailView->adjustSize();
+ m_detailView->show();
+ m_button->setText( i18n( "&Details <<" ) );
+ m_showDetails = true;
+ }
+}
+
+KCookieAdvice KCookieWin::advice( KCookieJar *cookiejar, KHttpCookie* cookie )
+{
+ int result = exec();
+
+ cookiejar->setShowCookieDetails ( m_showDetails );
+
+ KCookieAdvice advice = (result==QDialog::Accepted) ? KCookieAccept:KCookieReject;
+
+ int preferredPolicy = m_btnGrp->id( m_btnGrp->selected() );
+ cookiejar->setPreferredDefaultPolicy( preferredPolicy );
+
+ switch ( preferredPolicy )
+ {
+ case 2:
+ cookiejar->setGlobalAdvice( advice );
+ break;
+ case 1:
+ cookiejar->setDomainAdvice( cookie, advice );
+ break;
+ case 0:
+ default:
+ break;
+ }
+ return advice;
+}
+
+KCookieDetail::KCookieDetail( KHttpCookieList cookieList, int cookieCount,
+ QWidget* parent, const char* name )
+ :QGroupBox( parent, name )
+{
+ setTitle( i18n("Cookie Details") );
+ QGridLayout* grid = new QGridLayout( this, 9, 2,
+ KDialog::spacingHint(),
+ KDialog::marginHint() );
+ grid->addRowSpacing( 0, fontMetrics().lineSpacing() );
+ grid->setColStretch( 1, 3 );
+
+ QLabel* label = new QLabel( i18n("Name:"), this );
+ grid->addWidget( label, 1, 0 );
+ m_name = new KLineEdit( this );
+ m_name->setReadOnly( true );
+ m_name->setMaximumWidth( fontMetrics().maxWidth() * 25 );
+ grid->addWidget( m_name, 1 ,1 );
+
+ //Add the value
+ label = new QLabel( i18n("Value:"), this );
+ grid->addWidget( label, 2, 0 );
+ m_value = new KLineEdit( this );
+ m_value->setReadOnly( true );
+ m_value->setMaximumWidth( fontMetrics().maxWidth() * 25 );
+ grid->addWidget( m_value, 2, 1);
+
+ label = new QLabel( i18n("Expires:"), this );
+ grid->addWidget( label, 3, 0 );
+ m_expires = new KLineEdit( this );
+ m_expires->setReadOnly( true );
+ m_expires->setMaximumWidth(fontMetrics().maxWidth() * 25 );
+ grid->addWidget( m_expires, 3, 1);
+
+ label = new QLabel( i18n("Path:"), this );
+ grid->addWidget( label, 4, 0 );
+ m_path = new KLineEdit( this );
+ m_path->setReadOnly( true );
+ m_path->setMaximumWidth( fontMetrics().maxWidth() * 25 );
+ grid->addWidget( m_path, 4, 1);
+
+ label = new QLabel( i18n("Domain:"), this );
+ grid->addWidget( label, 5, 0 );
+ m_domain = new KLineEdit( this );
+ m_domain->setReadOnly( true );
+ m_domain->setMaximumWidth( fontMetrics().maxWidth() * 25 );
+ grid->addWidget( m_domain, 5, 1);
+
+ label = new QLabel( i18n("Exposure:"), this );
+ grid->addWidget( label, 6, 0 );
+ m_secure = new KLineEdit( this );
+ m_secure->setReadOnly( true );
+ m_secure->setMaximumWidth( fontMetrics().maxWidth() * 25 );
+ grid->addWidget( m_secure, 6, 1 );
+
+ if ( cookieCount > 1 )
+ {
+ QPushButton* btnNext = new QPushButton( i18n("Next cookie","&Next >>"), this );
+ btnNext->setFixedSize( btnNext->sizeHint() );
+ grid->addMultiCellWidget( btnNext, 8, 8, 0, 1 );
+ connect( btnNext, SIGNAL(clicked()), SLOT(slotNextCookie()) );
+#ifndef QT_NO_TOOLTIP
+ QToolTip::add( btnNext, i18n("Show details of the next cookie") );
+#endif
+ }
+ m_cookieList = cookieList;
+ m_cookie = 0;
+ slotNextCookie();
+}
+
+KCookieDetail::~KCookieDetail()
+{
+}
+
+void KCookieDetail::slotNextCookie()
+{
+ KHttpCookiePtr cookie = m_cookieList.first();
+ if (m_cookie) while(cookie)
+ {
+ if (cookie == m_cookie)
+ {
+ cookie = m_cookieList.next();
+ break;
+ }
+ cookie = m_cookieList.next();
+ }
+ m_cookie = cookie;
+ if (!m_cookie)
+ m_cookie = m_cookieList.first();
+
+ if ( m_cookie )
+ {
+ m_name->setText( m_cookie->name() );
+ m_value->setText( ( m_cookie->value() ) );
+ if ( m_cookie->domain().isEmpty() )
+ m_domain->setText( i18n("Not specified") );
+ else
+ m_domain->setText( m_cookie->domain() );
+ m_path->setText( m_cookie->path() );
+ QDateTime cookiedate;
+ cookiedate.setTime_t( m_cookie->expireDate() );
+ if ( m_cookie->expireDate() )
+ m_expires->setText( KGlobal::locale()->formatDateTime(cookiedate) );
+ else
+ m_expires->setText( i18n("End of Session") );
+ QString sec;
+ if (m_cookie->isSecure())
+ {
+ if (m_cookie->isHttpOnly())
+ sec = i18n("Secure servers only");
+ else
+ sec = i18n("Secure servers, page scripts");
+ }
+ else
+ {
+ if (m_cookie->isHttpOnly())
+ sec = i18n("Servers");
+ else
+ sec = i18n("Servers, page scripts");
+ }
+ m_secure->setText( sec );
+ }
+}
+
+#include "kcookiewin.moc"
diff --git a/kioslave/http/kcookiejar/kcookiewin.h b/kioslave/http/kcookiejar/kcookiewin.h
new file mode 100644
index 000000000..30e92e7e0
--- /dev/null
+++ b/kioslave/http/kcookiejar/kcookiewin.h
@@ -0,0 +1,84 @@
+/*
+ This file is part of the KDE File Manager
+
+ Copyright (C) 1998- Waldo Bastian (bastian@kde.org)
+ Copyright (C) 2000- Dawit Alemayehu (adawit@kde.org)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation; either version 2
+ of the License, or (at your option) any later version.
+
+ This software 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this library; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+//----------------------------------------------------------------------------
+//
+// KDE File Manager -- HTTP Cookie Dialogs
+// $Id$
+
+#ifndef _KCOOKIEWIN_H_
+#define _KCOOKIEWIN_H_
+
+#include <qgroupbox.h>
+
+#include <kdialog.h>
+#include "kcookiejar.h"
+
+class KLineEdit;
+class QPushButton;
+class QVButtonGroup;
+class KURLLabel;
+
+class KCookieDetail : public QGroupBox
+{
+ Q_OBJECT
+
+public :
+ KCookieDetail( KHttpCookieList cookieList, int cookieCount, QWidget *parent=0,
+ const char *name=0 );
+ ~KCookieDetail();
+
+private :
+ KLineEdit* m_name;
+ KLineEdit* m_value;
+ KLineEdit* m_expires;
+ KLineEdit* m_domain;
+ KLineEdit* m_path;
+ KLineEdit* m_secure;
+
+ KHttpCookieList m_cookieList;
+ KHttpCookiePtr m_cookie;
+
+private slots:
+ void slotNextCookie();
+};
+
+class KCookieWin : public KDialog
+{
+ Q_OBJECT
+
+public :
+ KCookieWin( QWidget *parent, KHttpCookieList cookieList, int defaultButton=0,
+ bool showDetails=false );
+ ~KCookieWin();
+
+ KCookieAdvice advice( KCookieJar *cookiejar, KHttpCookie* cookie );
+
+private :
+ QPushButton* m_button;
+ QVButtonGroup* m_btnGrp;
+ KCookieDetail* m_detailView;
+ bool m_showDetails;
+
+private slots:
+ void slotCookieDetails();
+};
+#endif
diff --git a/kioslave/http/kcookiejar/main.cpp b/kioslave/http/kcookiejar/main.cpp
new file mode 100644
index 000000000..1e943b939
--- /dev/null
+++ b/kioslave/http/kcookiejar/main.cpp
@@ -0,0 +1,92 @@
+/*
+This file is part of KDE
+
+ Copyright (C) 1998-2000 Waldo Bastian (bastian@kde.org)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <dcopclient.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+#include <kapplication.h>
+
+static const char description[] =
+ I18N_NOOP("HTTP Cookie Daemon");
+
+static const char version[] = "1.0";
+
+static const KCmdLineOptions options[] =
+{
+ { "shutdown", I18N_NOOP("Shut down cookie jar"), 0 },
+ { "remove <domain>", I18N_NOOP("Remove cookies for domain"), 0 },
+ { "remove-all", I18N_NOOP("Remove all cookies"), 0 },
+ { "reload-config", I18N_NOOP("Reload configuration file"), 0 },
+ KCmdLineLastOption
+};
+
+extern "C" KDE_EXPORT int kdemain(int argc, char *argv[])
+{
+ KLocale::setMainCatalogue("kdelibs");
+ KCmdLineArgs::init(argc, argv, "kcookiejar", I18N_NOOP("HTTP cookie daemon"),
+ description, version);
+
+ KCmdLineArgs::addCmdLineOptions( options );
+
+ KInstance a("kcookiejar");
+
+ kapp->dcopClient()->attach();
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ QCString replyType;
+ QByteArray replyData;
+ if (args->isSet("remove-all"))
+ {
+ kapp->dcopClient()->call( "kded", "kcookiejar", "deleteAllCookies()", QByteArray(), replyType, replyData);
+ }
+ if (args->isSet("remove"))
+ {
+ QString domain = args->getOption("remove");
+ QByteArray params;
+ QDataStream stream(params, IO_WriteOnly);
+ stream << domain;
+ kapp->dcopClient()->call( "kded", "kcookiejar", "deleteCookiesFromDomain(QString)", params, replyType, replyData);
+ }
+ if (args->isSet("shutdown"))
+ {
+ QCString module = "kcookiejar";
+ QByteArray params;
+ QDataStream stream(params, IO_WriteOnly);
+ stream << module;
+ kapp->dcopClient()->call( "kded", "kded", "unloadModule(QCString)", params, replyType, replyData);
+ }
+ else if(args->isSet("reload-config"))
+ {
+ kapp->dcopClient()->call( "kded", "kcookiejar", "reloadPolicy()", QByteArray(), replyType, replyData);
+ }
+ else
+ {
+ QCString module = "kcookiejar";
+ QByteArray params;
+ QDataStream stream(params, IO_WriteOnly);
+ stream << module;
+ kapp->dcopClient()->call( "kded", "kded", "loadModule(QCString)", params, replyType, replyData);
+ }
+
+ return 0;
+}
diff --git a/kioslave/http/kcookiejar/netscape_cookie_spec.html b/kioslave/http/kcookiejar/netscape_cookie_spec.html
new file mode 100644
index 000000000..eb190f2e3
--- /dev/null
+++ b/kioslave/http/kcookiejar/netscape_cookie_spec.html
@@ -0,0 +1,331 @@
+<HTML>
+<HEAD>
+<TITLE>Client Side State - HTTP Cookies</TITLE>
+</HEAD>
+
+<BODY BGCOLOR="#ffffff" LINK="#0000ff" VLINK="#ff0000" ALINK="#ff0000" TEXT="#000000" >
+
+
+<CENTER>
+<!-- BANNER:s3 -->
+<A HREF="/maps/banners/documentation_s3.map"><IMG SRC="/images/banners/documentation_s3.gif" ALT="Documentation" BORDER=0 WIDTH=612 HEIGHT=50 ISMAP USEMAP="#banner_nav"></A>
+<MAP NAME="banner_nav">
+<AREA SHAPE=RECT COORDS="62,11,91,40" HREF="/">
+<AREA SHAPE=RECT COORDS="153,41,221,50" HREF="/">
+<AREA SHAPE=RECT COORDS="298,8,374,34" HREF="/support/index.html">
+<AREA SHAPE=RECT COORDS="381,15,586,43" HREF="http://help.netscape.com/browse/index.html">
+<AREA SHAPE=default NOHREF>
+</MAP>
+
+<!-- BANNER:s3 -->
+
+<H2>
+<FONT SIZE=+3>P</FONT>ERSISTENT
+<FONT SIZE=+3>C</FONT>LIENT
+<FONT SIZE=+3>S</FONT>TATE<BR>
+<FONT SIZE=+3>HTTP C</FONT>OOKIES
+</H2>
+
+<H3>Preliminary Specification - Use with caution</H3>
+</CENTER>
+
+<HR SIZE=4>
+
+<CENTER>
+<H3>
+<FONT SIZE=+2>I</FONT>NTRODUCTION
+</H3>
+</CENTER>
+
+Cookies are a general mechanism which server side connections (such as
+CGI scripts) can use to both store and retrieve information on the
+client side of the connection. The addition of a simple, persistent,
+client-side state significantly extends the capabilities of Web-based
+client/server applications.<P>
+
+<CENTER>
+<H3>
+<FONT SIZE=+2>O</FONT>VERVIEW
+</H3>
+</CENTER>
+
+A server, when returning an HTTP object to a client, may also send a
+piece of state information which the client will store. Included in that
+state object is a description of the range of URLs for which that state is
+valid. Any future HTTP requests made by the client which fall in that
+range will include a transmittal of the current value of the state
+object from the client back to the server. The state object is called
+a <B>cookie</B>, for no compelling reason. <P>
+This simple mechanism provides a powerful new tool which enables a host
+of new types of applications to be written for web-based environments.
+Shopping applications can now store information about the currently
+selected items, for fee services can send back registration information
+and free the client from retyping a user-id on next connection,
+sites can store per-user preferences on the client, and have the client supply
+those preferences every time that site is connected to.
+
+<CENTER>
+<H3>
+<FONT SIZE=+2>S</FONT>PECIFICATION
+</H3>
+</CENTER>
+
+A cookie is introduced to the client by including a <B>Set-Cookie</B>
+header as part of an HTTP response, typically this will be generated
+by a CGI script.
+
+<H3>Syntax of the Set-Cookie HTTP Response Header</H3>
+
+This is the format a CGI script would use to add to the HTTP headers
+a new piece of data which is to be stored by the client for later retrieval.
+
+<PRE>
+Set-Cookie: <I>NAME</I>=<I>VALUE</I>; expires=<I>DATE</I>;
+path=<I>PATH</I>; domain=<I>DOMAIN_NAME</I>; secure
+</PRE>
+<DL>
+<DT> <I>NAME</I>=<I>VALUE</I><DD>
+This string is a sequence of characters excluding semi-colon, comma and white
+space. If there is a need to place such data in the name or value, some
+encoding method such as URL style %XX encoding is recommended, though no
+encoding is defined or required. <P> This is the only required attribute
+on the <B>Set-Cookie</B> header. <P>
+<DT><B>expires</B>=<I>DATE</I>
+<DD>
+The <B>expires</B> attribute specifies a date string that
+defines the valid life time of that cookie. Once the expiration
+date has been reached, the cookie will no longer be stored or
+given out. <P>
+The date string is formatted as:
+<BLOCKQUOTE> <TT>Wdy, DD-Mon-YYYY HH:MM:SS GMT</TT></BLOCKQUOTE>
+This is based on
+<A TARGET="_top" HREF="http://ds.internic.net/rfc/rfc822.txt">RFC 822</A>,
+<A TARGET="_top" HREF="http://ds.internic.net/rfc/rfc850.txt">RFC 850</A>,
+<A TARGET="_top" HREF="http://www.w3.org/hypertext/WWW/Protocols/rfc1036/rfc1036.html#z6">
+RFC 1036</A>, and
+<A TARGET="_top" HREF="http://ds1.internic.net/rfc/rfc1123.txt">
+RFC 1123</A>,
+with the variations that the only legal time zone is <B>GMT</B> and
+the separators between the elements of the date must be dashes.
+<P>
+<B>expires</B> is an optional attribute. If not specified, the cookie will
+expire when the user's session ends. <P>
+<B>Note:</B> There is a bug in Netscape Navigator version 1.1 and earlier.
+Only cookies whose <B>path</B> attribute is set explicitly to "/" will
+be properly saved between sessions if they have an <B>expires</B>
+attribute.<P>
+
+<DT> <B>domain</B>=<I>DOMAIN_NAME</I>
+<DD>
+When searching the cookie list for valid cookies, a comparison of the
+<B>domain</B>
+attributes of the cookie is made with the Internet domain name of the
+host from which the URL will be fetched. If there is a tail match,
+then the cookie will go through <B>path</B> matching to see if it
+should be sent. "Tail matching" means that <B>domain</B> attribute
+is matched against the tail of the fully qualified domain name of
+the host. A <B>domain</B> attribute of "acme.com" would match
+host names "anvil.acme.com" as well as "shipping.crate.acme.com". <P>
+
+Only hosts within the specified domain
+can set a cookie for a domain and domains must have at least two (2)
+or three (3) periods in them to prevent domains of the form:
+".com", ".edu", and "va.us". Any domain that fails within
+one of the seven special top level domains listed below only require
+two periods. Any other domain requires at least three. The
+seven special top level domains are: "COM", "EDU", "NET", "ORG",
+"GOV", "MIL", and "INT".
+
+ <P>
+The default value of <B>domain</B> is the host name of the server
+which generated the cookie response. <P>
+<DT> <B>path</B>=<I>PATH</I>
+<DD>
+The <B>path</B> attribute is used to specify the subset of URLs in a
+domain for
+which the cookie is valid. If a cookie has already passed <B>domain</B>
+matching, then the pathname component
+of the URL is compared with the path attribute, and if there is
+a match, the cookie is considered valid and is sent along with
+the URL request. The path "/foo"
+would match "/foobar" and "/foo/bar.html". The path "/" is the most
+general path. <P>
+If the <B>path</B> is not specified, it as assumed to be the same path
+as the document being described by the header which contains the cookie.
+<P>
+<DT> <B>secure</B>
+<DD>
+If a cookie is marked <B>secure</B>, it will only be transmitted if the
+communications channel with the host is a secure one. Currently
+this means that secure cookies will only be sent to HTTPS (HTTP over SSL)
+servers. <P>
+If <B>secure</B> is not specified, a cookie is considered safe to be sent
+in the clear over unsecured channels.
+</DL>
+
+<H3>Syntax of the Cookie HTTP Request Header</H3>
+
+When requesting a URL from an HTTP server, the browser will match
+the URL against all cookies and if any of them match, a line
+containing the name/value pairs of all matching cookies will
+be included in the HTTP request. Here is the format of that line:
+<PRE>
+Cookie: <I>NAME1=OPAQUE_STRING1</I>; <I>NAME2=OPAQUE_STRING2 ...</I>
+</PRE>
+
+<H3>Additional Notes</H3>
+
+<UL>
+<LI>Multiple <B>Set-Cookie</B> headers can be issued in a single server
+response.
+<p>
+<LI>Instances of the same path and name will overwrite each other, with the
+latest instance taking precedence. Instances of the same path but
+different names will add additional mappings.
+<p>
+<LI>Setting the path to a higher-level value does not override other more
+specific path mappings. If there are multiple matches for a given cookie
+name, but with separate paths, all the matching cookies will be sent.
+(See examples below.)
+<p>
+<LI>The
+expires header lets the client know when it is safe to purge the mapping
+but the client is not required to do so. A client may also delete a
+cookie before it's expiration date arrives if the number of cookies
+exceeds its internal limits.
+<p>
+<LI>When sending cookies to a server, all cookies with a more specific
+path mapping should be sent before cookies with less specific path
+mappings. For example, a cookie "name1=foo" with a path mapping
+of "/" should be sent after a cookie "name1=foo2" with
+a path mapping of "/bar" if they are both to be sent.
+<p>
+<LI>There are limitations on the number of cookies that a client
+can store at any one time. This is a specification of the minimum
+number of cookies that a client should be prepared to receive and
+store.
+
+<UL>
+ <LI>300 total cookies
+ <LI>4 kilobytes per cookie, where the name and the OPAQUE_STRING
+ combine to form the 4 kilobyte limit.
+ <LI>20 cookies per server or domain. (note that completely
+ specified hosts and domains are treated as separate entities
+ and have a 20 cookie limitation for each, not combined)
+</UL>
+Servers should not expect clients to be able to exceed these limits.
+When the 300 cookie limit or the 20 cookie per server limit
+is exceeded, clients should delete the least recently used cookie.
+When a cookie larger than 4 kilobytes is encountered the cookie
+should be trimmed to fit, but the name should remain intact
+as long as it is less than 4 kilobytes.
+ <P>
+<LI>If a CGI script wishes to delete a cookie, it can do so by
+returning a cookie with the same name, and an <B>expires</B> time
+which is in the past. The path and name must match exactly
+in order for the expiring cookie to replace the valid cookie.
+This requirement makes it difficult for anyone but the originator
+of a cookie to delete a cookie.
+<P><LI>When caching HTTP, as a proxy server might do, the <B>Set-cookie</B>
+response header should never be cached.
+<P><LI>If a proxy server receives a response which
+contains a <B>Set-cookie</B> header, it should propagate the <B>Set-cookie</B>
+header to the client, regardless of whether the response was 304
+(Not Modified) or 200 (OK).
+<P>Similarly, if a client request contains a Cookie: header, it
+should be forwarded through a proxy, even if the conditional
+If-modified-since request is being made.
+</UL>
+
+<CENTER>
+<H3>
+<FONT SIZE=+2>E</FONT>XAMPLES
+</H3>
+</CENTER>
+
+Here are some sample exchanges which are designed to illustrate the use
+of cookies.
+<H3>First Example transaction sequence:</H3>
+<DL>
+<dt>Client requests a document, and receives in the response:<dd>
+<PRE>
+Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT</PRE>
+<dt>When client requests a URL in path "/" on this server, it sends:<DD>
+<PRE>Cookie: CUSTOMER=WILE_E_COYOTE</PRE>
+<dt>Client requests a document, and receives in the response:<dd>
+<PRE>Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/</PRE>
+<dt>When client requests a URL in path "/" on this server, it sends:<dd>
+<PRE>Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001</PRE>
+<dt>Client receives:<dd>
+<PRE>Set-Cookie: SHIPPING=FEDEX; path=/foo</PRE>
+<dt>When client requests a URL in path "/" on this server, it sends:<dd>
+<PRE>Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001</PRE>
+<dt>When client requests a URL in path "/foo" on this server, it sends:<dd>
+<PRE>Cookie: CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX</PRE>
+</DL>
+<H3>Second Example transaction sequence:</H3>
+<DL>
+<dt>Assume all mappings from above have been cleared.<p>
+<dt>Client receives:<dd>
+<PRE>Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/</PRE>
+<dt>When client requests a URL in path "/" on this server, it sends:<dd>
+<PRE>Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001</PRE>
+<dt>Client receives:<dd>
+<PRE>Set-Cookie: PART_NUMBER=RIDING_ROCKET_0023; path=/ammo</PRE>
+<dt>When client requests a URL in path "/ammo" on this server, it sends:<dd>
+<PRE>Cookie: PART_NUMBER=RIDING_ROCKET_0023; PART_NUMBER=ROCKET_LAUNCHER_0001</PRE>
+<dd>NOTE: There are two name/value pairs named "PART_NUMBER" due to the
+inheritance
+of the "/" mapping in addition to the "/ammo" mapping.
+</DL>
+
+<HR SIZE=4>
+<P>
+
+<CENTER>
+
+
+<!-- footer -->
+<TABLE WIDTH=600 BORDER=0 CELLPADDING=0 CELLSPACING=0>
+<TR>
+<TD WIDTH=600 HEIGHT=8><HR SIZE=1 NOSHADE></TD></TR>
+<TR><TD ALIGN=LEFT VALIGN=TOP><FONT FACE="sans-serif, Arial, Helvetica" SIZE=-2><A HREF="http://home.netscape.com/misc/nav_redir/help.html" TARGET="_top">Help</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<A
+HREF="http://home.netscape.com/misc/nav_redir/site_map.html" TARGET="_top">Site&nbsp;Map</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<A
+HREF="http://home.netscape.com/misc/nav_redir/howtoget.html" TARGET="_top">How&nbsp;to&nbsp;Get&nbsp;Netscape&nbsp;Products</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<A HREF="http://home.netscape.com/misc/nav_redir/ad.html" TARGET="_top">Advertise&nbsp;With&nbsp;Us</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/addsite.html" TARGET="_top">Add Site</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<A HREF="http://home.netscape.com/misc/nav_redir/custom_browser.html" TARGET="_top">Custom Browser Program</A></FONT></TD></TR>
+<TR>
+<TD WIDTH=600 HEIGHT=8 COLSPAN=0></TD>
+</TR>
+
+<TR>
+<TD ALIGN=LEFT VALIGN=TOP>
+<!-- Channels -->
+<FONT FACE="sans-serif, Arial, Helvetica" SIZE=-2><A HREF="http://home.netscape.com/misc/nav_redir/channels/autos.html" TARGET="_top">Autos</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A
+HREF="http://home.netscape.com/misc/nav_redir/channels/business.html" TARGET="_top">Business</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/computers_internet.html" TARGET="_top">Computing&nbsp;&amp;&nbsp;Internet</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/entertainment.html" TARGET="_top">Entertainment</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A
+HREF="http://home.netscape.com/misc/nav_redir/channels/kids_family.html" TARGET="_top">Family</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A
+HREF="http://home.netscape.com/misc/nav_redir/channels/games.html" TARGET="_top">Games</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/health.html" TARGET="_top">Health</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/lifestyles.html" TARGET="_top">Lifestyles</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A
+HREF="http://home.netscape.com/misc/nav_redir/channels/local.html" TARGET="_top">Local</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/netscape.html" TARGET="_top">Netscape</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/open_directory.html">Netscape&nbsp;Open&nbsp;Directory</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A
+HREF="http://home.netscape.com/misc/nav_redir/channels/news.html" TARGET="_top">News</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/personalize_finance.html" TARGET="_top">Personal&nbsp;Finance</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A
+HREF="http://home.netscape.com/misc/nav_redir/channels/real_estate.html" TARGET="_top">Real Estate</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/education.html" TARGET="_top">Research&nbsp;&amp;&nbsp;Learn</A>&nbsp;&nbsp;&nbsp;|&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/shopping.html" TARGET="_top">Shopping</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/smallbiz.html" TARGET="_top">Small Business</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A
+HREF="http://home.netscape.com/misc/nav_redir/channels/sports.html" TARGET="_top">Sports</A>&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp; <A HREF="http://home.netscape.com/misc/nav_redir/channels/travel.html" TARGET="_top">Travel</A></FONT></TD></TR>
+</TABLE>
+
+<TABLE WIDTH=600 BORDER=0 CELLPADDING=0 CELLSPACING=0>
+<TR><TD WIDTH=600 HEIGHT=8 COLSPAN=0></TD></TR>
+<TR>
+<TD WIDTH=600 COLSPAN=5 VALIGN=TOP ALIGN=LEFT>
+<FONT FACE="sans-serif, Arial, Helvetica" SIZE=-2>
+&copy; 1999 Netscape, All Rights Reserved. <A HREF="http://home.netscape.com/legal_notices/index.html">Legal & Privacy Notices</A><BR>This site powered by <A HREF="http://home.netscape.com/comprod/server_central/index.html" TARGET="_top">Netscape SuiteSpot servers</A>.</FONT></TD>
+</TR>
+</TABLE>
+<!-- end footer -->
+
+
+
+
+</CENTER>
+<P>
+
+
+
+</BODY>
+</HTML> \ No newline at end of file
diff --git a/kioslave/http/kcookiejar/rfc2109 b/kioslave/http/kcookiejar/rfc2109
new file mode 100644
index 000000000..432fdcc6e
--- /dev/null
+++ b/kioslave/http/kcookiejar/rfc2109
@@ -0,0 +1,1179 @@
+
+
+
+
+
+
+Network Working Group D. Kristol
+Request for Comments: 2109 Bell Laboratories, Lucent Technologies
+Category: Standards Track L. Montulli
+ Netscape Communications
+ February 1997
+
+
+ HTTP State Management Mechanism
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+1. ABSTRACT
+
+ This document specifies a way to create a stateful session with HTTP
+ requests and responses. It describes two new headers, Cookie and
+ Set-Cookie, which carry state information between participating
+ origin servers and user agents. The method described here differs
+ from Netscape's Cookie proposal, but it can interoperate with
+ HTTP/1.0 user agents that use Netscape's method. (See the HISTORICAL
+ section.)
+
+2. TERMINOLOGY
+
+ The terms user agent, client, server, proxy, and origin server have
+ the same meaning as in the HTTP/1.0 specification.
+
+ Fully-qualified host name (FQHN) means either the fully-qualified
+ domain name (FQDN) of a host (i.e., a completely specified domain
+ name ending in a top-level domain such as .com or .uk), or the
+ numeric Internet Protocol (IP) address of a host. The fully
+ qualified domain name is preferred; use of numeric IP addresses is
+ strongly discouraged.
+
+ The terms request-host and request-URI refer to the values the client
+ would send to the server as, respectively, the host (but not port)
+ and abs_path portions of the absoluteURI (http_URL) of the HTTP
+ request line. Note that request-host must be a FQHN.
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 1]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ Hosts names can be specified either as an IP address or a FQHN
+ string. Sometimes we compare one host name with another. Host A's
+ name domain-matches host B's if
+
+ * both host names are IP addresses and their host name strings match
+ exactly; or
+
+ * both host names are FQDN strings and their host name strings match
+ exactly; or
+
+ * A is a FQDN string and has the form NB, where N is a non-empty name
+ string, B has the form .B', and B' is a FQDN string. (So, x.y.com
+ domain-matches .y.com but not y.com.)
+
+ Note that domain-match is not a commutative operation: a.b.c.com
+ domain-matches .c.com, but not the reverse.
+
+ Because it was used in Netscape's original implementation of state
+ management, we will use the term cookie to refer to the state
+ information that passes between an origin server and user agent, and
+ that gets stored by the user agent.
+
+3. STATE AND SESSIONS
+
+ This document describes a way to create stateful sessions with HTTP
+ requests and responses. Currently, HTTP servers respond to each
+ client request without relating that request to previous or
+ subsequent requests; the technique allows clients and servers that
+ wish to exchange state information to place HTTP requests and
+ responses within a larger context, which we term a "session". This
+ context might be used to create, for example, a "shopping cart", in
+ which user selections can be aggregated before purchase, or a
+ magazine browsing system, in which a user's previous reading affects
+ which offerings are presented.
+
+ There are, of course, many different potential contexts and thus many
+ different potential types of session. The designers' paradigm for
+ sessions created by the exchange of cookies has these key attributes:
+
+ 1. Each session has a beginning and an end.
+
+ 2. Each session is relatively short-lived.
+
+ 3. Either the user agent or the origin server may terminate a
+ session.
+
+ 4. The session is implicit in the exchange of state information.
+
+
+
+
+Kristol & Montulli Standards Track [Page 2]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+4. OUTLINE
+
+ We outline here a way for an origin server to send state information
+ to the user agent, and for the user agent to return the state
+ information to the origin server. The goal is to have a minimal
+ impact on HTTP and user agents. Only origin servers that need to
+ maintain sessions would suffer any significant impact, and that
+ impact can largely be confined to Common Gateway Interface (CGI)
+ programs, unless the server provides more sophisticated state
+ management support. (See Implementation Considerations, below.)
+
+4.1 Syntax: General
+
+ The two state management headers, Set-Cookie and Cookie, have common
+ syntactic properties involving attribute-value pairs. The following
+ grammar uses the notation, and tokens DIGIT (decimal digits) and
+ token (informally, a sequence of non-special, non-white space
+ characters) from the HTTP/1.1 specification [RFC 2068] to describe
+ their syntax.
+
+ av-pairs = av-pair *(";" av-pair)
+ av-pair = attr ["=" value] ; optional value
+ attr = token
+ value = word
+ word = token | quoted-string
+
+ Attributes (names) (attr) are case-insensitive. White space is
+ permitted between tokens. Note that while the above syntax
+ description shows value as optional, most attrs require them.
+
+ NOTE: The syntax above allows whitespace between the attribute and
+ the = sign.
+
+4.2 Origin Server Role
+
+4.2.1 General
+
+ The origin server initiates a session, if it so desires. (Note that
+ "session" here does not refer to a persistent network connection but
+ to a logical session created from HTTP requests and responses. The
+ presence or absence of a persistent connection should have no effect
+ on the use of cookie-derived sessions). To initiate a session, the
+ origin server returns an extra response header to the client, Set-
+ Cookie. (The details follow later.)
+
+ A user agent returns a Cookie request header (see below) to the
+ origin server if it chooses to continue a session. The origin server
+ may ignore it or use it to determine the current state of the
+
+
+
+Kristol & Montulli Standards Track [Page 3]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ session. It may send back to the client a Set-Cookie response header
+ with the same or different information, or it may send no Set-Cookie
+ header at all. The origin server effectively ends a session by
+ sending the client a Set-Cookie header with Max-Age=0.
+
+ Servers may return a Set-Cookie response headers with any response.
+ User agents should send Cookie request headers, subject to other
+ rules detailed below, with every request.
+
+ An origin server may include multiple Set-Cookie headers in a
+ response. Note that an intervening gateway could fold multiple such
+ headers into a single header.
+
+4.2.2 Set-Cookie Syntax
+
+ The syntax for the Set-Cookie response header is
+
+ set-cookie = "Set-Cookie:" cookies
+ cookies = 1#cookie
+ cookie = NAME "=" VALUE *(";" cookie-av)
+ NAME = attr
+ VALUE = value
+ cookie-av = "Comment" "=" value
+ | "Domain" "=" value
+ | "Max-Age" "=" value
+ | "Path" "=" value
+ | "Secure"
+ | "Version" "=" 1*DIGIT
+
+ Informally, the Set-Cookie response header comprises the token Set-
+ Cookie:, followed by a comma-separated list of one or more cookies.
+ Each cookie begins with a NAME=VALUE pair, followed by zero or more
+ semi-colon-separated attribute-value pairs. The syntax for
+ attribute-value pairs was shown earlier. The specific attributes and
+ the semantics of their values follows. The NAME=VALUE attribute-
+ value pair must come first in each cookie. The others, if present,
+ can occur in any order. If an attribute appears more than once in a
+ cookie, the behavior is undefined.
+
+ NAME=VALUE
+ Required. The name of the state information ("cookie") is NAME,
+ and its value is VALUE. NAMEs that begin with $ are reserved for
+ other uses and must not be used by applications.
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 4]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ The VALUE is opaque to the user agent and may be anything the
+ origin server chooses to send, possibly in a server-selected
+ printable ASCII encoding. "Opaque" implies that the content is of
+ interest and relevance only to the origin server. The content
+ may, in fact, be readable by anyone that examines the Set-Cookie
+ header.
+
+ Comment=comment
+ Optional. Because cookies can contain private information about a
+ user, the Cookie attribute allows an origin server to document its
+ intended use of a cookie. The user can inspect the information to
+ decide whether to initiate or continue a session with this cookie.
+
+ Domain=domain
+ Optional. The Domain attribute specifies the domain for which the
+ cookie is valid. An explicitly specified domain must always start
+ with a dot.
+
+ Max-Age=delta-seconds
+ Optional. The Max-Age attribute defines the lifetime of the
+ cookie, in seconds. The delta-seconds value is a decimal non-
+ negative integer. After delta-seconds seconds elapse, the client
+ should discard the cookie. A value of zero means the cookie
+ should be discarded immediately.
+
+ Path=path
+ Optional. The Path attribute specifies the subset of URLs to
+ which this cookie applies.
+
+ Secure
+ Optional. The Secure attribute (with no value) directs the user
+ agent to use only (unspecified) secure means to contact the origin
+ server whenever it sends back this cookie.
+
+ The user agent (possibly under the user's control) may determine
+ what level of security it considers appropriate for "secure"
+ cookies. The Secure attribute should be considered security
+ advice from the server to the user agent, indicating that it is in
+ the session's interest to protect the cookie contents.
+
+ Version=version
+ Required. The Version attribute, a decimal integer, identifies to
+ which version of the state management specification the cookie
+ conforms. For this specification, Version=1 applies.
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 5]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+4.2.3 Controlling Caching
+
+ An origin server must be cognizant of the effect of possible caching
+ of both the returned resource and the Set-Cookie header. Caching
+ "public" documents is desirable. For example, if the origin server
+ wants to use a public document such as a "front door" page as a
+ sentinel to indicate the beginning of a session for which a Set-
+ Cookie response header must be generated, the page should be stored
+ in caches "pre-expired" so that the origin server will see further
+ requests. "Private documents", for example those that contain
+ information strictly private to a session, should not be cached in
+ shared caches.
+
+ If the cookie is intended for use by a single user, the Set-cookie
+ header should not be cached. A Set-cookie header that is intended to
+ be shared by multiple users may be cached.
+
+ The origin server should send the following additional HTTP/1.1
+ response headers, depending on circumstances:
+
+ * To suppress caching of the Set-Cookie header: Cache-control: no-
+ cache="set-cookie".
+
+ and one of the following:
+
+ * To suppress caching of a private document in shared caches: Cache-
+ control: private.
+
+ * To allow caching of a document and require that it be validated
+ before returning it to the client: Cache-control: must-revalidate.
+
+ * To allow caching of a document, but to require that proxy caches
+ (not user agent caches) validate it before returning it to the
+ client: Cache-control: proxy-revalidate.
+
+ * To allow caching of a document and request that it be validated
+ before returning it to the client (by "pre-expiring" it):
+ Cache-control: max-age=0. Not all caches will revalidate the
+ document in every case.
+
+ HTTP/1.1 servers must send Expires: old-date (where old-date is a
+ date long in the past) on responses containing Set-Cookie response
+ headers unless they know for certain (by out of band means) that
+ there are no downsteam HTTP/1.0 proxies. HTTP/1.1 servers may send
+ other Cache-Control directives that permit caching by HTTP/1.1
+ proxies in addition to the Expires: old-date directive; the Cache-
+ Control directive will override the Expires: old-date for HTTP/1.1
+ proxies.
+
+
+
+Kristol & Montulli Standards Track [Page 6]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+4.3 User Agent Role
+
+4.3.1 Interpreting Set-Cookie
+
+ The user agent keeps separate track of state information that arrives
+ via Set-Cookie response headers from each origin server (as
+ distinguished by name or IP address and port). The user agent
+ applies these defaults for optional attributes that are missing:
+
+ VersionDefaults to "old cookie" behavior as originally specified by
+ Netscape. See the HISTORICAL section.
+
+ Domain Defaults to the request-host. (Note that there is no dot at
+ the beginning of request-host.)
+
+ Max-AgeThe default behavior is to discard the cookie when the user
+ agent exits.
+
+ Path Defaults to the path of the request URL that generated the
+ Set-Cookie response, up to, but not including, the
+ right-most /.
+
+ Secure If absent, the user agent may send the cookie over an
+ insecure channel.
+
+4.3.2 Rejecting Cookies
+
+ To prevent possible security or privacy violations, a user agent
+ rejects a cookie (shall not store its information) if any of the
+ following is true:
+
+ * The value for the Path attribute is not a prefix of the request-
+ URI.
+
+ * The value for the Domain attribute contains no embedded dots or
+ does not start with a dot.
+
+ * The value for the request-host does not domain-match the Domain
+ attribute.
+
+ * The request-host is a FQDN (not IP address) and has the form HD,
+ where D is the value of the Domain attribute, and H is a string
+ that contains one or more dots.
+
+ Examples:
+
+ * A Set-Cookie from request-host y.x.foo.com for Domain=.foo.com
+ would be rejected, because H is y.x and contains a dot.
+
+
+
+Kristol & Montulli Standards Track [Page 7]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ * A Set-Cookie from request-host x.foo.com for Domain=.foo.com would
+ be accepted.
+
+ * A Set-Cookie with Domain=.com or Domain=.com., will always be
+ rejected, because there is no embedded dot.
+
+ * A Set-Cookie with Domain=ajax.com will be rejected because the
+ value for Domain does not begin with a dot.
+
+4.3.3 Cookie Management
+
+ If a user agent receives a Set-Cookie response header whose NAME is
+ the same as a pre-existing cookie, and whose Domain and Path
+ attribute values exactly (string) match those of a pre-existing
+ cookie, the new cookie supersedes the old. However, if the Set-
+ Cookie has a value for Max-Age of zero, the (old and new) cookie is
+ discarded. Otherwise cookies accumulate until they expire (resources
+ permitting), at which time they are discarded.
+
+ Because user agents have finite space in which to store cookies, they
+ may also discard older cookies to make space for newer ones, using,
+ for example, a least-recently-used algorithm, along with constraints
+ on the maximum number of cookies that each origin server may set.
+
+ If a Set-Cookie response header includes a Comment attribute, the
+ user agent should store that information in a human-readable form
+ with the cookie and should display the comment text as part of a
+ cookie inspection user interface.
+
+ User agents should allow the user to control cookie destruction. An
+ infrequently-used cookie may function as a "preferences file" for
+ network applications, and a user may wish to keep it even if it is
+ the least-recently-used cookie. One possible implementation would be
+ an interface that allows the permanent storage of a cookie through a
+ checkbox (or, conversely, its immediate destruction).
+
+ Privacy considerations dictate that the user have considerable
+ control over cookie management. The PRIVACY section contains more
+ information.
+
+4.3.4 Sending Cookies to the Origin Server
+
+ When it sends a request to an origin server, the user agent sends a
+ Cookie request header to the origin server if it has cookies that are
+ applicable to the request, based on
+
+ * the request-host;
+
+
+
+
+Kristol & Montulli Standards Track [Page 8]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ * the request-URI;
+
+ * the cookie's age.
+
+ The syntax for the header is:
+
+ cookie = "Cookie:" cookie-version
+ 1*((";" | ",") cookie-value)
+ cookie-value = NAME "=" VALUE [";" path] [";" domain]
+ cookie-version = "$Version" "=" value
+ NAME = attr
+ VALUE = value
+ path = "$Path" "=" value
+ domain = "$Domain" "=" value
+
+ The value of the cookie-version attribute must be the value from the
+ Version attribute, if any, of the corresponding Set-Cookie response
+ header. Otherwise the value for cookie-version is 0. The value for
+ the path attribute must be the value from the Path attribute, if any,
+ of the corresponding Set-Cookie response header. Otherwise the
+ attribute should be omitted from the Cookie request header. The
+ value for the domain attribute must be the value from the Domain
+ attribute, if any, of the corresponding Set-Cookie response header.
+ Otherwise the attribute should be omitted from the Cookie request
+ header.
+
+ Note that there is no Comment attribute in the Cookie request header
+ corresponding to the one in the Set-Cookie response header. The user
+ agent does not return the comment information to the origin server.
+
+ The following rules apply to choosing applicable cookie-values from
+ among all the cookies the user agent has.
+
+ Domain Selection
+ The origin server's fully-qualified host name must domain-match
+ the Domain attribute of the cookie.
+
+ Path Selection
+ The Path attribute of the cookie must match a prefix of the
+ request-URI.
+
+ Max-Age Selection
+ Cookies that have expired should have been discarded and thus
+ are not forwarded to an origin server.
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 9]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ If multiple cookies satisfy the criteria above, they are ordered in
+ the Cookie header such that those with more specific Path attributes
+ precede those with less specific. Ordering with respect to other
+ attributes (e.g., Domain) is unspecified.
+
+ Note: For backward compatibility, the separator in the Cookie header
+ is semi-colon (;) everywhere. A server should also accept comma (,)
+ as the separator between cookie-values for future compatibility.
+
+4.3.5 Sending Cookies in Unverifiable Transactions
+
+ Users must have control over sessions in order to ensure privacy.
+ (See PRIVACY section below.) To simplify implementation and to
+ prevent an additional layer of complexity where adequate safeguards
+ exist, however, this document distinguishes between transactions that
+ are verifiable and those that are unverifiable. A transaction is
+ verifiable if the user has the option to review the request-URI prior
+ to its use in the transaction. A transaction is unverifiable if the
+ user does not have that option. Unverifiable transactions typically
+ arise when a user agent automatically requests inlined or embedded
+ entities or when it resolves redirection (3xx) responses from an
+ origin server. Typically the origin transaction, the transaction
+ that the user initiates, is verifiable, and that transaction may
+ directly or indirectly induce the user agent to make unverifiable
+ transactions.
+
+ When it makes an unverifiable transaction, a user agent must enable a
+ session only if a cookie with a domain attribute D was sent or
+ received in its origin transaction, such that the host name in the
+ Request-URI of the unverifiable transaction domain-matches D.
+
+ This restriction prevents a malicious service author from using
+ unverifiable transactions to induce a user agent to start or continue
+ a session with a server in a different domain. The starting or
+ continuation of such sessions could be contrary to the privacy
+ expectations of the user, and could also be a security problem.
+
+ User agents may offer configurable options that allow the user agent,
+ or any autonomous programs that the user agent executes, to ignore
+ the above rule, so long as these override options default to "off".
+
+ Many current user agents already provide a review option that would
+ render many links verifiable. For instance, some user agents display
+ the URL that would be referenced for a particular link when the mouse
+ pointer is placed over that link. The user can therefore determine
+ whether to visit that site before causing the browser to do so.
+ (Though not implemented on current user agents, a similar technique
+ could be used for a button used to submit a form -- the user agent
+
+
+
+Kristol & Montulli Standards Track [Page 10]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ could display the action to be taken if the user were to select that
+ button.) However, even this would not make all links verifiable; for
+ example, links to automatically loaded images would not normally be
+ subject to "mouse pointer" verification.
+
+ Many user agents also provide the option for a user to view the HTML
+ source of a document, or to save the source to an external file where
+ it can be viewed by another application. While such an option does
+ provide a crude review mechanism, some users might not consider it
+ acceptable for this purpose.
+
+4.4 How an Origin Server Interprets the Cookie Header
+
+ A user agent returns much of the information in the Set-Cookie header
+ to the origin server when the Path attribute matches that of a new
+ request. When it receives a Cookie header, the origin server should
+ treat cookies with NAMEs whose prefix is $ specially, as an attribute
+ for the adjacent cookie. The value for such a NAME is to be
+ interpreted as applying to the lexically (left-to-right) most recent
+ cookie whose name does not have the $ prefix. If there is no
+ previous cookie, the value applies to the cookie mechanism as a
+ whole. For example, consider the cookie
+
+ Cookie: $Version="1"; Customer="WILE_E_COYOTE";
+ $Path="/acme"
+
+ $Version applies to the cookie mechanism as a whole (and gives the
+ version number for the cookie mechanism). $Path is an attribute
+ whose value (/acme) defines the Path attribute that was used when the
+ Customer cookie was defined in a Set-Cookie response header.
+
+4.5 Caching Proxy Role
+
+ One reason for separating state information from both a URL and
+ document content is to facilitate the scaling that caching permits.
+ To support cookies, a caching proxy must obey these rules already in
+ the HTTP specification:
+
+ * Honor requests from the cache, if possible, based on cache validity
+ rules.
+
+ * Pass along a Cookie request header in any request that the proxy
+ must make of another server.
+
+ * Return the response to the client. Include any Set-Cookie response
+ header.
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 11]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ * Cache the received response subject to the control of the usual
+ headers, such as Expires, Cache-control: no-cache, and Cache-
+ control: private,
+
+ * Cache the Set-Cookie subject to the control of the usual header,
+ Cache-control: no-cache="set-cookie". (The Set-Cookie header
+ should usually not be cached.)
+
+ Proxies must not introduce Set-Cookie (Cookie) headers of their own
+ in proxy responses (requests).
+
+5. EXAMPLES
+
+5.1 Example 1
+
+ Most detail of request and response headers has been omitted. Assume
+ the user agent has no stored cookies.
+
+ 1. User Agent -> Server
+
+ POST /acme/login HTTP/1.1
+ [form data]
+
+ User identifies self via a form.
+
+ 2. Server -> User Agent
+
+ HTTP/1.1 200 OK
+ Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
+
+ Cookie reflects user's identity.
+
+ 3. User Agent -> Server
+
+ POST /acme/pickitem HTTP/1.1
+ Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"
+ [form data]
+
+ User selects an item for "shopping basket."
+
+ 4. Server -> User Agent
+
+ HTTP/1.1 200 OK
+ Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1";
+ Path="/acme"
+
+ Shopping basket contains an item.
+
+
+
+
+Kristol & Montulli Standards Track [Page 12]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ 5. User Agent -> Server
+
+ POST /acme/shipping HTTP/1.1
+ Cookie: $Version="1";
+ Customer="WILE_E_COYOTE"; $Path="/acme";
+ Part_Number="Rocket_Launcher_0001"; $Path="/acme"
+ [form data]
+
+ User selects shipping method from form.
+
+ 6. Server -> User Agent
+
+ HTTP/1.1 200 OK
+ Set-Cookie: Shipping="FedEx"; Version="1"; Path="/acme"
+
+ New cookie reflects shipping method.
+
+ 7. User Agent -> Server
+
+ POST /acme/process HTTP/1.1
+ Cookie: $Version="1";
+ Customer="WILE_E_COYOTE"; $Path="/acme";
+ Part_Number="Rocket_Launcher_0001"; $Path="/acme";
+ Shipping="FedEx"; $Path="/acme"
+ [form data]
+
+ User chooses to process order.
+
+ 8. Server -> User Agent
+
+ HTTP/1.1 200 OK
+
+ Transaction is complete.
+
+ The user agent makes a series of requests on the origin server, after
+ each of which it receives a new cookie. All the cookies have the
+ same Path attribute and (default) domain. Because the request URLs
+ all have /acme as a prefix, and that matches the Path attribute, each
+ request contains all the cookies received so far.
+
+5.2 Example 2
+
+ This example illustrates the effect of the Path attribute. All
+ detail of request and response headers has been omitted. Assume the
+ user agent has no stored cookies.
+
+ Imagine the user agent has received, in response to earlier requests,
+ the response headers
+
+
+
+Kristol & Montulli Standards Track [Page 13]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1";
+ Path="/acme"
+
+ and
+
+ Set-Cookie: Part_Number="Riding_Rocket_0023"; Version="1";
+ Path="/acme/ammo"
+
+ A subsequent request by the user agent to the (same) server for URLs
+ of the form /acme/ammo/... would include the following request
+ header:
+
+ Cookie: $Version="1";
+ Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo";
+ Part_Number="Rocket_Launcher_0001"; $Path="/acme"
+
+ Note that the NAME=VALUE pair for the cookie with the more specific
+ Path attribute, /acme/ammo, comes before the one with the less
+ specific Path attribute, /acme. Further note that the same cookie
+ name appears more than once.
+
+ A subsequent request by the user agent to the (same) server for a URL
+ of the form /acme/parts/ would include the following request header:
+
+ Cookie: $Version="1"; Part_Number="Rocket_Launcher_0001"; $Path="/acme"
+
+ Here, the second cookie's Path attribute /acme/ammo is not a prefix
+ of the request URL, /acme/parts/, so the cookie does not get
+ forwarded to the server.
+
+6. IMPLEMENTATION CONSIDERATIONS
+
+ Here we speculate on likely or desirable details for an origin server
+ that implements state management.
+
+6.1 Set-Cookie Content
+
+ An origin server's content should probably be divided into disjoint
+ application areas, some of which require the use of state
+ information. The application areas can be distinguished by their
+ request URLs. The Set-Cookie header can incorporate information
+ about the application areas by setting the Path attribute for each
+ one.
+
+ The session information can obviously be clear or encoded text that
+ describes state. However, if it grows too large, it can become
+ unwieldy. Therefore, an implementor might choose for the session
+ information to be a key to a server-side resource. Of course, using
+
+
+
+Kristol & Montulli Standards Track [Page 14]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ a database creates some problems that this state management
+ specification was meant to avoid, namely:
+
+ 1. keeping real state on the server side;
+
+ 2. how and when to garbage-collect the database entry, in case the
+ user agent terminates the session by, for example, exiting.
+
+6.2 Stateless Pages
+
+ Caching benefits the scalability of WWW. Therefore it is important
+ to reduce the number of documents that have state embedded in them
+ inherently. For example, if a shopping-basket-style application
+ always displays a user's current basket contents on each page, those
+ pages cannot be cached, because each user's basket's contents would
+ be different. On the other hand, if each page contains just a link
+ that allows the user to "Look at My Shopping Basket", the page can be
+ cached.
+
+6.3 Implementation Limits
+
+ Practical user agent implementations have limits on the number and
+ size of cookies that they can store. In general, user agents' cookie
+ support should have no fixed limits. They should strive to store as
+ many frequently-used cookies as possible. Furthermore, general-use
+ user agents should provide each of the following minimum capabilities
+ individually, although not necessarily simultaneously:
+
+ * at least 300 cookies
+
+ * at least 4096 bytes per cookie (as measured by the size of the
+ characters that comprise the cookie non-terminal in the syntax
+ description of the Set-Cookie header)
+
+ * at least 20 cookies per unique host or domain name
+
+ User agents created for specific purposes or for limited-capacity
+ devices should provide at least 20 cookies of 4096 bytes, to ensure
+ that the user can interact with a session-based origin server.
+
+ The information in a Set-Cookie response header must be retained in
+ its entirety. If for some reason there is inadequate space to store
+ the cookie, it must be discarded, not truncated.
+
+ Applications should use as few and as small cookies as possible, and
+ they should cope gracefully with the loss of a cookie.
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 15]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+6.3.1 Denial of Service Attacks
+
+ User agents may choose to set an upper bound on the number of cookies
+ to be stored from a given host or domain name or on the size of the
+ cookie information. Otherwise a malicious server could attempt to
+ flood a user agent with many cookies, or large cookies, on successive
+ responses, which would force out cookies the user agent had received
+ from other servers. However, the minima specified above should still
+ be supported.
+
+7. PRIVACY
+
+7.1 User Agent Control
+
+ An origin server could create a Set-Cookie header to track the path
+ of a user through the server. Users may object to this behavior as
+ an intrusive accumulation of information, even if their identity is
+ not evident. (Identity might become evident if a user subsequently
+ fills out a form that contains identifying information.) This state
+ management specification therefore requires that a user agent give
+ the user control over such a possible intrusion, although the
+ interface through which the user is given this control is left
+ unspecified. However, the control mechanisms provided shall at least
+ allow the user
+
+ * to completely disable the sending and saving of cookies.
+
+ * to determine whether a stateful session is in progress.
+
+ * to control the saving of a cookie on the basis of the cookie's
+ Domain attribute.
+
+ Such control could be provided by, for example, mechanisms
+
+ * to notify the user when the user agent is about to send a cookie
+ to the origin server, offering the option not to begin a session.
+
+ * to display a visual indication that a stateful session is in
+ progress.
+
+ * to let the user decide which cookies, if any, should be saved
+ when the user concludes a window or user agent session.
+
+ * to let the user examine the contents of a cookie at any time.
+
+ A user agent usually begins execution with no remembered state
+ information. It should be possible to configure a user agent never
+ to send Cookie headers, in which case it can never sustain state with
+
+
+
+Kristol & Montulli Standards Track [Page 16]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ an origin server. (The user agent would then behave like one that is
+ unaware of how to handle Set-Cookie response headers.)
+
+ When the user agent terminates execution, it should let the user
+ discard all state information. Alternatively, the user agent may ask
+ the user whether state information should be retained; the default
+ should be "no". If the user chooses to retain state information, it
+ would be restored the next time the user agent runs.
+
+ NOTE: User agents should probably be cautious about using files to
+ store cookies long-term. If a user runs more than one instance of
+ the user agent, the cookies could be commingled or otherwise messed
+ up.
+
+7.2 Protocol Design
+
+ The restrictions on the value of the Domain attribute, and the rules
+ concerning unverifiable transactions, are meant to reduce the ways
+ that cookies can "leak" to the "wrong" site. The intent is to
+ restrict cookies to one, or a closely related set of hosts.
+ Therefore a request-host is limited as to what values it can set for
+ Domain. We consider it acceptable for hosts host1.foo.com and
+ host2.foo.com to share cookies, but not a.com and b.com.
+
+ Similarly, a server can only set a Path for cookies that are related
+ to the request-URI.
+
+8. SECURITY CONSIDERATIONS
+
+8.1 Clear Text
+
+ The information in the Set-Cookie and Cookie headers is unprotected.
+ Two consequences are:
+
+ 1. Any sensitive information that is conveyed in them is exposed
+ to intruders.
+
+ 2. A malicious intermediary could alter the headers as they travel
+ in either direction, with unpredictable results.
+
+ These facts imply that information of a personal and/or financial
+ nature should only be sent over a secure channel. For less sensitive
+ information, or when the content of the header is a database key, an
+ origin server should be vigilant to prevent a bad Cookie value from
+ causing failures.
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 17]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+8.2 Cookie Spoofing
+
+ Proper application design can avoid spoofing attacks from related
+ domains. Consider:
+
+ 1. User agent makes request to victim.cracker.edu, gets back
+ cookie session_id="1234" and sets the default domain
+ victim.cracker.edu.
+
+ 2. User agent makes request to spoof.cracker.edu, gets back
+ cookie session-id="1111", with Domain=".cracker.edu".
+
+ 3. User agent makes request to victim.cracker.edu again, and
+ passes
+
+ Cookie: $Version="1";
+ session_id="1234";
+ session_id="1111"; $Domain=".cracker.edu"
+
+ The server at victim.cracker.edu should detect that the second
+ cookie was not one it originated by noticing that the Domain
+ attribute is not for itself and ignore it.
+
+8.3 Unexpected Cookie Sharing
+
+ A user agent should make every attempt to prevent the sharing of
+ session information between hosts that are in different domains.
+ Embedded or inlined objects may cause particularly severe privacy
+ problems if they can be used to share cookies between disparate
+ hosts. For example, a malicious server could embed cookie
+ information for host a.com in a URI for a CGI on host b.com. User
+ agent implementors are strongly encouraged to prevent this sort of
+ exchange whenever possible.
+
+9. OTHER, SIMILAR, PROPOSALS
+
+ Three other proposals have been made to accomplish similar goals.
+ This specification is an amalgam of Kristol's State-Info proposal and
+ Netscape's Cookie proposal.
+
+ Brian Behlendorf proposed a Session-ID header that would be user-
+ agent-initiated and could be used by an origin server to track
+ "clicktrails". It would not carry any origin-server-defined state,
+ however. Phillip Hallam-Baker has proposed another client-defined
+ session ID mechanism for similar purposes.
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 18]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+ While both session IDs and cookies can provide a way to sustain
+ stateful sessions, their intended purpose is different, and,
+ consequently, the privacy requirements for them are different. A
+ user initiates session IDs to allow servers to track progress through
+ them, or to distinguish multiple users on a shared machine. Cookies
+ are server-initiated, so the cookie mechanism described here gives
+ users control over something that would otherwise take place without
+ the users' awareness. Furthermore, cookies convey rich, server-
+ selected information, whereas session IDs comprise user-selected,
+ simple information.
+
+10. HISTORICAL
+
+10.1 Compatibility With Netscape's Implementation
+
+ HTTP/1.0 clients and servers may use Set-Cookie and Cookie headers
+ that reflect Netscape's original cookie proposal. These notes cover
+ inter-operation between "old" and "new" cookies.
+
+10.1.1 Extended Cookie Header
+
+ This proposal adds attribute-value pairs to the Cookie request header
+ in a compatible way. An "old" client that receives a "new" cookie
+ will ignore attributes it does not understand; it returns what it
+ does understand to the origin server. A "new" client always sends
+ cookies in the new form.
+
+ An "old" server that receives a "new" cookie will see what it thinks
+ are many cookies with names that begin with a $, and it will ignore
+ them. (The "old" server expects these cookies to be separated by
+ semi-colon, not comma.) A "new" server can detect cookies that have
+ passed through an "old" client, because they lack a $Version
+ attribute.
+
+10.1.2 Expires and Max-Age
+
+ Netscape's original proposal defined an Expires header that took a
+ date value in a fixed-length variant format in place of Max-Age:
+
+ Wdy, DD-Mon-YY HH:MM:SS GMT
+
+ Note that the Expires date format contains embedded spaces, and that
+ "old" cookies did not have quotes around values. Clients that
+ implement to this specification should be aware of "old" cookies and
+ Expires.
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 19]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+10.1.3 Punctuation
+
+ In Netscape's original proposal, the values in attribute-value pairs
+ did not accept "-quoted strings. Origin servers should be cautious
+ about sending values that require quotes unless they know the
+ receiving user agent understands them (i.e., "new" cookies). A
+ ("new") user agent should only use quotes around values in Cookie
+ headers when the cookie's version(s) is (are) all compliant with this
+ specification or later.
+
+ In Netscape's original proposal, no whitespace was permitted around
+ the = that separates attribute-value pairs. Therefore such
+ whitespace should be used with caution in new implementations.
+
+10.2 Caching and HTTP/1.0
+
+ Some caches, such as those conforming to HTTP/1.0, will inevitably
+ cache the Set-Cookie header, because there was no mechanism to
+ suppress caching of headers prior to HTTP/1.1. This caching can lead
+ to security problems. Documents transmitted by an origin server
+ along with Set-Cookie headers will usually either be uncachable, or
+ will be "pre-expired". As long as caches obey instructions not to
+ cache documents (following Expires: <a date in the past> or Pragma:
+ no-cache (HTTP/1.0), or Cache-control: no-cache (HTTP/1.1))
+ uncachable documents present no problem. However, pre-expired
+ documents may be stored in caches. They require validation (a
+ conditional GET) on each new request, but some cache operators loosen
+ the rules for their caches, and sometimes serve expired documents
+ without first validating them. This combination of factors can lead
+ to cookies meant for one user later being sent to another user. The
+ Set-Cookie header is stored in the cache, and, although the document
+ is stale (expired), the cache returns the document in response to
+ later requests, including cached headers.
+
+11. ACKNOWLEDGEMENTS
+
+ This document really represents the collective efforts of the
+ following people, in addition to the authors: Roy Fielding, Marc
+ Hedlund, Ted Hardie, Koen Holtman, Shel Kaphan, Rohit Khare.
+
+
+
+
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 20]
+
+RFC 2109 HTTP State Management Mechanism February 1997
+
+
+12. AUTHORS' ADDRESSES
+
+ David M. Kristol
+ Bell Laboratories, Lucent Technologies
+ 600 Mountain Ave. Room 2A-227
+ Murray Hill, NJ 07974
+
+ Phone: (908) 582-2250
+ Fax: (908) 582-5809
+ EMail: dmk@bell-labs.com
+
+
+ Lou Montulli
+ Netscape Communications Corp.
+ 501 E. Middlefield Rd.
+ Mountain View, CA 94043
+
+ Phone: (415) 528-2600
+ EMail: montulli@netscape.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 21]
+
diff --git a/kioslave/http/kcookiejar/rfc2965 b/kioslave/http/kcookiejar/rfc2965
new file mode 100644
index 000000000..8a4d02b17
--- /dev/null
+++ b/kioslave/http/kcookiejar/rfc2965
@@ -0,0 +1,1459 @@
+
+
+
+
+
+
+Network Working Group D. Kristol
+Request for Comments: 2965 Bell Laboratories, Lucent Technologies
+Obsoletes: 2109 L. Montulli
+Category: Standards Track Epinions.com, Inc.
+ October 2000
+
+
+ HTTP State Management Mechanism
+
+Status of this Memo
+
+ This document specifies an Internet standards track protocol for the
+ Internet community, and requests discussion and suggestions for
+ improvements. Please refer to the current edition of the "Internet
+ Official Protocol Standards" (STD 1) for the standardization state
+ and status of this protocol. Distribution of this memo is unlimited.
+
+Copyright Notice
+
+ Copyright (C) The Internet Society (2000). All Rights Reserved.
+
+IESG Note
+
+ The IESG notes that this mechanism makes use of the .local top-level
+ domain (TLD) internally when handling host names that don't contain
+ any dots, and that this mechanism might not work in the expected way
+ should an actual .local TLD ever be registered.
+
+Abstract
+
+ This document specifies a way to create a stateful session with
+ Hypertext Transfer Protocol (HTTP) requests and responses. It
+ describes three new headers, Cookie, Cookie2, and Set-Cookie2, which
+ carry state information between participating origin servers and user
+ agents. The method described here differs from Netscape's Cookie
+ proposal [Netscape], but it can interoperate with HTTP/1.0 user
+ agents that use Netscape's method. (See the HISTORICAL section.)
+
+ This document reflects implementation experience with RFC 2109 and
+ obsoletes it.
+
+1. TERMINOLOGY
+
+ The terms user agent, client, server, proxy, origin server, and
+ http_URL have the same meaning as in the HTTP/1.1 specification
+ [RFC2616]. The terms abs_path and absoluteURI have the same meaning
+ as in the URI Syntax specification [RFC2396].
+
+
+
+
+Kristol & Montulli Standards Track [Page 1]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ Host name (HN) means either the host domain name (HDN) or the numeric
+ Internet Protocol (IP) address of a host. The fully qualified domain
+ name is preferred; use of numeric IP addresses is strongly
+ discouraged.
+
+ The terms request-host and request-URI refer to the values the client
+ would send to the server as, respectively, the host (but not port)
+ and abs_path portions of the absoluteURI (http_URL) of the HTTP
+ request line. Note that request-host is a HN.
+
+ The term effective host name is related to host name. If a host name
+ contains no dots, the effective host name is that name with the
+ string .local appended to it. Otherwise the effective host name is
+ the same as the host name. Note that all effective host names
+ contain at least one dot.
+
+ The term request-port refers to the port portion of the absoluteURI
+ (http_URL) of the HTTP request line. If the absoluteURI has no
+ explicit port, the request-port is the HTTP default, 80. The
+ request-port of a cookie is the request-port of the request in which
+ a Set-Cookie2 response header was returned to the user agent.
+
+ Host names can be specified either as an IP address or a HDN string.
+ Sometimes we compare one host name with another. (Such comparisons
+ SHALL be case-insensitive.) Host A's name domain-matches host B's if
+
+ * their host name strings string-compare equal; or
+
+ * A is a HDN string and has the form NB, where N is a non-empty
+ name string, B has the form .B', and B' is a HDN string. (So,
+ x.y.com domain-matches .Y.com but not Y.com.)
+
+ Note that domain-match is not a commutative operation: a.b.c.com
+ domain-matches .c.com, but not the reverse.
+
+ The reach R of a host name H is defined as follows:
+
+ * If
+
+ - H is the host domain name of a host; and,
+
+ - H has the form A.B; and
+
+ - A has no embedded (that is, interior) dots; and
+
+ - B has at least one embedded dot, or B is the string "local".
+ then the reach of H is .B.
+
+
+
+
+Kristol & Montulli Standards Track [Page 2]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ * Otherwise, the reach of H is H.
+
+ For two strings that represent paths, P1 and P2, P1 path-matches P2
+ if P2 is a prefix of P1 (including the case where P1 and P2 string-
+ compare equal). Thus, the string /tec/waldo path-matches /tec.
+
+ Because it was used in Netscape's original implementation of state
+ management, we will use the term cookie to refer to the state
+ information that passes between an origin server and user agent, and
+ that gets stored by the user agent.
+
+1.1 Requirements
+
+ The key words "MAY", "MUST", "MUST NOT", "OPTIONAL", "RECOMMENDED",
+ "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT" in this
+ document are to be interpreted as described in RFC 2119 [RFC2119].
+
+2. STATE AND SESSIONS
+
+ This document describes a way to create stateful sessions with HTTP
+ requests and responses. Currently, HTTP servers respond to each
+ client request without relating that request to previous or
+ subsequent requests; the state management mechanism allows clients
+ and servers that wish to exchange state information to place HTTP
+ requests and responses within a larger context, which we term a
+ "session". This context might be used to create, for example, a
+ "shopping cart", in which user selections can be aggregated before
+ purchase, or a magazine browsing system, in which a user's previous
+ reading affects which offerings are presented.
+
+ Neither clients nor servers are required to support cookies. A
+ server MAY refuse to provide content to a client that does not return
+ the cookies it sends.
+
+3. DESCRIPTION
+
+ We describe here a way for an origin server to send state information
+ to the user agent, and for the user agent to return the state
+ information to the origin server. The goal is to have a minimal
+ impact on HTTP and user agents.
+
+3.1 Syntax: General
+
+ The two state management headers, Set-Cookie2 and Cookie, have common
+ syntactic properties involving attribute-value pairs. The following
+ grammar uses the notation, and tokens DIGIT (decimal digits), token
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 3]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ (informally, a sequence of non-special, non-white space characters),
+ and http_URL from the HTTP/1.1 specification [RFC2616] to describe
+ their syntax.
+
+ av-pairs = av-pair *(";" av-pair)
+ av-pair = attr ["=" value] ; optional value
+ attr = token
+ value = token | quoted-string
+
+ Attributes (names) (attr) are case-insensitive. White space is
+ permitted between tokens. Note that while the above syntax
+ description shows value as optional, most attrs require them.
+
+ NOTE: The syntax above allows whitespace between the attribute and
+ the = sign.
+
+3.2 Origin Server Role
+
+ 3.2.1 General The origin server initiates a session, if it so
+ desires. To do so, it returns an extra response header to the
+ client, Set-Cookie2. (The details follow later.)
+
+ A user agent returns a Cookie request header (see below) to the
+ origin server if it chooses to continue a session. The origin server
+ MAY ignore it or use it to determine the current state of the
+ session. It MAY send back to the client a Set-Cookie2 response
+ header with the same or different information, or it MAY send no
+ Set-Cookie2 header at all. The origin server effectively ends a
+ session by sending the client a Set-Cookie2 header with Max-Age=0.
+
+ Servers MAY return Set-Cookie2 response headers with any response.
+ User agents SHOULD send Cookie request headers, subject to other
+ rules detailed below, with every request.
+
+ An origin server MAY include multiple Set-Cookie2 headers in a
+ response. Note that an intervening gateway could fold multiple such
+ headers into a single header.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 4]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ 3.2.2 Set-Cookie2 Syntax The syntax for the Set-Cookie2 response
+ header is
+
+ set-cookie = "Set-Cookie2:" cookies
+ cookies = 1#cookie
+ cookie = NAME "=" VALUE *(";" set-cookie-av)
+ NAME = attr
+ VALUE = value
+ set-cookie-av = "Comment" "=" value
+ | "CommentURL" "=" <"> http_URL <">
+ | "Discard"
+ | "Domain" "=" value
+ | "Max-Age" "=" value
+ | "Path" "=" value
+ | "Port" [ "=" <"> portlist <"> ]
+ | "Secure"
+ | "Version" "=" 1*DIGIT
+ portlist = 1#portnum
+ portnum = 1*DIGIT
+
+ Informally, the Set-Cookie2 response header comprises the token Set-
+ Cookie2:, followed by a comma-separated list of one or more cookies.
+ Each cookie begins with a NAME=VALUE pair, followed by zero or more
+ semi-colon-separated attribute-value pairs. The syntax for
+ attribute-value pairs was shown earlier. The specific attributes and
+ the semantics of their values follows. The NAME=VALUE attribute-
+ value pair MUST come first in each cookie. The others, if present,
+ can occur in any order. If an attribute appears more than once in a
+ cookie, the client SHALL use only the value associated with the first
+ appearance of the attribute; a client MUST ignore values after the
+ first.
+
+ The NAME of a cookie MAY be the same as one of the attributes in this
+ specification. However, because the cookie's NAME must come first in
+ a Set-Cookie2 response header, the NAME and its VALUE cannot be
+ confused with an attribute-value pair.
+
+ NAME=VALUE
+ REQUIRED. The name of the state information ("cookie") is NAME,
+ and its value is VALUE. NAMEs that begin with $ are reserved and
+ MUST NOT be used by applications.
+
+ The VALUE is opaque to the user agent and may be anything the
+ origin server chooses to send, possibly in a server-selected
+ printable ASCII encoding. "Opaque" implies that the content is of
+ interest and relevance only to the origin server. The content
+ may, in fact, be readable by anyone that examines the Set-Cookie2
+ header.
+
+
+
+Kristol & Montulli Standards Track [Page 5]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ Comment=value
+ OPTIONAL. Because cookies can be used to derive or store private
+ information about a user, the value of the Comment attribute
+ allows an origin server to document how it intends to use the
+ cookie. The user can inspect the information to decide whether to
+ initiate or continue a session with this cookie. Characters in
+ value MUST be in UTF-8 encoding. [RFC2279]
+
+ CommentURL="http_URL"
+ OPTIONAL. Because cookies can be used to derive or store private
+ information about a user, the CommentURL attribute allows an
+ origin server to document how it intends to use the cookie. The
+ user can inspect the information identified by the URL to decide
+ whether to initiate or continue a session with this cookie.
+
+ Discard
+ OPTIONAL. The Discard attribute instructs the user agent to
+ discard the cookie unconditionally when the user agent terminates.
+
+ Domain=value
+ OPTIONAL. The value of the Domain attribute specifies the domain
+ for which the cookie is valid. If an explicitly specified value
+ does not start with a dot, the user agent supplies a leading dot.
+
+ Max-Age=value
+ OPTIONAL. The value of the Max-Age attribute is delta-seconds,
+ the lifetime of the cookie in seconds, a decimal non-negative
+ integer. To handle cached cookies correctly, a client SHOULD
+ calculate the age of the cookie according to the age calculation
+ rules in the HTTP/1.1 specification [RFC2616]. When the age is
+ greater than delta-seconds seconds, the client SHOULD discard the
+ cookie. A value of zero means the cookie SHOULD be discarded
+ immediately.
+
+ Path=value
+ OPTIONAL. The value of the Path attribute specifies the subset of
+ URLs on the origin server to which this cookie applies.
+
+ Port[="portlist"]
+ OPTIONAL. The Port attribute restricts the port to which a cookie
+ may be returned in a Cookie request header. Note that the syntax
+ REQUIREs quotes around the OPTIONAL portlist even if there is only
+ one portnum in portlist.
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 6]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ Secure
+ OPTIONAL. The Secure attribute (with no value) directs the user
+ agent to use only (unspecified) secure means to contact the origin
+ server whenever it sends back this cookie, to protect the
+ confidentially and authenticity of the information in the cookie.
+
+ The user agent (possibly with user interaction) MAY determine what
+ level of security it considers appropriate for "secure" cookies.
+ The Secure attribute should be considered security advice from the
+ server to the user agent, indicating that it is in the session's
+ interest to protect the cookie contents. When it sends a "secure"
+ cookie back to a server, the user agent SHOULD use no less than
+ the same level of security as was used when it received the cookie
+ from the server.
+
+ Version=value
+ REQUIRED. The value of the Version attribute, a decimal integer,
+ identifies the version of the state management specification to
+ which the cookie conforms. For this specification, Version=1
+ applies.
+
+ 3.2.3 Controlling Caching An origin server must be cognizant of the
+ effect of possible caching of both the returned resource and the
+ Set-Cookie2 header. Caching "public" documents is desirable. For
+ example, if the origin server wants to use a public document such as
+ a "front door" page as a sentinel to indicate the beginning of a
+ session for which a Set-Cookie2 response header must be generated,
+ the page SHOULD be stored in caches "pre-expired" so that the origin
+ server will see further requests. "Private documents", for example
+ those that contain information strictly private to a session, SHOULD
+ NOT be cached in shared caches.
+
+ If the cookie is intended for use by a single user, the Set-Cookie2
+ header SHOULD NOT be cached. A Set-Cookie2 header that is intended
+ to be shared by multiple users MAY be cached.
+
+ The origin server SHOULD send the following additional HTTP/1.1
+ response headers, depending on circumstances:
+
+ * To suppress caching of the Set-Cookie2 header:
+
+ Cache-control: no-cache="set-cookie2"
+
+ and one of the following:
+
+ * To suppress caching of a private document in shared caches:
+
+ Cache-control: private
+
+
+
+Kristol & Montulli Standards Track [Page 7]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ * To allow caching of a document and require that it be validated
+ before returning it to the client:
+
+ Cache-Control: must-revalidate, max-age=0
+
+ * To allow caching of a document, but to require that proxy
+ caches (not user agent caches) validate it before returning it
+ to the client:
+
+ Cache-Control: proxy-revalidate, max-age=0
+
+ * To allow caching of a document and request that it be validated
+ before returning it to the client (by "pre-expiring" it):
+
+ Cache-control: max-age=0
+
+ Not all caches will revalidate the document in every case.
+
+ HTTP/1.1 servers MUST send Expires: old-date (where old-date is a
+ date long in the past) on responses containing Set-Cookie2 response
+ headers unless they know for certain (by out of band means) that
+ there are no HTTP/1.0 proxies in the response chain. HTTP/1.1
+ servers MAY send other Cache-Control directives that permit caching
+ by HTTP/1.1 proxies in addition to the Expires: old-date directive;
+ the Cache-Control directive will override the Expires: old-date for
+ HTTP/1.1 proxies.
+
+3.3 User Agent Role
+
+ 3.3.1 Interpreting Set-Cookie2 The user agent keeps separate track
+ of state information that arrives via Set-Cookie2 response headers
+ from each origin server (as distinguished by name or IP address and
+ port). The user agent MUST ignore attribute-value pairs whose
+ attribute it does not recognize. The user agent applies these
+ defaults for optional attributes that are missing:
+
+ Discard The default behavior is dictated by the presence or absence
+ of a Max-Age attribute.
+
+ Domain Defaults to the effective request-host. (Note that because
+ there is no dot at the beginning of effective request-host,
+ the default Domain can only domain-match itself.)
+
+ Max-Age The default behavior is to discard the cookie when the user
+ agent exits.
+
+ Path Defaults to the path of the request URL that generated the
+ Set-Cookie2 response, up to and including the right-most /.
+
+
+
+Kristol & Montulli Standards Track [Page 8]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ Port The default behavior is that a cookie MAY be returned to any
+ request-port.
+
+ Secure If absent, the user agent MAY send the cookie over an
+ insecure channel.
+
+ 3.3.2 Rejecting Cookies To prevent possible security or privacy
+ violations, a user agent rejects a cookie according to rules below.
+ The goal of the rules is to try to limit the set of servers for which
+ a cookie is valid, based on the values of the Path, Domain, and Port
+ attributes and the request-URI, request-host and request-port.
+
+ A user agent rejects (SHALL NOT store its information) if the Version
+ attribute is missing. Moreover, a user agent rejects (SHALL NOT
+ store its information) if any of the following is true of the
+ attributes explicitly present in the Set-Cookie2 response header:
+
+ * The value for the Path attribute is not a prefix of the
+ request-URI.
+
+ * The value for the Domain attribute contains no embedded dots,
+ and the value is not .local.
+
+ * The effective host name that derives from the request-host does
+ not domain-match the Domain attribute.
+
+ * The request-host is a HDN (not IP address) and has the form HD,
+ where D is the value of the Domain attribute, and H is a string
+ that contains one or more dots.
+
+ * The Port attribute has a "port-list", and the request-port was
+ not in the list.
+
+ Examples:
+
+ * A Set-Cookie2 from request-host y.x.foo.com for Domain=.foo.com
+ would be rejected, because H is y.x and contains a dot.
+
+ * A Set-Cookie2 from request-host x.foo.com for Domain=.foo.com
+ would be accepted.
+
+ * A Set-Cookie2 with Domain=.com or Domain=.com., will always be
+ rejected, because there is no embedded dot.
+
+ * A Set-Cookie2 with Domain=ajax.com will be accepted, and the
+ value for Domain will be taken to be .ajax.com, because a dot
+ gets prepended to the value.
+
+
+
+
+Kristol & Montulli Standards Track [Page 9]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ * A Set-Cookie2 with Port="80,8000" will be accepted if the
+ request was made to port 80 or 8000 and will be rejected
+ otherwise.
+
+ * A Set-Cookie2 from request-host example for Domain=.local will
+ be accepted, because the effective host name for the request-
+ host is example.local, and example.local domain-matches .local.
+
+ 3.3.3 Cookie Management If a user agent receives a Set-Cookie2
+ response header whose NAME is the same as that of a cookie it has
+ previously stored, the new cookie supersedes the old when: the old
+ and new Domain attribute values compare equal, using a case-
+ insensitive string-compare; and, the old and new Path attribute
+ values string-compare equal (case-sensitive). However, if the Set-
+ Cookie2 has a value for Max-Age of zero, the (old and new) cookie is
+ discarded. Otherwise a cookie persists (resources permitting) until
+ whichever happens first, then gets discarded: its Max-Age lifetime is
+ exceeded; or, if the Discard attribute is set, the user agent
+ terminates the session.
+
+ Because user agents have finite space in which to store cookies, they
+ MAY also discard older cookies to make space for newer ones, using,
+ for example, a least-recently-used algorithm, along with constraints
+ on the maximum number of cookies that each origin server may set.
+
+ If a Set-Cookie2 response header includes a Comment attribute, the
+ user agent SHOULD store that information in a human-readable form
+ with the cookie and SHOULD display the comment text as part of a
+ cookie inspection user interface.
+
+ If a Set-Cookie2 response header includes a CommentURL attribute, the
+ user agent SHOULD store that information in a human-readable form
+ with the cookie, or, preferably, SHOULD allow the user to follow the
+ http_URL link as part of a cookie inspection user interface.
+
+ The cookie inspection user interface may include a facility whereby a
+ user can decide, at the time the user agent receives the Set-Cookie2
+ response header, whether or not to accept the cookie. A potentially
+ confusing situation could arise if the following sequence occurs:
+
+ * the user agent receives a cookie that contains a CommentURL
+ attribute;
+
+ * the user agent's cookie inspection interface is configured so
+ that it presents a dialog to the user before the user agent
+ accepts the cookie;
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 10]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ * the dialog allows the user to follow the CommentURL link when
+ the user agent receives the cookie; and,
+
+ * when the user follows the CommentURL link, the origin server
+ (or another server, via other links in the returned content)
+ returns another cookie.
+
+ The user agent SHOULD NOT send any cookies in this context. The user
+ agent MAY discard any cookie it receives in this context that the
+ user has not, through some user agent mechanism, deemed acceptable.
+
+ User agents SHOULD allow the user to control cookie destruction, but
+ they MUST NOT extend the cookie's lifetime beyond that controlled by
+ the Discard and Max-Age attributes. An infrequently-used cookie may
+ function as a "preferences file" for network applications, and a user
+ may wish to keep it even if it is the least-recently-used cookie. One
+ possible implementation would be an interface that allows the
+ permanent storage of a cookie through a checkbox (or, conversely, its
+ immediate destruction).
+
+ Privacy considerations dictate that the user have considerable
+ control over cookie management. The PRIVACY section contains more
+ information.
+
+ 3.3.4 Sending Cookies to the Origin Server When it sends a request
+ to an origin server, the user agent includes a Cookie request header
+ if it has stored cookies that are applicable to the request, based on
+
+ * the request-host and request-port;
+
+ * the request-URI;
+
+ * the cookie's age.
+
+ The syntax for the header is:
+
+cookie = "Cookie:" cookie-version 1*((";" | ",") cookie-value)
+cookie-value = NAME "=" VALUE [";" path] [";" domain] [";" port]
+cookie-version = "$Version" "=" value
+NAME = attr
+VALUE = value
+path = "$Path" "=" value
+domain = "$Domain" "=" value
+port = "$Port" [ "=" <"> value <"> ]
+
+ The value of the cookie-version attribute MUST be the value from the
+ Version attribute of the corresponding Set-Cookie2 response header.
+ Otherwise the value for cookie-version is 0. The value for the path
+
+
+
+Kristol & Montulli Standards Track [Page 11]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ attribute MUST be the value from the Path attribute, if one was
+ present, of the corresponding Set-Cookie2 response header. Otherwise
+ the attribute SHOULD be omitted from the Cookie request header. The
+ value for the domain attribute MUST be the value from the Domain
+ attribute, if one was present, of the corresponding Set-Cookie2
+ response header. Otherwise the attribute SHOULD be omitted from the
+ Cookie request header.
+
+ The port attribute of the Cookie request header MUST mirror the Port
+ attribute, if one was present, in the corresponding Set-Cookie2
+ response header. That is, the port attribute MUST be present if the
+ Port attribute was present in the Set-Cookie2 header, and it MUST
+ have the same value, if any. Otherwise, if the Port attribute was
+ absent from the Set-Cookie2 header, the attribute likewise MUST be
+ omitted from the Cookie request header.
+
+ Note that there is neither a Comment nor a CommentURL attribute in
+ the Cookie request header corresponding to the ones in the Set-
+ Cookie2 response header. The user agent does not return the comment
+ information to the origin server.
+
+ The user agent applies the following rules to choose applicable
+ cookie-values to send in Cookie request headers from among all the
+ cookies it has received.
+
+ Domain Selection
+ The origin server's effective host name MUST domain-match the
+ Domain attribute of the cookie.
+
+ Port Selection
+ There are three possible behaviors, depending on the Port
+ attribute in the Set-Cookie2 response header:
+
+ 1. By default (no Port attribute), the cookie MAY be sent to any
+ port.
+
+ 2. If the attribute is present but has no value (e.g., Port), the
+ cookie MUST only be sent to the request-port it was received
+ from.
+
+ 3. If the attribute has a port-list, the cookie MUST only be
+ returned if the new request-port is one of those listed in
+ port-list.
+
+ Path Selection
+ The request-URI MUST path-match the Path attribute of the cookie.
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 12]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ Max-Age Selection
+ Cookies that have expired should have been discarded and thus are
+ not forwarded to an origin server.
+
+ If multiple cookies satisfy the criteria above, they are ordered in
+ the Cookie header such that those with more specific Path attributes
+ precede those with less specific. Ordering with respect to other
+ attributes (e.g., Domain) is unspecified.
+
+ Note: For backward compatibility, the separator in the Cookie header
+ is semi-colon (;) everywhere. A server SHOULD also accept comma (,)
+ as the separator between cookie-values for future compatibility.
+
+ 3.3.5 Identifying What Version is Understood: Cookie2 The Cookie2
+ request header facilitates interoperation between clients and servers
+ that understand different versions of the cookie specification. When
+ the client sends one or more cookies to an origin server, if at least
+ one of those cookies contains a $Version attribute whose value is
+ different from the version that the client understands, then the
+ client MUST also send a Cookie2 request header, the syntax for which
+ is
+
+ cookie2 = "Cookie2:" cookie-version
+
+ Here the value for cookie-version is the highest version of cookie
+ specification (currently 1) that the client understands. The client
+ needs to send at most one such request header per request.
+
+ 3.3.6 Sending Cookies in Unverifiable Transactions Users MUST have
+ control over sessions in order to ensure privacy. (See PRIVACY
+ section below.) To simplify implementation and to prevent an
+ additional layer of complexity where adequate safeguards exist,
+ however, this document distinguishes between transactions that are
+ verifiable and those that are unverifiable. A transaction is
+ verifiable if the user, or a user-designated agent, has the option to
+ review the request-URI prior to its use in the transaction. A
+ transaction is unverifiable if the user does not have that option.
+ Unverifiable transactions typically arise when a user agent
+ automatically requests inlined or embedded entities or when it
+ resolves redirection (3xx) responses from an origin server.
+ Typically the origin transaction, the transaction that the user
+ initiates, is verifiable, and that transaction may directly or
+ indirectly induce the user agent to make unverifiable transactions.
+
+ An unverifiable transaction is to a third-party host if its request-
+ host U does not domain-match the reach R of the request-host O in the
+ origin transaction.
+
+
+
+
+Kristol & Montulli Standards Track [Page 13]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ When it makes an unverifiable transaction, a user agent MUST disable
+ all cookie processing (i.e., MUST NOT send cookies, and MUST NOT
+ accept any received cookies) if the transaction is to a third-party
+ host.
+
+ This restriction prevents a malicious service author from using
+ unverifiable transactions to induce a user agent to start or continue
+ a session with a server in a different domain. The starting or
+ continuation of such sessions could be contrary to the privacy
+ expectations of the user, and could also be a security problem.
+
+ User agents MAY offer configurable options that allow the user agent,
+ or any autonomous programs that the user agent executes, to ignore
+ the above rule, so long as these override options default to "off".
+
+ (N.B. Mechanisms may be proposed that will automate overriding the
+ third-party restrictions under controlled conditions.)
+
+ Many current user agents already provide a review option that would
+ render many links verifiable. For instance, some user agents display
+ the URL that would be referenced for a particular link when the mouse
+ pointer is placed over that link. The user can therefore determine
+ whether to visit that site before causing the browser to do so.
+ (Though not implemented on current user agents, a similar technique
+ could be used for a button used to submit a form -- the user agent
+ could display the action to be taken if the user were to select that
+ button.) However, even this would not make all links verifiable; for
+ example, links to automatically loaded images would not normally be
+ subject to "mouse pointer" verification.
+
+ Many user agents also provide the option for a user to view the HTML
+ source of a document, or to save the source to an external file where
+ it can be viewed by another application. While such an option does
+ provide a crude review mechanism, some users might not consider it
+ acceptable for this purpose.
+
+3.4 How an Origin Server Interprets the Cookie Header
+
+ A user agent returns much of the information in the Set-Cookie2
+ header to the origin server when the request-URI path-matches the
+ Path attribute of the cookie. When it receives a Cookie header, the
+ origin server SHOULD treat cookies with NAMEs whose prefix is $
+ specially, as an attribute for the cookie.
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 14]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+3.5 Caching Proxy Role
+
+ One reason for separating state information from both a URL and
+ document content is to facilitate the scaling that caching permits.
+ To support cookies, a caching proxy MUST obey these rules already in
+ the HTTP specification:
+
+ * Honor requests from the cache, if possible, based on cache
+ validity rules.
+
+ * Pass along a Cookie request header in any request that the
+ proxy must make of another server.
+
+ * Return the response to the client. Include any Set-Cookie2
+ response header.
+
+ * Cache the received response subject to the control of the usual
+ headers, such as Expires,
+
+ Cache-control: no-cache
+
+ and
+
+ Cache-control: private
+
+ * Cache the Set-Cookie2 subject to the control of the usual
+ header,
+
+ Cache-control: no-cache="set-cookie2"
+
+ (The Set-Cookie2 header should usually not be cached.)
+
+ Proxies MUST NOT introduce Set-Cookie2 (Cookie) headers of their own
+ in proxy responses (requests).
+
+4. EXAMPLES
+
+4.1 Example 1
+
+ Most detail of request and response headers has been omitted. Assume
+ the user agent has no stored cookies.
+
+ 1. User Agent -> Server
+
+ POST /acme/login HTTP/1.1
+ [form data]
+
+ User identifies self via a form.
+
+
+
+Kristol & Montulli Standards Track [Page 15]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ 2. Server -> User Agent
+
+ HTTP/1.1 200 OK
+ Set-Cookie2: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
+
+ Cookie reflects user's identity.
+
+ 3. User Agent -> Server
+
+ POST /acme/pickitem HTTP/1.1
+ Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"
+ [form data]
+
+ User selects an item for "shopping basket".
+
+ 4. Server -> User Agent
+
+ HTTP/1.1 200 OK
+ Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1";
+ Path="/acme"
+
+ Shopping basket contains an item.
+
+ 5. User Agent -> Server
+
+ POST /acme/shipping HTTP/1.1
+ Cookie: $Version="1";
+ Customer="WILE_E_COYOTE"; $Path="/acme";
+ Part_Number="Rocket_Launcher_0001"; $Path="/acme"
+ [form data]
+
+ User selects shipping method from form.
+
+ 6. Server -> User Agent
+
+ HTTP/1.1 200 OK
+ Set-Cookie2: Shipping="FedEx"; Version="1"; Path="/acme"
+
+ New cookie reflects shipping method.
+
+ 7. User Agent -> Server
+
+ POST /acme/process HTTP/1.1
+ Cookie: $Version="1";
+ Customer="WILE_E_COYOTE"; $Path="/acme";
+ Part_Number="Rocket_Launcher_0001"; $Path="/acme";
+ Shipping="FedEx"; $Path="/acme"
+ [form data]
+
+
+
+Kristol & Montulli Standards Track [Page 16]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ User chooses to process order.
+
+ 8. Server -> User Agent
+
+ HTTP/1.1 200 OK
+
+ Transaction is complete.
+
+ The user agent makes a series of requests on the origin server, after
+ each of which it receives a new cookie. All the cookies have the
+ same Path attribute and (default) domain. Because the request-URIs
+ all path-match /acme, the Path attribute of each cookie, each request
+ contains all the cookies received so far.
+
+4.2 Example 2
+
+ This example illustrates the effect of the Path attribute. All
+ detail of request and response headers has been omitted. Assume the
+ user agent has no stored cookies.
+
+ Imagine the user agent has received, in response to earlier requests,
+ the response headers
+
+ Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1";
+ Path="/acme"
+
+ and
+
+ Set-Cookie2: Part_Number="Riding_Rocket_0023"; Version="1";
+ Path="/acme/ammo"
+
+ A subsequent request by the user agent to the (same) server for URLs
+ of the form /acme/ammo/... would include the following request
+ header:
+
+ Cookie: $Version="1";
+ Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo";
+ Part_Number="Rocket_Launcher_0001"; $Path="/acme"
+
+ Note that the NAME=VALUE pair for the cookie with the more specific
+ Path attribute, /acme/ammo, comes before the one with the less
+ specific Path attribute, /acme. Further note that the same cookie
+ name appears more than once.
+
+ A subsequent request by the user agent to the (same) server for a URL
+ of the form /acme/parts/ would include the following request header:
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 17]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ Cookie: $Version="1"; Part_Number="Rocket_Launcher_0001";
+ $Path="/acme"
+
+ Here, the second cookie's Path attribute /acme/ammo is not a prefix
+ of the request URL, /acme/parts/, so the cookie does not get
+ forwarded to the server.
+
+5. IMPLEMENTATION CONSIDERATIONS
+
+ Here we provide guidance on likely or desirable details for an origin
+ server that implements state management.
+
+5.1 Set-Cookie2 Content
+
+ An origin server's content should probably be divided into disjoint
+ application areas, some of which require the use of state
+ information. The application areas can be distinguished by their
+ request URLs. The Set-Cookie2 header can incorporate information
+ about the application areas by setting the Path attribute for each
+ one.
+
+ The session information can obviously be clear or encoded text that
+ describes state. However, if it grows too large, it can become
+ unwieldy. Therefore, an implementor might choose for the session
+ information to be a key to a server-side resource. Of course, using
+ a database creates some problems that this state management
+ specification was meant to avoid, namely:
+
+ 1. keeping real state on the server side;
+
+ 2. how and when to garbage-collect the database entry, in case the
+ user agent terminates the session by, for example, exiting.
+
+5.2 Stateless Pages
+
+ Caching benefits the scalability of WWW. Therefore it is important
+ to reduce the number of documents that have state embedded in them
+ inherently. For example, if a shopping-basket-style application
+ always displays a user's current basket contents on each page, those
+ pages cannot be cached, because each user's basket's contents would
+ be different. On the other hand, if each page contains just a link
+ that allows the user to "Look at My Shopping Basket", the page can be
+ cached.
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 18]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+5.3 Implementation Limits
+
+ Practical user agent implementations have limits on the number and
+ size of cookies that they can store. In general, user agents' cookie
+ support should have no fixed limits. They should strive to store as
+ many frequently-used cookies as possible. Furthermore, general-use
+ user agents SHOULD provide each of the following minimum capabilities
+ individually, although not necessarily simultaneously:
+
+ * at least 300 cookies
+
+ * at least 4096 bytes per cookie (as measured by the characters
+ that comprise the cookie non-terminal in the syntax description
+ of the Set-Cookie2 header, and as received in the Set-Cookie2
+ header)
+
+ * at least 20 cookies per unique host or domain name
+
+ User agents created for specific purposes or for limited-capacity
+ devices SHOULD provide at least 20 cookies of 4096 bytes, to ensure
+ that the user can interact with a session-based origin server.
+
+ The information in a Set-Cookie2 response header MUST be retained in
+ its entirety. If for some reason there is inadequate space to store
+ the cookie, it MUST be discarded, not truncated.
+
+ Applications should use as few and as small cookies as possible, and
+ they should cope gracefully with the loss of a cookie.
+
+ 5.3.1 Denial of Service Attacks User agents MAY choose to set an
+ upper bound on the number of cookies to be stored from a given host
+ or domain name or on the size of the cookie information. Otherwise a
+ malicious server could attempt to flood a user agent with many
+ cookies, or large cookies, on successive responses, which would force
+ out cookies the user agent had received from other servers. However,
+ the minima specified above SHOULD still be supported.
+
+6. PRIVACY
+
+ Informed consent should guide the design of systems that use cookies.
+ A user should be able to find out how a web site plans to use
+ information in a cookie and should be able to choose whether or not
+ those policies are acceptable. Both the user agent and the origin
+ server must assist informed consent.
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 19]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+6.1 User Agent Control
+
+ An origin server could create a Set-Cookie2 header to track the path
+ of a user through the server. Users may object to this behavior as
+ an intrusive accumulation of information, even if their identity is
+ not evident. (Identity might become evident, for example, if a user
+ subsequently fills out a form that contains identifying information.)
+ This state management specification therefore requires that a user
+ agent give the user control over such a possible intrusion, although
+ the interface through which the user is given this control is left
+ unspecified. However, the control mechanisms provided SHALL at least
+ allow the user
+
+ * to completely disable the sending and saving of cookies.
+
+ * to determine whether a stateful session is in progress.
+
+ * to control the saving of a cookie on the basis of the cookie's
+ Domain attribute.
+
+ Such control could be provided, for example, by mechanisms
+
+ * to notify the user when the user agent is about to send a
+ cookie to the origin server, to offer the option not to begin a
+ session.
+
+ * to display a visual indication that a stateful session is in
+ progress.
+
+ * to let the user decide which cookies, if any, should be saved
+ when the user concludes a window or user agent session.
+
+ * to let the user examine and delete the contents of a cookie at
+ any time.
+
+ A user agent usually begins execution with no remembered state
+ information. It SHOULD be possible to configure a user agent never
+ to send Cookie headers, in which case it can never sustain state with
+ an origin server. (The user agent would then behave like one that is
+ unaware of how to handle Set-Cookie2 response headers.)
+
+ When the user agent terminates execution, it SHOULD let the user
+ discard all state information. Alternatively, the user agent MAY ask
+ the user whether state information should be retained; the default
+ should be "no". If the user chooses to retain state information, it
+ would be restored the next time the user agent runs.
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 20]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ NOTE: User agents should probably be cautious about using files to
+ store cookies long-term. If a user runs more than one instance of
+ the user agent, the cookies could be commingled or otherwise
+ corrupted.
+
+6.2 Origin Server Role
+
+ An origin server SHOULD promote informed consent by adding CommentURL
+ or Comment information to the cookies it sends. CommentURL is
+ preferred because of the opportunity to provide richer information in
+ a multiplicity of languages.
+
+6.3 Clear Text
+
+ The information in the Set-Cookie2 and Cookie headers is unprotected.
+ As a consequence:
+
+ 1. Any sensitive information that is conveyed in them is exposed
+ to intruders.
+
+ 2. A malicious intermediary could alter the headers as they travel
+ in either direction, with unpredictable results.
+
+ These facts imply that information of a personal and/or financial
+ nature should only be sent over a secure channel. For less sensitive
+ information, or when the content of the header is a database key, an
+ origin server should be vigilant to prevent a bad Cookie value from
+ causing failures.
+
+ A user agent in a shared user environment poses a further risk.
+ Using a cookie inspection interface, User B could examine the
+ contents of cookies that were saved when User A used the machine.
+
+7. SECURITY CONSIDERATIONS
+
+7.1 Protocol Design
+
+ The restrictions on the value of the Domain attribute, and the rules
+ concerning unverifiable transactions, are meant to reduce the ways
+ that cookies can "leak" to the "wrong" site. The intent is to
+ restrict cookies to one host, or a closely related set of hosts.
+ Therefore a request-host is limited as to what values it can set for
+ Domain. We consider it acceptable for hosts host1.foo.com and
+ host2.foo.com to share cookies, but not a.com and b.com.
+
+ Similarly, a server can set a Path only for cookies that are related
+ to the request-URI.
+
+
+
+
+Kristol & Montulli Standards Track [Page 21]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+7.2 Cookie Spoofing
+
+ Proper application design can avoid spoofing attacks from related
+ domains. Consider:
+
+ 1. User agent makes request to victim.cracker.edu, gets back
+ cookie session_id="1234" and sets the default domain
+ victim.cracker.edu.
+
+ 2. User agent makes request to spoof.cracker.edu, gets back cookie
+ session-id="1111", with Domain=".cracker.edu".
+
+ 3. User agent makes request to victim.cracker.edu again, and
+ passes
+
+ Cookie: $Version="1"; session_id="1234",
+ $Version="1"; session_id="1111"; $Domain=".cracker.edu"
+
+ The server at victim.cracker.edu should detect that the second
+ cookie was not one it originated by noticing that the Domain
+ attribute is not for itself and ignore it.
+
+7.3 Unexpected Cookie Sharing
+
+ A user agent SHOULD make every attempt to prevent the sharing of
+ session information between hosts that are in different domains.
+ Embedded or inlined objects may cause particularly severe privacy
+ problems if they can be used to share cookies between disparate
+ hosts. For example, a malicious server could embed cookie
+ information for host a.com in a URI for a CGI on host b.com. User
+ agent implementors are strongly encouraged to prevent this sort of
+ exchange whenever possible.
+
+7.4 Cookies For Account Information
+
+ While it is common practice to use them this way, cookies are not
+ designed or intended to be used to hold authentication information,
+ such as account names and passwords. Unless such cookies are
+ exchanged over an encrypted path, the account information they
+ contain is highly vulnerable to perusal and theft.
+
+8. OTHER, SIMILAR, PROPOSALS
+
+ Apart from RFC 2109, three other proposals have been made to
+ accomplish similar goals. This specification began as an amalgam of
+ Kristol's State-Info proposal [DMK95] and Netscape's Cookie proposal
+ [Netscape].
+
+
+
+
+Kristol & Montulli Standards Track [Page 22]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ Brian Behlendorf proposed a Session-ID header that would be user-
+ agent-initiated and could be used by an origin server to track
+ "clicktrails". It would not carry any origin-server-defined state,
+ however. Phillip Hallam-Baker has proposed another client-defined
+ session ID mechanism for similar purposes.
+
+ While both session IDs and cookies can provide a way to sustain
+ stateful sessions, their intended purpose is different, and,
+ consequently, the privacy requirements for them are different. A
+ user initiates session IDs to allow servers to track progress through
+ them, or to distinguish multiple users on a shared machine. Cookies
+ are server-initiated, so the cookie mechanism described here gives
+ users control over something that would otherwise take place without
+ the users' awareness. Furthermore, cookies convey rich, server-
+ selected information, whereas session IDs comprise user-selected,
+ simple information.
+
+9. HISTORICAL
+
+9.1 Compatibility with Existing Implementations
+
+ Existing cookie implementations, based on the Netscape specification,
+ use the Set-Cookie (not Set-Cookie2) header. User agents that
+ receive in the same response both a Set-Cookie and Set-Cookie2
+ response header for the same cookie MUST discard the Set-Cookie
+ information and use only the Set-Cookie2 information. Furthermore, a
+ user agent MUST assume, if it received a Set-Cookie2 response header,
+ that the sending server complies with this document and will
+ understand Cookie request headers that also follow this
+ specification.
+
+ New cookies MUST replace both equivalent old- and new-style cookies.
+ That is, if a user agent that follows both this specification and
+ Netscape's original specification receives a Set-Cookie2 response
+ header, and the NAME and the Domain and Path attributes match (per
+ the Cookie Management section) a Netscape-style cookie, the
+ Netscape-style cookie MUST be discarded, and the user agent MUST
+ retain only the cookie adhering to this specification.
+
+ Older user agents that do not understand this specification, but that
+ do understand Netscape's original specification, will not recognize
+ the Set-Cookie2 response header and will receive and send cookies
+ according to the older specification.
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 23]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+ A user agent that supports both this specification and Netscape-style
+ cookies SHOULD send a Cookie request header that follows the older
+ Netscape specification if it received the cookie in a Set-Cookie
+ response header and not in a Set-Cookie2 response header. However,
+ it SHOULD send the following request header as well:
+
+ Cookie2: $Version="1"
+
+ The Cookie2 header advises the server that the user agent understands
+ new-style cookies. If the server understands new-style cookies, as
+ well, it SHOULD continue the stateful session by sending a Set-
+ Cookie2 response header, rather than Set-Cookie. A server that does
+ not understand new-style cookies will simply ignore the Cookie2
+ request header.
+
+9.2 Caching and HTTP/1.0
+
+ Some caches, such as those conforming to HTTP/1.0, will inevitably
+ cache the Set-Cookie2 and Set-Cookie headers, because there was no
+ mechanism to suppress caching of headers prior to HTTP/1.1. This
+ caching can lead to security problems. Documents transmitted by an
+ origin server along with Set-Cookie2 and Set-Cookie headers usually
+ either will be uncachable, or will be "pre-expired". As long as
+ caches obey instructions not to cache documents (following Expires:
+ <a date in the past> or Pragma: no-cache (HTTP/1.0), or Cache-
+ control: no-cache (HTTP/1.1)) uncachable documents present no
+ problem. However, pre-expired documents may be stored in caches.
+ They require validation (a conditional GET) on each new request, but
+ some cache operators loosen the rules for their caches, and sometimes
+ serve expired documents without first validating them. This
+ combination of factors can lead to cookies meant for one user later
+ being sent to another user. The Set-Cookie2 and Set-Cookie headers
+ are stored in the cache, and, although the document is stale
+ (expired), the cache returns the document in response to later
+ requests, including cached headers.
+
+10. ACKNOWLEDGEMENTS
+
+ This document really represents the collective efforts of the HTTP
+ Working Group of the IETF and, particularly, the following people, in
+ addition to the authors: Roy Fielding, Yaron Goland, Marc Hedlund,
+ Ted Hardie, Koen Holtman, Shel Kaphan, Rohit Khare, Foteos Macrides,
+ David W. Morris.
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 24]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+11. AUTHORS' ADDRESSES
+
+ David M. Kristol
+ Bell Laboratories, Lucent Technologies
+ 600 Mountain Ave. Room 2A-333
+ Murray Hill, NJ 07974
+
+ Phone: (908) 582-2250
+ Fax: (908) 582-1239
+ EMail: dmk@bell-labs.com
+
+
+ Lou Montulli
+ Epinions.com, Inc.
+ 2037 Landings Dr.
+ Mountain View, CA 94301
+
+ EMail: lou@montulli.org
+
+12. REFERENCES
+
+ [DMK95] Kristol, D.M., "Proposed HTTP State-Info Mechanism",
+ available at <http://portal.research.bell-
+ labs.com/~dmk/state-info.html>, September, 1995.
+
+ [Netscape] "Persistent Client State -- HTTP Cookies", available at
+ <http://www.netscape.com/newsref/std/cookie_spec.html>,
+ undated.
+
+ [RFC2109] Kristol, D. and L. Montulli, "HTTP State Management
+ Mechanism", RFC 2109, February 1997.
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119, March 1997.
+
+ [RFC2279] Yergeau, F., "UTF-8, a transformation format of Unicode
+ and ISO-10646", RFC 2279, January 1998.
+
+ [RFC2396] Berners-Lee, T., Fielding, R. and L. Masinter, "Uniform
+ Resource Identifiers (URI): Generic Syntax", RFC 2396,
+ August 1998.
+
+ [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H. and T.
+ Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1",
+ RFC 2616, June 1999.
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 25]
+
+RFC 2965 HTTP State Management Mechanism October 2000
+
+
+13. Full Copyright Statement
+
+ Copyright (C) The Internet Society (2000). All Rights Reserved.
+
+ This document and translations of it may be copied and furnished to
+ others, and derivative works that comment on or otherwise explain it
+ or assist in its implementation may be prepared, copied, published
+ and distributed, in whole or in part, without restriction of any
+ kind, provided that the above copyright notice and this paragraph are
+ included on all such copies and derivative works. However, this
+ document itself may not be modified in any way, such as by removing
+ the copyright notice or references to the Internet Society or other
+ Internet organizations, except as needed for the purpose of
+ developing Internet standards in which case the procedures for
+ copyrights defined in the Internet Standards process must be
+ followed, or as required to translate it into languages other than
+ English.
+
+ The limited permissions granted above are perpetual and will not be
+ revoked by the Internet Society or its successors or assigns.
+
+ This document and the information contained herein is provided on an
+ "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
+ TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+ BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
+ HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+Acknowledgement
+
+ Funding for the RFC Editor function is currently provided by the
+ Internet Society.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Kristol & Montulli Standards Track [Page 26]
+
diff --git a/kioslave/http/kcookiejar/tests/Makefile.am b/kioslave/http/kcookiejar/tests/Makefile.am
new file mode 100644
index 000000000..b79dd10fb
--- /dev/null
+++ b/kioslave/http/kcookiejar/tests/Makefile.am
@@ -0,0 +1,18 @@
+# $Id$
+# Makefile.am of kdebase/kioslave/http
+
+INCLUDES= $(all_includes)
+
+####### Files
+
+check_PROGRAMS = kcookiejartest
+
+kcookiejartest_SOURCES = kcookiejartest.cpp
+kcookiejartest_LDADD = $(LIB_KIO)
+kcookiejartest_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+check-local: kcookiejartest
+ ./kcookiejartest $(srcdir)/cookie.test
+ ./kcookiejartest $(srcdir)/cookie_rfc.test
+ ./kcookiejartest $(srcdir)/cookie_saving.test
+ ./kcookiejartest $(srcdir)/cookie_settings.test
diff --git a/kioslave/http/kcookiejar/tests/cookie.test b/kioslave/http/kcookiejar/tests/cookie.test
new file mode 100644
index 000000000..6619bf82d
--- /dev/null
+++ b/kioslave/http/kcookiejar/tests/cookie.test
@@ -0,0 +1,162 @@
+## Check setting of cookies
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR%
+CHECK http://w.y.z/ Cookie: some_value=value1
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/"
+CHECK http://a.b.c/ Cookie: some_value=value2
+## Check if clearing cookie jar works
+CLEAR COOKIES
+CHECK http://w.y.z/
+CHECK http://a.b.c/
+## Check cookie syntax
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value with spaces
+CHECK http://w.y.z/ Cookie: some_value=value with spaces
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value"
+CHECK http://a.b.c/ Cookie: some_value="quoted value"
+# Without a = sign, the cookie gets interpreted as the value for a cookie with no name
+# This is what IE and Netscape does
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value
+CHECK http://a.b.c/ Cookie: some_value; some_value="quoted value"
+COOKIE ASK http://a.b.c/ Set-Cookie: some_other_value
+CHECK http://a.b.c/ Cookie: some_other_value; some_value="quoted value"
+CLEAR COOKIES
+# This doesn't work with old-style netscape cookies, it should work with RFC2965 cookies
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value; and such"
+# IE & Netscape does this:
+CHECK http://a.b.c/ Cookie: some_value="quoted value
+# Mozilla does:
+# CHECK http://a.b.c/ Cookie: some_value="quoted value; and such"
+# COOKIE ASK http://a.b.c/ Set-Cookie: some_value="quoted value;
+# CHECK http://a.b.c/ Cookie: some_value=
+# Note that we parse RFC2965 cookies like Mozilla does
+CLEAR COOKIES
+## Check if deleting cookies works
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR%
+CHECK http://w.y.z/ Cookie: some_value=value1
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%LASTYEAR%
+CHECK http://w.y.z/
+## Check if updating cookies works
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value2; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR%
+CHECK http://w.y.z/ Cookie: some_value=value3
+## Check if multiple cookies work
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value2=foobar; Path="/"; expires=%NEXTYEAR%
+CHECK http://w.y.z/ Cookie: some_value2=foobar; some_value=value3
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=; Path="/"; expires=%LASTYEAR%
+CHECK http://w.y.z/ Cookie: some_value2=foobar
+CLEAR COOKIES
+## Check if path restrictions work
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR%
+CHECK http://w.y.z/
+CHECK http://w.y.z/Foo Cookie: some_value=value1
+CHECK http://w.y.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y.z/Foo/bar Cookie: some_value=value1
+CLEAR COOKIES
+## Check if default path works
+# RFC2965 says that we should default to the URL path, but netscape cookies default to /
+COOKIE ASK http://w.y.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR%
+CHECK http://w.y.z/
+CHECK http://w.y.z/Foo Cookie: some_value=value1
+CHECK http://w.y.z/FooBar
+CHECK http://w.y.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y.z/Foo/bar Cookie: some_value=value1
+CLEAR COOKIES
+## Check if cookies are correctly ordered based on path
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR%
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value2=value2; Path="/Foo/Bar"; expires=%NEXTYEAR%
+CHECK http://w.y.z/Foo/Bar Cookie: some_value2=value2; some_value=value1
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%
+CHECK http://w.y.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3
+CLEAR COOKIES
+## Check cookies with same name but different paths
+COOKIE ASK http://w.y.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR%
+COOKIE ASK http://w.y.z/Bar/ Set-Cookie: some_value=value2; expires=%NEXTYEAR%
+CHECK http://w.y.z/Foo/Bar Cookie: some_value=value1
+CHECK http://w.y.z/Bar/Foo Cookie: some_value=value2
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value3; expires=%NEXTYEAR%
+CHECK http://w.y.z/Foo/Bar Cookie: some_value=value1; some_value=value3
+## Check secure cookie handling
+COOKIE ASK https://secure.y.z/ Set-Cookie: some_value2=value2; Path="/"; expires=%NEXTYEAR%; secure
+CHECK https://secure.y.z/Foo/bar Cookie: some_value2=value2
+CHECK http://secure.y.z/Foo/bar
+CLEAR COOKIES
+COOKIE ASK http://secure.y.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%; secure
+CHECK https://secure.y.z/Foo/bar Cookie: some_value3=value3
+CHECK http://secure.y.z/Foo/bar
+CLEAR COOKIES
+## Check domain restrictions #1
+COOKIE ASK http://www.acme.com/ Set-Cookie: some_value=value1; Domain=".acme.com"; expires=%NEXTYEAR%
+CHECK http://www.acme.com/ Cookie: some_value=value1
+CHECK http://www.abc.com/
+CHECK http://frop.acme.com/ Cookie: some_value=value1
+CLEAR COOKIES
+## Check domain restrictions #2
+COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain=".novell.com"; expires=%NEXTYEAR%
+CHECK http://novell.com/ Cookie: some_value=value1
+CHECK http://www.novell.com/ Cookie: some_value=value1
+CLEAR COOKIES
+COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain="novell.com"; expires=%NEXTYEAR%
+CHECK http://novell.com/ Cookie: some_value=value1
+CHECK http://www.novell.com/ Cookie: some_value=value1
+CLEAR COOKIES
+## Check domain restrictions #3
+COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; expires=%NEXTYEAR%
+CHECK http://novell.com/ Cookie: some_value=value1
+# FIXME: Allegedly IE sends cookies to sub-domains as well!
+# See e.g. https://bugzilla.mozilla.org/show_bug.cgi?id=223027
+CHECK http://www.novell.com/
+CLEAR COOKIES
+## Check domain restrictions #4
+COOKIE ASK http://novell.com/ Set-Cookie: some_value=value1; Domain=".com"; expires=%NEXTYEAR%
+CHECK http://novell.com/ Cookie: some_value=value1
+# If the specified domain is too broad, we default to host only
+CHECK http://www.novell.com/
+CHECK http://com/
+CHECK http://sun.com/
+## Check domain restrictions #5
+CLEAR COOKIES
+COOKIE ASK http://novell.co.uk/ Set-Cookie: some_value=value1; Domain=".co.uk"; expires=%NEXTYEAR%
+CHECK http://novell.co.uk/ Cookie: some_value=value1
+# If the specified domain is too broad, we default to host only
+CHECK http://www.novell.co.uk/
+CHECK http://co.uk/
+CHECK http://sun.co.uk/
+COOKIE ASK http://x.y.z.foobar.com/ Set-Cookie: set_by=x.y.z.foobar.com; Domain=".foobar.com"; expires=%NEXTYEAR%
+CHECK http://x.y.z.foobar.com/ Cookie: set_by=x.y.z.foobar.com
+CHECK http://y.z.foobar.com/ Cookie: set_by=x.y.z.foobar.com
+CHECK http://z.foobar.com/ Cookie: set_by=x.y.z.foobar.com
+CHECK http://www.foobar.com/ Cookie: set_by=x.y.z.foobar.com
+CHECK http://foobar.com/ Cookie: set_by=x.y.z.foobar.com
+CLEAR COOKIES
+## Check domain restrictions #6
+COOKIE ASK http://x.y.z.frop.com/ Set-Cookie: set_by=x.y.z.frop.com; Domain=".foobar.com"; expires=%NEXTYEAR%
+COOKIE ASK http://x.y.z.frop.com/ Set-Cookie: set_by2=x.y.z.frop.com; Domain=".com"; expires=%NEXTYEAR%
+CHECK http://x.y.z.foobar.com/
+CHECK http://y.z.foobar.com/
+CHECK http://z.foobar.com/
+CHECK http://www.foobar.com/
+CHECK http://foobar.com/
+CLEAR COOKIES
+## Check domain restrictions #7
+COOKIE ASK http://frop.com/ Set-Cookie: set_by=x.y.z.frop.com; Domain=".foobar.com"; expires=%NEXTYEAR%
+COOKIE ASK http://frop.com/ Set-Cookie: set_by2=x.y.z.frop.com; Domain=".com"; expires=%NEXTYEAR%
+CHECK http://x.y.z.foobar.com/
+CHECK http://y.z.foobar.com/
+CHECK http://z.foobar.com/
+CHECK http://www.foobar.com/
+CHECK http://foobar.com/
+CLEAR COOKIES
+## Check domain restrictions #8
+CONFIG AcceptSessionCookies true
+COOKIE ACCEPT http://www.foobar.com Set-Cookie: from=foobar.com; domain=bar.com; Path="/"
+CHECK http://bar.com
+CLEAR COOKIES
+## Check cookies with IP address hostnames
+COOKIE ASK http://192.168.0.1 Set-Cookie: name1=value1; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://192.168.0.1 Set-Cookie: name11=value11; domain="test.local"; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://192.168.0.1:8080 Set-Cookie: name2=value2; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK https://192.168.0.1 Set-Cookie: name3=value3; Path="/"; expires=%NEXTYEAR%; secure
+CHECK http://192.168.0.1 Cookie: name11=value11; name1=value1
+CHECK http://192.168.0.1:8080 Cookie: name2=value2
+CHECK https://192.168.0.1 Cookie: name3=value3; name11=value11; name1=value1
+CHECK http://192.168.0.10
+CHECK http://192.168.0
diff --git a/kioslave/http/kcookiejar/tests/cookie_rfc.test b/kioslave/http/kcookiejar/tests/cookie_rfc.test
new file mode 100644
index 000000000..e1d8a40de
--- /dev/null
+++ b/kioslave/http/kcookiejar/tests/cookie_rfc.test
@@ -0,0 +1,148 @@
+## Check setting of cookies
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600
+# Although the examples in RFC2965 uses $Version="1" the syntax description suggests that
+# such quotes are not allowed, KDE BR59990 reports that the Sun Java server fails to handle
+# cookies that use $Version="1"
+CHECK http://w.y.z/ Cookie: $Version=1; some_value="value1"; $Path="/"
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value="value2"; Version=1; Path="/"
+CHECK http://a.b.c/ Cookie: $Version=1; some_value="value2"; $Path="/"
+## Check if clearing cookie jar works
+CLEAR COOKIES
+CHECK http://w.y.z/
+CHECK http://a.b.c/
+## Check cookie syntax
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value with spaces"; Version=1
+CHECK http://w.y.z/ Cookie: $Version=1; some_value="value with spaces"
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value ="extra space 1"; Version=1
+CHECK http://w.y.z/ Cookie: $Version=1; some_value="extra space 1"
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value= "extra space 2"; Version=1
+CHECK http://w.y.z/ Cookie: $Version=1; some_value="extra space 2"
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=unquoted; Version=1
+CHECK http://a.b.c/ Cookie: $Version=1; some_value=unquoted
+# Note that we parse this different for Netscape-style cookies!
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value="quoted value; and such"; Version=1;
+CHECK http://a.b.c/ Cookie: $Version=1; some_value="quoted value; and such"
+CLEAR COOKIES
+## Check if deleting cookies works #1
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600
+CHECK http://w.y.z/ Cookie: $Version=1; some_value="value1"; $Path="/"
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/"; Max-Age=0
+CHECK http://w.y.z/
+## Check if updating cookies works
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Max-Age=3600
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600
+CHECK http://w.y.z/ Cookie: $Version=1; some_value=value3; $Path="/"
+## Check if multiple cookies work
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value2=foobar; Version=1; Path="/"; Max-Age=3600
+CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"; some_value=value3; $Path="/"
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0
+CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"
+CLEAR COOKIES
+## Check if we prepend domain with a dot
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Domain=.y.z; Max-Age=3600
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Domain=y.z.; Max-Age=3600
+CHECK http://w.y.z/ Cookie: $Version=1; some_value=value3; $Path="/"; $Domain=".y.z"
+CLEAR COOKIES
+## Check if multiple cookies on a single line work
+## FIXME
+#COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600, some_value2=foobar; Version=1; Path="/"; Max-Age=3600
+# CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"; some_value=value3; $Path="/"
+# COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0
+# CHECK http://w.y.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"
+CLEAR COOKIES
+## Check if path restrictions work
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600
+CHECK http://w.y.z/
+CHECK http://w.y.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CLEAR COOKIES
+## Check if default path works
+# RFC2965 says that we should default to the URL path
+COOKIE ASK http://w.y.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600
+CHECK http://w.y.z/
+CHECK http://w.y.z/Foo Cookie: $Version=1; some_value=value1
+CHECK http://w.y.z/FooBar
+CHECK http://w.y.z/Foo/ Cookie: $Version=1; some_value=value1
+CHECK http://w.y.z/Foo/bar Cookie: $Version=1; some_value=value1
+CLEAR COOKIES
+## Check if cookies are correctly ordered based on path
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/Foo/Bar"; Max-Age=3600
+CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600
+CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/"
+CLEAR COOKIES
+## Check cookies with same name but different paths
+COOKIE ASK http://w.y.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600
+COOKIE ASK http://w.y.z/Bar/ Set-Cookie2: some_value=value2; Version=1; Max-Age=3600
+CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value=value1
+CHECK http://w.y.z/Bar/Foo Cookie: $Version=1; some_value=value2
+COOKIE ASK http://w.y.z/ Set-Cookie2: some_value=value3; Version=1; Max-Age=3600
+CHECK http://w.y.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3
+## Check secure cookie handling
+COOKIE ASK https://secure.y.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/"; Max-Age=3600; Secure
+CHECK https://secure.y.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/"
+CHECK http://secure.y.z/Foo/bar
+CLEAR COOKIES
+COOKIE ASK http://secure.y.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600; Secure
+CHECK https://secure.y.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/"
+CHECK http://secure.y.z/Foo/bar
+CLEAR COOKIES
+## Check domain restrictions #1
+COOKIE ASK http://www.acme.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".acme.com"; Max-Age=3600
+CHECK http://www.acme.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme.com"
+CHECK http://www.abc.com/
+CHECK http://frop.acme.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme.com"
+CLEAR COOKIES
+## Check domain restrictions #2
+COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".novell.com"; Max-Age=3600
+CHECK http://novell.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell.com"
+CHECK http://www.novell.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell.com"
+CLEAR COOKIES
+## Check domain restrictions #3
+COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600
+CHECK http://novell.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell.com/
+CLEAR COOKIES
+## Check domain restrictions #4
+COOKIE ASK http://novell.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".com"; Max-Age=3600
+# If the specified domain is too broad, we ignore the Domain
+# FIXME: RFC2965 says we should ignore the cookie completely
+CHECK http://novell.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell.com/
+CHECK http://com/
+CHECK http://sun.com/
+## Check domain restrictions #5
+CLEAR COOKIES
+COOKIE ASK http://novell.co.uk/ Set-Cookie2: some_value=value1; Version=1; Domain=".co.uk"; Max-Age=3600
+# If the specified domain is too broad, we default to host only
+# FIXME: RFC2965 says we should ignore the cookie completely
+CHECK http://novell.co.uk/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell.co.uk/
+CHECK http://co.uk/
+CHECK http://sun.co.uk/
+COOKIE ASK http://x.y.z.foobar.com/ Set-Cookie2: set_by=x.y.z.foobar.com; Version=1; Domain=".foobar.com"; Max-Age=3600
+CHECK http://x.y.z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com"
+CHECK http://y.z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com"
+CHECK http://z.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com"
+CHECK http://www.foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com"
+CHECK http://foobar.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar.com"
+CLEAR COOKIES
+## Check domain restrictions #6
+COOKIE ASK http://x.y.z.frop.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600
+COOKIE ASK http://x.y.z.frop.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600
+CHECK http://x.y.z.foobar.com/
+CHECK http://y.z.foobar.com/
+CHECK http://z.foobar.com/
+CHECK http://www.foobar.com/
+CHECK http://foobar.com/
+CLEAR COOKIES
+## Check domain restrictions #7
+COOKIE ASK http://frop.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600
+COOKIE ASK http://frop.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600
+CHECK http://x.y.z.foobar.com/
+CHECK http://y.z.foobar.com/
+CHECK http://z.foobar.com/
+CHECK http://www.foobar.com/
+CHECK http://foobar.com/
diff --git a/kioslave/http/kcookiejar/tests/cookie_saving.test b/kioslave/http/kcookiejar/tests/cookie_saving.test
new file mode 100644
index 000000000..cb9f34c42
--- /dev/null
+++ b/kioslave/http/kcookiejar/tests/cookie_saving.test
@@ -0,0 +1,430 @@
+## Check setting of cookies
+COOKIE ASK http://w.y.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/"
+## Check if clearing cookie jar works
+CLEAR COOKIES
+## Check cookie syntax
+COOKIE ASK http://w.y1.z/ Set-Cookie: some_value=value with spaces; expires=%NEXTYEAR%
+COOKIE ASK http://a.b1.c/ Set-Cookie: some_value="quoted value"; expires=%NEXTYEAR%
+# Without a = sign, the cookie gets interpreted as the value for a cookie with no name
+# This is what IE and Netscape does
+COOKIE ASK http://a.b1.c/ Set-Cookie: some_value
+COOKIE ASK http://a.b1.c/ Set-Cookie: some_other_value; expires=%NEXTYEAR%
+# This doesn't work with old-style netscape cookies, it should work with RFC2965 cookies
+COOKIE ASK http://a.b2.c/ Set-Cookie: some_value="quoted value; and such"; expires=%NEXTYEAR%
+# IE & Netscape does this:
+## Check if deleting cookies works
+COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value1; Path="/"; expires=%LASTYEAR%
+## Check if updating cookies works
+COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value2; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR%
+## Check if multiple cookies work
+COOKIE ASK http://w.y3.z/ Set-Cookie: some_value2=foobar; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://w.y3.z/ Set-Cookie: some_value=; Path="/"; expires=%LASTYEAR%
+## Check if path restrictions work
+COOKIE ASK http://w.y4.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR%
+## Check if default path works
+COOKIE ASK http://w.y5.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR%
+## Check if cookies are correctly ordered based on path
+COOKIE ASK http://w.y6.z/ Set-Cookie: some_value=value1; Path="/Foo"; expires=%NEXTYEAR%
+COOKIE ASK http://w.y6.z/ Set-Cookie: some_value2=value2; Path="/Foo/Bar"; expires=%NEXTYEAR%
+COOKIE ASK http://w.y6.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%
+## Check cookies with same name but different paths
+COOKIE ASK http://w.y7.z/Foo/ Set-Cookie: some_value=value1; expires=%NEXTYEAR%
+COOKIE ASK http://w.y7.z/Bar/ Set-Cookie: some_value=value2; expires=%NEXTYEAR%
+COOKIE ASK http://w.y7.z/ Set-Cookie: some_value=value3; expires=%NEXTYEAR%
+## Check secure cookie handling
+COOKIE ASK https://secure.y7.z/ Set-Cookie: some_value2=value2; Path="/"; expires=%NEXTYEAR%; secure
+COOKIE ASK http://secure.y8.z/ Set-Cookie: some_value3=value3; Path="/"; expires=%NEXTYEAR%; secure
+## Check domain restrictions #1
+COOKIE ASK http://www.acme9.com/ Set-Cookie: some_value=value1; Domain=".acme9.com"; expires=%NEXTYEAR%
+## Check domain restrictions #2
+COOKIE ASK http://novell10.com/ Set-Cookie: some_value=value1; Domain=".novell10.com"; expires=%NEXTYEAR%
+COOKIE ASK http://novell11.com/ Set-Cookie: some_value=value1; Domain="novell11.com"; expires=%NEXTYEAR%
+## Check domain restrictions #3
+COOKIE ASK http://novell12.com/ Set-Cookie: some_value=value1; expires=%NEXTYEAR%
+## Check domain restrictions #4
+COOKIE ASK http://novell13.com/ Set-Cookie: some_value=value1; Domain=".com"; expires=%NEXTYEAR%
+# If the specified domain is too broad, we default to host only
+## Check domain restrictions #5
+COOKIE ASK http://novell14.co.uk/ Set-Cookie: some_value=value1; Domain=".co.uk"; expires=%NEXTYEAR%
+COOKIE ASK http://x.y.z.foobar14.com/ Set-Cookie: set_by=x.y.z.foobar14.com; Domain=".foobar14.com"; expires=%NEXTYEAR%
+## Check domain restrictions #6
+COOKIE ASK http://x.y.z.frop15.com/ Set-Cookie: set_by=x.y.z.frop15.com; Domain=".foobar15.com"; expires=%NEXTYEAR%
+COOKIE ASK http://x.y.z.frop15.com/ Set-Cookie: set_by2=x.y.z.frop15.com; Domain=".com"; expires=%NEXTYEAR%
+## Check domain restrictions #7
+COOKIE ASK http://frop16.com/ Set-Cookie: set_by=x.y.z.frop16.com; Domain=".foobar16.com"; expires=%NEXTYEAR%
+COOKIE ASK http://frop16.com/ Set-Cookie: set_by2=x.y.z.frop16.com; Domain=".com"; expires=%NEXTYEAR%
+## RFC Cookies
+## Check setting of cookies
+COOKIE ASK http://w.y20.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600
+# Although the examples in RFC2965 uses $Version="1" the syntax description suggests that
+# such quotes are not allowed, KDE BR59990 reports that the Sun Java server fails to handle
+# cookies that use $Version="1"
+COOKIE ASK http://a.b20.c/ Set-Cookie2: some_value="value2"; Version=1; Path="/"; Max-Age=3600
+## Check cookie syntax
+COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value="value with spaces"; Version=1; Max-Age=3600
+COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value ="extra space 1"; Version=1; Max-Age=3600
+COOKIE ASK http://w.y21.z/ Set-Cookie2: some_value= "extra space 2"; Version=1; Max-Age=3600
+COOKIE ASK http://a.b21.c/ Set-Cookie2: some_value=unquoted; Version=1; Max-Age=3600
+# Note that we parse this different for Netscape-style cookies!
+COOKIE ASK http://a.b21.c/ Set-Cookie2: some_value="quoted value; and such"; Version=1; Max-Age=3600
+## Check if deleting cookies works #1
+COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value="value1"; Version=1; Path="/"; Max-Age=3600
+COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value1; Version=1; Path="/"; Max-Age=0
+## Check if updating cookies works
+COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value2; Version=1; Path="/"; Max-Age=3600
+COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=value3; Version=1; Path="/"; Max-Age=3600
+## Check if multiple cookies work
+COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value2=foobar; Version=1; Path="/"; Max-Age=3600
+COOKIE ASK http://w.y22.z/ Set-Cookie2: some_value=; Version=1; Path="/"; Max-Age=0
+## Check if path restrictions work
+COOKIE ASK http://w.y23.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600
+## Check if default path works
+# RFC2965 says that we should default to the URL path
+COOKIE ASK http://w.y24.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600
+## Check if cookies are correctly ordered based on path
+COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value=value1; Version=1; Path="/Foo"; Max-Age=3600
+COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/Foo/Bar"; Max-Age=3600
+COOKIE ASK http://w.y25.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600
+## Check cookies with same name but different paths
+COOKIE ASK http://w.y26.z/Foo/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600
+COOKIE ASK http://w.y26.z/Bar/ Set-Cookie2: some_value=value2; Version=1; Max-Age=3600
+COOKIE ASK http://w.y26.z/ Set-Cookie2: some_value=value3; Version=1; Max-Age=3600
+## Check secure cookie handling
+COOKIE ASK https://secure.y26.z/ Set-Cookie2: some_value2=value2; Version=1; Path="/"; Max-Age=3600; Secure
+COOKIE ASK http://secure.y27.z/ Set-Cookie2: some_value3=value3; Version=1; Path="/"; Max-Age=3600; Secure
+## Check domain restrictions #1
+COOKIE ASK http://www.acme28.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".acme28.com"; Max-Age=3600
+## Check domain restrictions #2
+COOKIE ASK http://novell29.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".novell29.com"; Max-Age=3600
+## Check domain restrictions #3
+COOKIE ASK http://novell30.com/ Set-Cookie2: some_value=value1; Version=1; Max-Age=3600
+## Check domain restrictions #4
+COOKIE ASK http://novell31.com/ Set-Cookie2: some_value=value1; Version=1; Domain=".com"; Max-Age=3600
+# If the specified domain is too broad, we ignore the Domain
+# FIXME: RFC2965 says we should ignore the cookie completely
+## Check domain restrictions #5
+COOKIE ASK http://novell32.co.uk/ Set-Cookie2: some_value=value1; Version=1; Domain=".co.uk"; Max-Age=3600
+# If the specified domain is too broad, we default to host only
+# FIXME: RFC2965 says we should ignore the cookie completely
+COOKIE ASK http://x.y.z.foobar33.com/ Set-Cookie2: set_by=x.y.z.foobar.com; Version=1; Domain=".foobar33.com"; Max-Age=3600
+## Check domain restrictions #6
+COOKIE ASK http://x.y.z.frop34.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600
+COOKIE ASK http://x.y.z.frop34.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600
+## Check domain restrictions #7
+COOKIE ASK http://frop35.com/ Set-Cookie2: set_by=x.y.z.frop.com; Version=1; Domain=".foobar.com"; Max-Age=3600
+COOKIE ASK http://frop35.com/ Set-Cookie2: set_by2=x.y.z.frop.com; Version=1; Domain=".com"; Max-Age=3600
+
+## Check results
+CHECK http://w.y.z/
+CHECK http://a.b.c/
+CHECK http://w.y1.z/ Cookie: some_value=value with spaces
+CHECK http://a.b1.c/ Cookie: some_other_value; some_value="quoted value"
+CHECK http://a.b2.c/ Cookie: some_value="quoted value
+CHECK http://w.y3.z/ Cookie: some_value2=foobar
+CHECK http://w.y4.z/
+CHECK http://w.y4.z/Foo Cookie: some_value=value1
+CHECK http://w.y4.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1
+CHECK http://w.y5.z/
+CHECK http://w.y5.z/Foo Cookie: some_value=value1
+CHECK http://w.y5.z/FooBar
+CHECK http://w.y5.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1
+CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3
+CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3
+CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3
+CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2
+CHECK http://secure.y7.z/Foo/bar
+CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3
+CHECK http://secure.y8.z/Foo/bar
+CHECK http://www.acme9.com/ Cookie: some_value=value1
+CHECK http://www.abc9.com/
+CHECK http://frop.acme9.com/ Cookie: some_value=value1
+CHECK http://novell10.com/ Cookie: some_value=value1
+CHECK http://www.novell10.com/ Cookie: some_value=value1
+CHECK http://novell11.com/ Cookie: some_value=value1
+CHECK http://www.novell11.com/ Cookie: some_value=value1
+CHECK http://novell12.com/ Cookie: some_value=value1
+CHECK http://www.novell12.com/
+CHECK http://novell13.com/ Cookie: some_value=value1
+CHECK http://www.novell13.com/
+CHECK http://com/
+CHECK http://sun13.com/
+CHECK http://novell14.co.uk/ Cookie: some_value=value1
+CHECK http://www.novell14.co.uk/
+CHECK http://co.uk/
+CHECK http://sun14.co.uk/
+CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://x.y.z.foobar15.com/
+CHECK http://y.z.foobar15.com/
+CHECK http://z.foobar15.com/
+CHECK http://www.foobar15.com/
+CHECK http://foobar15.com/
+CHECK http://x.y.z.foobar16.com/
+CHECK http://y.z.foobar16.com/
+CHECK http://z.foobar16.com/
+CHECK http://www.foobar16.com/
+CHECK http://foobar16.com/
+## Check results for RFC cookies
+CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/"
+CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/"
+CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2"
+CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such"
+CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"
+CHECK http://w.y23.z/
+CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y24.z/
+CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1
+CHECK http://w.y24.z/FooBar
+CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1
+CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1
+CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/"
+CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3
+CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3
+CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/"
+CHECK http://secure.y26.z/Foo/bar
+CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/"
+CHECK http://secure.y27.z/Foo/bar
+CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com"
+CHECK http://www.abc28.com/
+CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com"
+CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com"
+CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com"
+CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell30.com/
+CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell31.com/
+CHECK http://com/
+CHECK http://sun31.com/
+CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell32.co.uk/
+CHECK http://co.uk/
+CHECK http://sun32.co.uk/
+CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://x.y.z.foobar.com/
+CHECK http://y.z.foobar.com/
+CHECK http://z.foobar.com/
+CHECK http://www.foobar.com/
+CHECK http://foobar.com/
+
+
+SAVE
+## Check result after saving
+CHECK http://w.y.z/
+CHECK http://a.b.c/
+CHECK http://w.y1.z/ Cookie: some_value=value with spaces
+CHECK http://a.b1.c/ Cookie: some_other_value; some_value="quoted value"
+CHECK http://a.b2.c/ Cookie: some_value="quoted value
+CHECK http://w.y3.z/ Cookie: some_value2=foobar
+CHECK http://w.y4.z/
+CHECK http://w.y4.z/Foo Cookie: some_value=value1
+CHECK http://w.y4.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1
+CHECK http://w.y5.z/
+CHECK http://w.y5.z/Foo Cookie: some_value=value1
+CHECK http://w.y5.z/FooBar
+CHECK http://w.y5.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1
+CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3
+CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3
+CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3
+CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2
+CHECK http://secure.y7.z/Foo/bar
+CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3
+CHECK http://secure.y8.z/Foo/bar
+CHECK http://www.acme9.com/ Cookie: some_value=value1
+CHECK http://www.abc9.com/
+CHECK http://frop.acme9.com/ Cookie: some_value=value1
+CHECK http://novell10.com/ Cookie: some_value=value1
+CHECK http://www.novell10.com/ Cookie: some_value=value1
+CHECK http://novell11.com/ Cookie: some_value=value1
+CHECK http://www.novell11.com/ Cookie: some_value=value1
+CHECK http://novell12.com/ Cookie: some_value=value1
+CHECK http://www.novell12.com/
+CHECK http://novell13.com/ Cookie: some_value=value1
+CHECK http://www.novell13.com/
+CHECK http://com/
+CHECK http://sun13.com/
+CHECK http://novell14.co.uk/ Cookie: some_value=value1
+CHECK http://www.novell14.co.uk/
+CHECK http://co.uk/
+CHECK http://sun14.co.uk/
+CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://x.y.z.foobar15.com/
+CHECK http://y.z.foobar15.com/
+CHECK http://z.foobar15.com/
+CHECK http://www.foobar15.com/
+CHECK http://foobar15.com/
+CHECK http://x.y.z.foobar16.com/
+CHECK http://y.z.foobar16.com/
+CHECK http://z.foobar16.com/
+CHECK http://www.foobar16.com/
+CHECK http://foobar16.com/
+## Check result for RFC cookies after saving
+CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/"
+CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/"
+CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2"
+CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such"
+CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"
+CHECK http://w.y23.z/
+CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y24.z/
+CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1
+CHECK http://w.y24.z/FooBar
+CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1
+CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1
+CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/"
+CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3
+CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3
+CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/"
+CHECK http://secure.y26.z/Foo/bar
+CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/"
+CHECK http://secure.y27.z/Foo/bar
+CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com"
+CHECK http://www.abc28.com/
+CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com"
+CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com"
+CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com"
+CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell30.com/
+CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell31.com/
+CHECK http://com/
+CHECK http://sun31.com/
+CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell32.co.uk/
+CHECK http://co.uk/
+CHECK http://sun32.co.uk/
+CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://x.y.z.foobar.com/
+CHECK http://y.z.foobar.com/
+CHECK http://z.foobar.com/
+CHECK http://www.foobar.com/
+CHECK http://foobar.com/
+
+SAVE
+## Check result after saving a second time
+CHECK http://w.y.z/
+CHECK http://a.b.c/
+CHECK http://w.y1.z/ Cookie: some_value=value with spaces
+CHECK http://a.b1.c/ Cookie: some_other_value; some_value="quoted value"
+CHECK http://a.b2.c/ Cookie: some_value="quoted value
+CHECK http://w.y3.z/ Cookie: some_value2=foobar
+CHECK http://w.y4.z/
+CHECK http://w.y4.z/Foo Cookie: some_value=value1
+CHECK http://w.y4.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y4.z/Foo/bar Cookie: some_value=value1
+CHECK http://w.y5.z/
+CHECK http://w.y5.z/Foo Cookie: some_value=value1
+CHECK http://w.y5.z/FooBar
+CHECK http://w.y5.z/Foo/ Cookie: some_value=value1
+CHECK http://w.y5.z/Foo/bar Cookie: some_value=value1
+CHECK http://w.y6.z/Foo/Bar Cookie: some_value2=value2; some_value=value1; some_value3=value3
+CHECK http://w.y7.z/Bar/Foo Cookie: some_value=value2; some_value=value3
+CHECK http://w.y7.z/Foo/Bar Cookie: some_value=value1; some_value=value3
+CHECK https://secure.y7.z/Foo/bar Cookie: some_value2=value2
+CHECK http://secure.y7.z/Foo/bar
+CHECK https://secure.y8.z/Foo/bar Cookie: some_value3=value3
+CHECK http://secure.y8.z/Foo/bar
+CHECK http://www.acme9.com/ Cookie: some_value=value1
+CHECK http://www.abc9.com/
+CHECK http://frop.acme9.com/ Cookie: some_value=value1
+CHECK http://novell10.com/ Cookie: some_value=value1
+CHECK http://www.novell10.com/ Cookie: some_value=value1
+CHECK http://novell11.com/ Cookie: some_value=value1
+CHECK http://www.novell11.com/ Cookie: some_value=value1
+CHECK http://novell12.com/ Cookie: some_value=value1
+CHECK http://www.novell12.com/
+CHECK http://novell13.com/ Cookie: some_value=value1
+CHECK http://www.novell13.com/
+CHECK http://com/
+CHECK http://sun13.com/
+CHECK http://novell14.co.uk/ Cookie: some_value=value1
+CHECK http://www.novell14.co.uk/
+CHECK http://co.uk/
+CHECK http://sun14.co.uk/
+CHECK http://x.y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://y.z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://z.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://www.foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://foobar14.com/ Cookie: set_by=x.y.z.foobar14.com
+CHECK http://x.y.z.foobar15.com/
+CHECK http://y.z.foobar15.com/
+CHECK http://z.foobar15.com/
+CHECK http://www.foobar15.com/
+CHECK http://foobar15.com/
+CHECK http://x.y.z.foobar16.com/
+CHECK http://y.z.foobar16.com/
+CHECK http://z.foobar16.com/
+CHECK http://www.foobar16.com/
+CHECK http://foobar16.com/
+## Check result for rfc cookies after saving a second time
+CHECK http://w.y20.z/ Cookie: $Version=1; some_value="value1"; $Path="/"
+CHECK http://a.b20.c/ Cookie: $Version=1; some_value="value2"; $Path="/"
+CHECK http://w.y21.z/ Cookie: $Version=1; some_value="extra space 2"
+CHECK http://a.b21.c/ Cookie: $Version=1; some_value="quoted value; and such"
+CHECK http://w.y22.z/ Cookie: $Version=1; some_value2=foobar; $Path="/"
+CHECK http://w.y23.z/
+CHECK http://w.y23.z/Foo Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y23.z/Foo/ Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y23.z/Foo/bar Cookie: $Version=1; some_value=value1; $Path="/Foo"
+CHECK http://w.y24.z/
+CHECK http://w.y24.z/Foo Cookie: $Version=1; some_value=value1
+CHECK http://w.y24.z/FooBar
+CHECK http://w.y24.z/Foo/ Cookie: $Version=1; some_value=value1
+CHECK http://w.y24.z/Foo/bar Cookie: $Version=1; some_value=value1
+CHECK http://w.y25.z/Foo/Bar Cookie: $Version=1; some_value2=value2; $Path="/Foo/Bar"; some_value=value1; $Path="/Foo"; some_value3=value3; $Path="/"
+CHECK http://w.y26.z/Bar/Foo Cookie: $Version=1; some_value=value2; some_value=value3
+CHECK http://w.y26.z/Foo/Bar Cookie: $Version=1; some_value=value1; some_value=value3
+CHECK https://secure.y26.z/Foo/bar Cookie: $Version=1; some_value2=value2; $Path="/"
+CHECK http://secure.y26.z/Foo/bar
+CHECK https://secure.y27.z/Foo/bar Cookie: $Version=1; some_value3=value3; $Path="/"
+CHECK http://secure.y27.z/Foo/bar
+CHECK http://www.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com"
+CHECK http://www.abc28.com/
+CHECK http://frop.acme28.com/ Cookie: $Version=1; some_value=value1; $Domain=".acme28.com"
+CHECK http://novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com"
+CHECK http://www.novell29.com/ Cookie: $Version=1; some_value=value1; $Domain=".novell29.com"
+CHECK http://novell30.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell30.com/
+CHECK http://novell31.com/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell31.com/
+CHECK http://com/
+CHECK http://sun31.com/
+CHECK http://novell32.co.uk/ Cookie: $Version=1; some_value=value1
+CHECK http://www.novell32.co.uk/
+CHECK http://co.uk/
+CHECK http://sun32.co.uk/
+CHECK http://x.y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://y.z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://z.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://www.foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://foobar33.com/ Cookie: $Version=1; set_by=x.y.z.foobar.com; $Domain=".foobar33.com"
+CHECK http://x.y.z.foobar.com/
+CHECK http://y.z.foobar.com/
+CHECK http://z.foobar.com/
+CHECK http://www.foobar.com/
+CHECK http://foobar.com/
diff --git a/kioslave/http/kcookiejar/tests/cookie_settings.test b/kioslave/http/kcookiejar/tests/cookie_settings.test
new file mode 100644
index 000000000..7fc1a03a7
--- /dev/null
+++ b/kioslave/http/kcookiejar/tests/cookie_settings.test
@@ -0,0 +1,116 @@
+## Check CookieGlobalAdvice setting
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value1; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value2; Path="/"
+CONFIG CookieGlobalAdvice Reject
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value3; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value4; Path="/"
+CONFIG CookieGlobalAdvice Accept
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value5; Path="/"; expires=%NEXTYEAR%
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value6; Path="/"
+CONFIG CookieGlobalAdvice Ask
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value7; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value8; Path="/"
+CONFIG AcceptSessionCookies true
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+# FIXME: Shouldn't this be considered a session cookie?
+# COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="0"
+# COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%LASTYEAR%
+# FIXME: The 'Discard' attribute makes the cookie a session cookie
+# COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+## Treat all cookies as session cookies
+CONFIG IgnoreExpirationDate true
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check host-based domain policies
+CONFIG IgnoreExpirationDate false
+CONFIG AcceptSessionCookies false
+CONFIG CookieDomainAdvice a.b.c:Reject
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check resetting of domain policies
+CONFIG CookieDomainAdvice
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check domain policies
+CONFIG CookieDomainAdvice .b.c:Reject
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check overriding of domain policies #1
+CONFIG CookieDomainAdvice .b.c:Reject,a.b.c:Accept
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check overriding of domain policies #2
+CONFIG CookieDomainAdvice a.b.c:Reject,.b.c:Accept
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check resetting of domain policies
+CONFIG CookieDomainAdvice
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ASK http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ASK http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ASK http://d.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ASK http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check overriding of domain policies #3
+CONFIG CookieDomainAdvice b.c:Reject,.b.c:Accept
+COOKIE REJECT http://b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE REJECT http://b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE REJECT http://b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+## Check overriding of domain policies #4
+CONFIG CookieDomainAdvice .a.b.c.d:Reject,.b.c.d:Accept,.c.d:Ask
+COOKIE REJECT http://www.a.b.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ACCEPT http://www.b.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE ASK http://www.c.d/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+## Check interaction with session policy
+CONFIG AcceptSessionCookies true
+CONFIG CookieDomainAdvice .b.c:Reject
+COOKIE REJECT http://a.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://a.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ACCEPT http://a.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
+COOKIE REJECT http://d.b.c/ Set-Cookie: some_value=value9; Path="/"; expires=%NEXTYEAR%
+COOKIE REJECT http://d.b.c/ Set-Cookie2: some_value=value10; Version=1; Path="/"; max-age="600"
+COOKIE ACCEPT http://d.b.c/ Set-Cookie: some_value=value11; Path="/"
+COOKIE ACCEPT http://d.b.c/ Set-Cookie2: some_value=value12; Version=1; Path="/"
diff --git a/kioslave/http/kcookiejar/tests/kcookiejartest.cpp b/kioslave/http/kcookiejar/tests/kcookiejartest.cpp
new file mode 100644
index 000000000..f196f1820
--- /dev/null
+++ b/kioslave/http/kcookiejar/tests/kcookiejartest.cpp
@@ -0,0 +1,270 @@
+/*
+ This file is part of KDE
+
+ Copyright (C) 2004 Waldo Bastian (bastian@kde.org)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ version 2 as published by the Free Software Foundation.
+
+ This software 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this library; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <qdatetime.h>
+#include <qstring.h>
+
+#include <kapplication.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+#include <kstandarddirs.h>
+
+#include "../kcookiejar.cpp"
+
+static const char *description = "KCookiejar regression test";
+
+static KCookieJar *jar;
+static QCString *lastYear;
+static QCString *nextYear;
+static KConfig *config = 0;
+
+
+static KCmdLineOptions options[] =
+{
+ { "+testfile", "Regression test to run", 0},
+ KCmdLineLastOption
+};
+
+static void FAIL(const QString &msg)
+{
+ qWarning("%s", msg.local8Bit().data());
+ exit(1);
+}
+
+static void popArg(QCString &command, QCString & line)
+{
+ int i = line.find(' ');
+ if (i != -1)
+ {
+ command = line.left(i);
+ line = line.mid(i+1);
+ }
+ else
+ {
+ command = line;
+ line = 0;
+ }
+}
+
+
+static void popArg(QString &command, QCString & line)
+{
+ int i = line.find(' ');
+ if (i != -1)
+ {
+ command = QString::fromLatin1(line.left(i));
+ line = line.mid(i+1);
+ }
+ else
+ {
+ command = QString::fromLatin1(line);
+ line = 0;
+ }
+}
+
+static void clearConfig()
+{
+ delete config;
+ QString file = locateLocal("config", "kcookiejar-testconfig");
+ QFile::remove(file);
+ config = new KConfig(file);
+ config->setGroup("Cookie Policy");
+ config->writeEntry("RejectCrossDomainCookies", false);
+ config->writeEntry("AcceptSessionCookies", false);
+ config->writeEntry("IgnoreExpirationDate", false);
+ config->writeEntry("CookieGlobalAdvice", "Ask");
+ jar->loadConfig(config, false);
+}
+
+static void clearCookies()
+{
+ jar->eatAllCookies();
+}
+
+static void saveCookies()
+{
+ QString file = locateLocal("config", "kcookiejar-testcookies");
+ QFile::remove(file);
+ jar->saveCookies(file);
+ delete jar;
+ jar = new KCookieJar();
+ clearConfig();
+ jar->loadCookies(file);
+}
+
+static void processCookie(QCString &line)
+{
+ QString policy;
+ popArg(policy, line);
+ KCookieAdvice expectedAdvice = KCookieJar::strToAdvice(policy);
+ if (expectedAdvice == KCookieDunno)
+ FAIL(QString("Unknown accept policy '%1'").arg(policy));
+
+ QString urlStr;
+ popArg(urlStr, line);
+ KURL url(urlStr);
+ if (!url.isValid())
+ FAIL(QString("Invalid URL '%1'").arg(urlStr));
+ if (url.isEmpty())
+ FAIL(QString("Missing URL"));
+
+ line.replace("%LASTYEAR%", *lastYear);
+ line.replace("%NEXTYEAR%", *nextYear);
+
+ KHttpCookieList list = jar->makeCookies(urlStr, line, 0);
+
+ if (list.isEmpty())
+ FAIL(QString("Failed to make cookies from: '%1'").arg(line));
+
+ for(KHttpCookie *cookie = list.first();
+ cookie; cookie = list.next())
+ {
+ KCookieAdvice cookieAdvice = jar->cookieAdvice(cookie);
+ if (cookieAdvice != expectedAdvice)
+ FAIL(urlStr+QString("\n'%2'\nGot advice '%3' expected '%4'").arg(line)
+ .arg(KCookieJar::adviceToStr(cookieAdvice))
+ .arg(KCookieJar::adviceToStr(expectedAdvice)));
+ jar->addCookie(cookie);
+ }
+}
+
+static void processCheck(QCString &line)
+{
+ QString urlStr;
+ popArg(urlStr, line);
+ KURL url(urlStr);
+ if (!url.isValid())
+ FAIL(QString("Invalid URL '%1'").arg(urlStr));
+ if (url.isEmpty())
+ FAIL(QString("Missing URL"));
+
+ QString expectedCookies = QString::fromLatin1(line);
+
+ QString cookies = jar->findCookies(urlStr, false, 0, 0).stripWhiteSpace();
+ if (cookies != expectedCookies)
+ FAIL(urlStr+QString("\nGot '%1' expected '%2'")
+ .arg(cookies, expectedCookies));
+}
+
+static void processClear(QCString &line)
+{
+ if (line == "CONFIG")
+ clearConfig();
+ else if (line == "COOKIES")
+ clearCookies();
+ else
+ FAIL(QString("Unknown command 'CLEAR %1'").arg(line));
+}
+
+static void processConfig(QCString &line)
+{
+ QCString key;
+ popArg(key, line);
+
+ if (key.isEmpty())
+ FAIL(QString("Missing Key"));
+
+ config->setGroup("Cookie Policy");
+ config->writeEntry(key.data(), line.data());
+ jar->loadConfig(config, false);
+}
+
+static void processLine(QCString line)
+{
+ if (line.isEmpty())
+ return;
+
+ if (line[0] == '#')
+ {
+ if (line[1] == '#')
+ qWarning("%s", line.data());
+ return;
+ }
+
+ QCString command;
+ popArg(command, line);
+ if (command.isEmpty())
+ return;
+
+ if (command == "COOKIE")
+ processCookie(line);
+ else if (command == "CHECK")
+ processCheck(line);
+ else if (command == "CLEAR")
+ processClear(line);
+ else if (command == "CONFIG")
+ processConfig(line);
+ else if (command == "SAVE")
+ saveCookies();
+ else
+ FAIL(QString("Unknown command '%1'").arg(command));
+}
+
+static void runRegression(const QString &filename)
+{
+ FILE *file = fopen(filename.local8Bit(), "r");
+ if (!file)
+ FAIL(QString("Can't open '%1'").arg(filename));
+
+ char buf[4096];
+ while (fgets(buf, sizeof(buf), file))
+ {
+ int l = strlen(buf);
+ if (l)
+ {
+ l--;
+ buf[l] = 0;
+ }
+ processLine(buf);
+ }
+ qWarning("%s OK", filename.local8Bit().data());
+}
+
+int main(int argc, char *argv[])
+{
+ QString arg1;
+ QCString arg2;
+ QString result;
+
+ lastYear = new QCString(QString("Fri, 04-May-%1 01:00:00 GMT").arg(QDate::currentDate().year()-1).utf8());
+ nextYear = new QCString(QString(" expires=Fri, 04-May-%1 01:00:00 GMT").arg(QDate::currentDate().year()+1).utf8());
+
+ KAboutData about("kcookietest", "kcookietest", "1.0", description, KAboutData::License_GPL, "(C) 2004 Waldo Bastian");
+ KCmdLineArgs::init( argc, argv, &about);
+
+ KCmdLineArgs::addCmdLineOptions( options );
+
+ KInstance a("kcookietest");
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ if (args->count() != 1)
+ KCmdLineArgs::usage();
+
+ jar = new KCookieJar;
+
+ clearConfig();
+
+ QString file = args->url(0).path();
+ runRegression(file);
+ return 0;
+}
diff --git a/kioslave/http/rfc2518.txt b/kioslave/http/rfc2518.txt
new file mode 100644
index 000000000..34d2e942a
--- /dev/null
+++ b/kioslave/http/rfc2518.txt
@@ -0,0 +1 @@
+http://www.ietf.org/rfc/rfc2518.txt
diff --git a/kioslave/http/rfc2616.txt b/kioslave/http/rfc2616.txt
new file mode 100644
index 000000000..7be662a97
--- /dev/null
+++ b/kioslave/http/rfc2616.txt
@@ -0,0 +1 @@
+http://www.ietf.org/rfc/rfc2616.txt
diff --git a/kioslave/http/rfc2617.txt b/kioslave/http/rfc2617.txt
new file mode 100644
index 000000000..da74cc63a
--- /dev/null
+++ b/kioslave/http/rfc2617.txt
@@ -0,0 +1 @@
+http://www.ietf.org/rfc/rfc2617.txt
diff --git a/kioslave/http/rfc2817.txt b/kioslave/http/rfc2817.txt
new file mode 100644
index 000000000..a29dfc44b
--- /dev/null
+++ b/kioslave/http/rfc2817.txt
@@ -0,0 +1 @@
+http://www.ietf.org/rfc/rfc2817.txt
diff --git a/kioslave/http/rfc2818.txt b/kioslave/http/rfc2818.txt
new file mode 100644
index 000000000..fff91b1a9
--- /dev/null
+++ b/kioslave/http/rfc2818.txt
@@ -0,0 +1 @@
+http://www.ietf.org/rfc/rfc2818.txt
diff --git a/kioslave/http/rfc3229.txt b/kioslave/http/rfc3229.txt
new file mode 100644
index 000000000..54a19b685
--- /dev/null
+++ b/kioslave/http/rfc3229.txt
@@ -0,0 +1 @@
+http://www.ietf.org/rfc/rfc3229.txt
diff --git a/kioslave/http/rfc3253.txt b/kioslave/http/rfc3253.txt
new file mode 100644
index 000000000..9968eea02
--- /dev/null
+++ b/kioslave/http/rfc3253.txt
@@ -0,0 +1 @@
+http://www.ietf.org/rfc/rfc3253.txt
diff --git a/kioslave/http/shoutcast-icecast.txt b/kioslave/http/shoutcast-icecast.txt
new file mode 100644
index 000000000..f7bdcf1e7
--- /dev/null
+++ b/kioslave/http/shoutcast-icecast.txt
@@ -0,0 +1,605 @@
+
+Audio and Apache HTTPD
+ApacheCon 2001
+Santa Clara, US
+
+April 6th, 2001
+
+Sander van Zoest <sander@vanZoest.com>
+Covalent Technologies, Inc.
+<http://www.covalent.net/>
+
+Latest version can be found at:
+ <http://www.vanZoest.com/sander/apachecon/2001/>
+
+Introduction:
+
+About this paper:
+
+Contents:
+
+ 1. Why serve Audio on the Net?
+
+ This is almost like asking, why are you reading this? it might be
+ because of the excitement caused by the new media that has recently
+ crazed upon the internet. People are looking to bring their lifes onto
+ the net, one of the things that brings that closer to a reality is the
+ ability to hear live broadcasts of the worlds news, favorite sport;
+ hear music and to teleconference with others. Sometimes it is simply
+ to enhance the mood to a web site or to provide audio feedback of
+ actions performed by the visitor of the web site.
+
+ 2. What makes delivering audio so different?
+
+ The biggest reason to what makes audio different then traditional
+ web media such as graphics, text and HTML is the fact that timing
+ is very important. This caused by the significant increase in size
+ of the media and the different quality levels that exist.
+
+ There really are two kinds of goals behind audio streams.
+ In one case there is a need for immediate response the moment
+ playback is requested and this can sacrifice quality. While
+ in the other case quality and a non-interrupted stream are much
+ more important.
+
+ This sort of timing is not really required of any other media,
+ with the exception of video. In the case of HTML and images the
+ files sizes are usually a lot smaller which causes the objects
+ to load much quicker and usually are not very useful without
+ having the entire file. In audio the middle of a stream can have
+ useful information and still set a particular mood.
+
+ 3. Different ways of delivery Audio on the Net.
+ Embedding audio in your Web Page
+
+ This used to be a lot more common in the past. Just like embedding
+ an image in a web page, it is possible to add a sound clip or score
+ to the web page.
+
+ The linked in audio files are usually short and of low quality to
+ avoid a long delay for downloading the rest of the web page and the
+ audio format needs to be supported by the browser natively or with
+ a browser plug-in to avoid annoying the visitor.
+
+ This can be accomplished using the HTML 4.0 [HTML4] object element which
+ works similar to how to specify an applet with the object element.
+ In the past this could also be accomplished using the embed and bgsound
+ browser specific additions to HTML.
+
+ example:
+ <object type="audio/x-midi" data="../media/sound.mid" width="200" height="26">
+ <param name="src" value="../media/sound.mid">
+ <param name="autostart" value="true">
+ <param name="controls" value="ControlPanel">
+ </object>
+
+ Each param element is specific to each browser. Please check with each
+ browser for specific information in regards to what param elements are
+ available.
+
+ In this method of delivering audio the audio file is served up via the
+ web server. When using an Apache HTTPD server make sure that the appropriate
+ mime type is configured for the audio file and that the audio file is
+ named and referenced by the appropriate extension.
+
+ Although the current HTML 4.01 [HTML4] says to use the object element
+ many browsers out on the market today still look for the embed element.
+ Below find a little snipbit that will work work in many browsers.
+
+ <object type="audio/x-midi" data="../media/sound.mid" width="200" height="26">
+ <param name="src" value="../media/sound.mid">
+ <param name="autostart" value="true">
+ <param name="controls" value="ControlPanel">
+
+ <embed type="audio/x-midi" src="../media/sound.mid"
+ width="200" height="26" autoplay="true" controls="ControlPanel">
+ <noembed>Your browser does not support embedded WAV files.</noembed>
+ </object>
+
+ With the increasing installation base of the Flash browser plug-in by
+ Macromedia most developers that are looking to provide this kind of
+ functionality to a web page are creating flash elements that have their
+ own way of adding audio that is discussed in Flash specific documents.
+
+ Downloading via HTTP
+
+ Using this method the visitor to the website will have to download the
+ entire audio file and save it to the hard drive before it can be
+ listened to. (1) This is very popular with people that want to listen
+ to high quality streams of audio and have a below ISDN connection to
+ the internet. In some cases where the demand for a stream is high or
+ the internet is congested downloading the content even for high bandwidth
+ users can be affective and useful.
+
+ One of the advantages of downloading audio to the local computer hard
+ drive is that it can be played back (once downloaded) any time as long
+ as the audio file is accessable from the computer.
+
+ There are a lot of sites on the internet that provide this functionality
+ for music and other audio files. It is also one of the easiest ways to
+ delivery high quality audio to visitors.
+
+ (1) Microsoft Windows Media Player in conjunction with the Microsoft
+ Internet Explorer Browser will automaticly start playing the
+ audio stream after a sufficient amount of the file has been
+ downloaded. This can be accomplished because of the tight
+ integration of the Browser and Media Player. With most audio players
+ you can listen to a file being downloaded, but you will have to
+ envoke the action manually.
+
+ . On-Demand streaming via HTTP
+
+ The real difference between downloading and on-demand streaming is
+ that in on-demand streaming the audio starts playing before the entire
+ audio file has been downloaded. This is accomplished by a hand of off
+ the browser to the audio player via an intermediate file format that
+ has been configured by the browser to be handled by the audio player.
+
+ Look in a further section entitled "Linking to Audio via Apache HTTPD"
+ below for more information about the different intermediate file formats.
+
+ This type of streaming is very popular among the open source crowd and
+ is the most widely implemented using the MP3 file format. Apache,
+ Shoutcast [SHOUTCAST] and Icecast [ICECAST] are the most common
+ software components used to provide on-demand streaming via HTTP. Both
+ Icecast and Shoutcast are not fully HTTP compliant, but Icecast is
+ becoming closer. For more information about the Shoutcast and Icecast
+ differences see the section below.
+
+ Sites like Live365.com and MP3.com are huge sites that rely on this
+ method of delivery of audio.
+
+ . On-Demand Streaming via RTSP/RTP
+
+ RTSP/RTP is a new set of streaming protocols that is getting more
+ backing and becoming more popular by the second. The specification
+ was developed by the Internet Engineering Task Force Working Groups
+ AVT [IETFAVT] and MMUSIC [IETFMMUSIC]. RTP the Realtime Transfer
+ Protocol has been around longer then RTSP and originally came out
+ of the work towards a better teleconferencing, mbone, type system.
+ RTSP is the Real-Time Streaming Protocol that is used as a control
+ protocol and acts similarily to HTTP except that it maintains state
+ and is bi-directional.
+
+ Currently the latest Real Networks Streaming Servers support RTSP
+ and RTP and Real Networks own proprietary transfer protocol RDT.
+ Apple's Darwin Streaming server is also RTSP/RTP compliant.
+
+ The RTSP/RTP protocol suite is very powerful and flexable in regards
+ to your streaming needs. It has the ability to suport "server-push"
+ style stream redirects and has the ability to throttle streams to
+ ensure the stream can sustain the limited bandwidth over the network.
+
+ For On-Demand streams the RTP protocol would usually stream over
+ TCP and have a second TCP connection open for RTSP. Because of the
+ rich features provided by the protocol suite, it is not very well
+ suited to allow people to download the stream and therefore the
+ download via HTTP method might still be preferred by some.
+
+ . Live Broadcast Streaming via RTSP/RTP
+
+ In the case of a live broadcast streaming RTSP/RTP shines. RTP allowing
+ for UDP datagrams to be transmitted to clients allows for fast immediate
+ delivery of content with the sacrifice of reliability. The RTP stream
+ can be send over IP Multicast to minimize bandwidth on the network.
+
+ Many Content Delivery Networks (CDNs) are starting to provide support for
+ RTSP/RTP proxies that should provide a better quality streaming environment
+ on the internet.
+
+ Much work is also being done in the RTP space to provide transfers over
+ telecommunication networks such as cellular phones. Although not directly
+ related, per se, it does provide a positive feeling knowing that all the
+ audio related transfer groups seem to be working towards a common standard
+ such as RTP.
+
+ . On-Demand or Live Broadcast streaming via MMS.
+
+ This is the Microsoft Windows Media Technologies Streaming protocol. It
+ is only supported by Microsoft Windows Media Player and currently only
+ works on Microsoft Windows.
+
+ 5. Configuring Mime Types
+
+ One of the most hardest things in serving audio has been the wide variety
+ of audio codecs and mime types available. The battle of mime types on the
+ audio player side of things isn't over, but it seems to be a little more
+ controlled.
+
+ On the server side of things provide the appropriate mime type for the
+ particular audio streams and/or files that are being served to the audio
+ players. Although some clients and operating systems handle files fully
+ based on the file extension. The mime type [RFC2045] is more specific
+ and more defined.
+
+ The registered mime types are maintained by IANA [IANA]. On their site
+ they have a list of all the registered mime types and their name space.
+
+ If you are planning on using a mime type that isn't registered by IANA
+ then signal this in the name space by adding a "x-" before the subtype.
+ Because this was not done very often in the audio space, there was a
+ lot of confusion to what the real mime type should be.
+
+ For example the MPEG 1.0 Layer 3 Audio (MP3) [ORAMP3BOOK] mime type
+ was not specified for the longest time. Because of this the mime type
+ was audio/x-mpeg. Although none of the audio players understood
+ audio/x-mpeg, but understood audio/mpeg it was not a technically
+ correct mime type. Later audio players recognized this and started
+ using the audio/x-mpeg mime type. Which in the end caused a lot
+ of hassles with clients needing to be configured differently depending
+ on the website and client that was used. Last november we thanked
+ Martin Nilsson of the ID3 tagging project for registering audo/mpeg
+ with IANA. [RFC3003]
+
+ Correct configuration of Mime Types is very important. Apache HTTPD
+ ships with a fairly up to date copy of the mime.types file, so most
+ of the default ones (including audio/mpeg) are there.
+
+ But in case you run into some that are not defined use the mod_mime
+ directives such as AddType to fix this.
+
+ Examples:
+ AddType audio/x-mpegurl .m3u
+ AddType audio/x-scpls .pls
+ AddType application/x-ogg .ogg
+
+
+ 6. Common Audio File Formats
+
+ There are many audio formats and metadata formats that exist. Many of
+ them do not have registered mime types and are hardly documented.
+ This section is an attempt at providing the most accurate mime type
+ information for each format with a rough description of what the files
+ are used for.
+
+ . Real Audio
+
+ Real Networks Proprietary audio format and meta formats. This is one
+ of the more common streaming audio formats today. It comes in several
+ sub flavors such as Real 5.0, Real G2 and Real 8.0 etc. The file size
+ varies depending on the bitrates and what combination of bitrates are
+ contained within the single file.
+ The following mime types are used
+ audio/x-pn-realaudio .ra, .ram, .rm
+ audio/x-pn-realaudio-plugin .rpm
+ application/x-pn-realmedia
+
+ . MPEG 1.0 Layer 3 Audio (MP3)
+
+ This is currently one of the most popular downloaded audio formats
+ that was originally developed by the Motion Pictures Experts Group
+ and has patents by the Fraunhofer IIS Institute and Thompson
+ Multimedia. [ORAMP3BOOK] The file is a lossy compression that at
+ a bitrate of 128kbps reduces the file size to roughly a MB/minute.
+ The mime type is audio/mpeg with the extension of .mp3 [RFC3003]
+
+ . Windows Media Audio
+
+ Originally known as MS Audio was developed by Microsoft as the MP3
+ killer. Still relatively a new format but heavily marketed by
+ Microsoft and becoming more popular by the minute. It is a successor
+ to the Microsoft Audio Streaming Format (ASF).
+
+ . WAV
+
+ Windows Audio Format is a pretty semi-complicated encapsulating
+ format that in the most common case is PCM with a WAV header up front.
+ It has the mime type audio/x-wav with the extension .wav.
+
+ . Vorbis
+
+ Ogg Vorbis [VORBIS] is still a relatively new format brought to
+ life by CD Paranoia author Christopher Montgomery; known to the
+ world as Monty. It is an open source audio format free of patents
+ and gotchas. It is a codec/file format that is roughly as good as
+ the MP3 format, if not much better. The mime type for Ogg Vorbis is
+ application/x-ogg with the extension of .ogg.
+
+ . MIDI
+
+ The MIDI standard and file format [MIDISPEC] have been used by
+ Musicians for a long time. It is a great format to add music to
+ a website without the long download times and needing special players
+ or plug-ins. The mime type is audio/x-midi and the extension is .mid
+
+ . Shockwave Flash (ADPCM/MP3) [FLASH4AUDIO]
+
+ Macromedia Flash [FLASH4AUDIO] uses its own internal audio format
+ that is often used on Flash websites. It is based on Adaptive
+ Differential Pulse Code Modulation (ADPCM) and the MP3 file format.
+ Because it is usually used from within Flash it usually isn't served
+ up seperatedly but it's extension is .swf
+
+ There are many many many more audio codecs and file formats that exist.
+ I have listed a few that won't be discussed but should be kept in mind.
+ Formats such as PCM/Raw Audio (audio/basic), MOD, MIDI (audio/x-midi),
+ QDesign (used by Quicktime), Beatnik, Sun's AU, Apple/SGI's AIFF, AAC
+ by the MPEG Group, Liquid Audio and AT&T's a2b (AAC derivatives),
+ Dolby AC-3, Yamaha's TwinVQ (originally by Nippon Telephone and Telegraph)
+ and MPEG-4 audio.
+
+ 7. Linking to Audio via Apache HTTPD
+
+ There are many different ways to link to audio from the Apache HTTPD
+ web server. It seems as if every codec has their own metafile format.
+ The metafile format is provided to allow the browser to hand off the
+ job of requesting the audio file to the audio player, because it is
+ more familiar with the file format and how to handle streaming or how
+ to actually connect to the audio server then the web browser is.
+
+ This section will discuss the more common methods to provide streaming
+ links to provide that gateway from the web to the audio world.
+
+ Probably the one that is the most recognized file is the RAM file.
+
+ . RAM
+
+ Real Audio Metafile. It is a pretty straight forward way that Real
+ Networks allowed their Real Player to take more control over their
+ proprietary audio streams. The file format is simply a URL on each
+ line that will be streamed in order by the client. The mime type
+ is the same as other RealAudio files audio/x-pn-realaudio where
+ the pn stands for Progressive Networks the old name of the company.
+
+ . M3U
+
+ This next one is the MPEG Layer 3 URL Metafile that has been around
+ for a very long time as a playlist format for MP3 players. It supported
+ URLs pretty early on by some players and got the mime type
+ audio/x-mpegurl and is now used by Icecast and many destination sites
+ such as MP3.com. The format is exactly the same as that of the RAM
+ file, just a list of urls that are separated by line feeds.
+
+ . PLS
+
+ This is the playlist files used by Nullsoft's Winamp MP3 Player. Later
+ on it got more widely used by Nullsoft's Shoutcast and has the mime
+ type of audio/x-scpls with the extension .pls. Before shoutcast the
+ mimetype was simply audio/x-pls. As you can see in the example below
+ it looks very much like a standard windows INI file format.
+
+ Example:
+ [playlist]
+ numberofentries=2
+ File1=<uri>
+ Title1=<title>
+ Length1=<length or -1>
+ File2=<uri>
+ Title2=<title>
+ Length2=<length or -1>
+
+ . SDP
+
+ This is the Session Description Protocol [RFC2327] which is heavily
+ used within RTSP and is a standard way of describing how to subscribe
+ to a particular RTP stream. The mime type is application/sdp with the
+ extension .sdp .
+
+ Sometimes you might see RTSL (Real-Time Streaming Language) floating
+ around. This was an old Real Networks format that has been succeeded
+ by SDP. It's mimetype was application/x-rtsl with the extension of .rtsl
+
+ . ASX
+
+ Is a Windows Media Metafile format [MSASX] that is based on early XML
+ standards. It can be found with many extensions such as .wvx, .wax
+ and .asx. I am not aware of a mime type for this format.
+
+ . SMIL
+
+ Is the Synchronized Multimedia Integration Language [SMIL20] that
+ is now a W3C Recommendation [W3SYMM]. It was originally developed
+ by Real Networks to provide an HTML-like language to their Real Player
+ that was more focused on multimedia. The mime type is application/smil
+ with the extensions of either .smil or .smi
+
+ . MHEG
+
+ Is a hypertext language developed by the ISO group. [MHEG1] [MHEG5]
+ and [MHEG5COR]. It has been adopted by the Digital Audio Visual
+ Council [DAVIC]. It is more used for teleconferencing, broadcasting
+ and television, but close enough related that it receives a mention
+ here. The mime type is application/x-mheg with the extension of
+ .mheg
+
+ 8. Configuring Apache HTTPD specificly to serve large Audio Files
+
+ Some of the most common things that you will need to adjust to be
+ able to serve many large audio files via the Apache HTTPD Server.
+ Because of the difference in size between HTML files and Audio files,
+ the MaxClients will need to be adjusted appropriatedly depending on
+ the amount of time listeners end up tieing up a process. If you are
+ serving high quality MP3 files at 128kbps for example you should
+ expect more then 5 minute download times for most people.
+
+ This will significantly impact your webserver since this means that
+ that process is occupied for the entire time. Because of this you
+ will also want to in crease the TimeOut Directive to a higher
+ number. This is to ensure that connections do not get disconnected
+ half way through a transfer and having that person hit "reload"
+ and connect again.
+
+ Because of the amount of time the downloads tie up the processes
+ of the server, the smallest footprint of the server in memory would
+ be recommended because that would mean you could run more processes
+ on the machine.
+
+ After that normal performance tweaks such as max file descriptor
+ changes and longer tcp listen queues apply.
+
+ 9. Icecast/Shoutcast Protocol.
+
+ Both protocols are very tightly based on HTTP/1.0. The main difference
+ is a group of new headers such as the icy headers by Shoutcast and the
+ new x-audiocast headers provided by Icecast.
+
+ A typical shoutcast request from the client.
+
+ GET / HTTP/1.0
+
+ ICY 200 OK
+ icy-notice1:<BR>This stream requires <a href="http://www.winamp.com/">
+ Winamp</a><BR>
+ icy-notice2:SHOUTcast Distributed Network Audio Server/posix v1.0b<BR>
+ icy-name: Great Songs
+ icy-genre: Jazz
+ icy-url: http://shout.serv.dom/
+ icy-pub: 1
+ icy-br: 24
+
+ <data><songtitle><data>
+
+ The icy headers display the song title and other formation including if
+ this stream is public and what the bitrate is.
+
+ A typical icecast request from the client.
+
+ GET / HTTP/1.0
+ Host: icecast.serv.dom
+ x-audiocast-udpport: 6000
+ Icy-MetaData: 0
+ Accept: */*
+
+ HTTP/1.0 200 OK
+ Server: Icecast/VERSION
+ Content-Type: audio/mpeg
+ x-audiocast-name: Great Songs
+ x-audiocast-genre: Jazz
+ x-audiocast-url: http://icecast.serv.dom/
+ x-audiocast-streamid:
+ x-audiocast-public: 0
+ x-audiocast-bitrate: 24
+ x-audiocast-description: served by Icecast
+
+ <data>
+
+ NOTE: I am mixing the headers of the controlling client with those form
+ a listening client. This might be better explained at a latter
+ date.
+
+ The CPAN Perl Package Apache::MP3 by Lincoln Stein implements a little of
+ each which works because MP3 players tend to support both.
+
+ One of the big differences in implementations between the listening clients
+ is that Icecast uses an out of band UDP channel to update metadata
+ while the Shoutcast server gets it meta data from the client embedded within
+ the MP3 stream. The general meta data for the stream is set up via the
+ icy and x-audiocast HTTP headers.
+
+ Although the MP3 standard documents were written for interrupted communication
+ it is not very specific on that. So although it doesn't state that there is
+ anything wrong with embedding garbage between MPEG frames the players that
+ do not understand it might make a noisy bleep and chirps because of it.
+
+References and Further Reading:
+
+[DAVIC]
+ Digital Audio Visual Council
+ <http://www.davic.org/>
+
+[FLASH4AUDIO]
+ L. J. Lotus, "Flash 4: Audio Options", ZD, Inc. 2000.
+ <http://www.zdnet.com/devhead/stories/articles/0,4413,2580376,00.html>
+
+[HTML4]
+ D. Ragget, A. Le Hors, I. Jacobs, "HTML 4.01 Specification", W3C
+ Recommendation, December, 1999.
+ <http://www.w3.org/TR/html401/>
+
+[IANA]
+ Internet Assigned Numbers Authority.
+ <http:/www.iana.org/>
+
+[ICECAST]
+ Icecast Open Source Streaming Audio System.
+ <http://www.icecast.org/>
+
+[IETFAVT]
+ Audio/Video Transport WG, Internet Engineering Task Force.
+ <http://www.ietf.org/html.charters/avt-charter.html>
+
+[IETFMMUSIC]
+ Multiparty Multimedia Session Control WG, Internet Engineering Task
+ Force. <http://www.ietf.org/html.charters/mmusic-charter.html>
+
+[IETFSIP]
+ Session Initiation Protocol WG, Internet Engineering Task Force.
+ <http://www.ietf.org/html.charters/sip-charter.html>
+
+[IPMULTICAST]
+ Transmit information to a group of recipients via a single transmission
+ by the source, in contrast to unicast.
+ IP Multicast Initiative
+ <http://www.ipmulticast.com/>
+
+[MIDISPEC]
+ The International MIDI Association,"MIDI File Format Spec 1.1",
+ <http://www.vanZoest.com/sander/apachecon/2001/midispec.html>
+
+[MHEG1]
+ ISO/IEC, "Information Technology - Coding of Multimedia and Hypermedia
+ Information - Part 1: MHEG Object Representation, Base Notation (ASN.1)";
+ Draft International Standard ISO 13522-1;1997;
+ <http://www.ansi.org/>
+ <http://www.iso.ch/cate/d22153.html>
+
+[MHEG5]
+ ISO/IEC, "Information Technology - Coding of Multimedia and Hypermedia
+ Information - Part 5: Support for Base-Level Interactive Applications";
+ Draft International Standard ISO 13522-5:1997;
+ <http://www.ansi.org/>
+ <http://www.iso.ch/cate/d26876.html>
+
+[MHEG5COR]
+ Information Technology - Coding of Multimedia and Hypermedia Information
+ - Part 5: Support for base-level interactive applications -
+ - Technical Corrigendum 1; ISO/IEC 13552-5:1997/Cor.1:1999(E)
+ <http://www.ansi.org/>
+ <http://www.iso.ch/cate/d31582.html>
+
+[MSASX]
+ Microsoft Corp. "All About Windows Media Metafiles". October 2000.
+ <http://msdn.microsoft.com/workshop/imedia/windowsmedia/
+ crcontent/asx.asp>
+
+[ORAMP3]
+ S. Hacker; MP3: The Definitive Guide; O'Reilly and Associates, Inc.
+ March, 2000.
+ <http://www.oreilly.com/catalog/mp3/>
+[RFC2045]
+ N. Freed and N. Borenstein, "Multipurpose Internet Mail
+ Extensions (MIME) Part One: Format of Internet Message Bodies",
+ RFC 2045, November 1996. <http://www.ietf.org/rfc/2045.txt>
+
+[RFC2327]
+ M. Handley and V. Jacobson, "SDP: Session Description Protocol",
+ RFC 2327, April 1998. <http://www.ietf.org/rfc/rfc2327.txt>
+
+[RFC3003]
+ M. Nilsson, "The audio/mpeg Media Type", RFC 3003, November 2000.
+ <http://www.ietf.org/rfc/rfc3003.txt>
+
+[SHOUTCAST]
+ Nullsoft Shoutcast MP3 Streaming Technology.
+ <http://www.shoutcast.com/>
+
+[SMIL20]
+ L. Rutledge, J. van Ossenbruggen, L. Hardman, D. Bulterman,
+ "Anticipating SMIL 2.0: The Developing Cooperative Infrastructure
+ for Multimedia on the Web"; 8th International WWW Conference,
+ Proc. May, 1999.
+ <http://www8.org/w8-papers/3c-hypermedia-video/anticipating/
+ anticipating.html>
+
+[W39CIR]
+ V. Krishnan and S. G. Chang, "Customized Internet Radio"; 9th
+ International WWW Conference Proc. May 2000.
+ <http://www9.org/w9cdrom/353/353.html>
+
+[VORBIS]
+ Ogg Vorbis - Open Source Audio Codec
+ <http://www.xiph.org/ogg/vorbis/>
+
+[W3SYMM]
+ W3C Synchronized Multimedia Activity (SYMM Working Group);
+ <http://www.w3.org/AudioVideo/>
diff --git a/kioslave/http/webdav.protocol b/kioslave/http/webdav.protocol
new file mode 100644
index 000000000..f4f4df462
--- /dev/null
+++ b/kioslave/http/webdav.protocol
@@ -0,0 +1,18 @@
+[Protocol]
+exec=kio_http
+protocol=webdav
+input=none
+output=filesystem
+listing=Name,Type,Size,Date,AccessDate,Access
+reading=true
+writing=true
+makedir=true
+deleting=true
+moving=true
+deleteRecursive=true
+defaultMimetype=application/octet-stream
+determineMimetypeFromExtension=false
+Icon=www
+maxInstances=3
+DocPath=kioslave/webdav.html
+Class=:internet
diff --git a/kioslave/http/webdavs.protocol b/kioslave/http/webdavs.protocol
new file mode 100644
index 000000000..c8b7cba3f
--- /dev/null
+++ b/kioslave/http/webdavs.protocol
@@ -0,0 +1,18 @@
+[Protocol]
+exec=kio_http
+protocol=webdavs
+input=none
+output=filesystem
+listing=Name,Type,Size,Date,AccessDate,Access
+reading=true
+writing=true
+makedir=true
+deleting=true
+moving=true
+deleteRecursive=true
+defaultMimetype=application/octet-stream
+determineMimetypeFromExtension=false
+Icon=www
+config=webdav
+DocPath=kioslave/webdavs.html
+Class=:internet
diff --git a/kioslave/metainfo/Makefile.am b/kioslave/metainfo/Makefile.am
new file mode 100644
index 000000000..6807019f4
--- /dev/null
+++ b/kioslave/metainfo/Makefile.am
@@ -0,0 +1,24 @@
+## $Id$
+## Makefile.am of kdebase/kioslave/metainfo
+
+INCLUDES = $(all_includes)
+AM_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+METASOURCES = AUTO
+
+kde_module_LTLIBRARIES = kio_metainfo.la
+
+kio_metainfo_la_SOURCES = metainfo.cpp
+kio_metainfo_la_LIBADD = $(LIB_KIO)
+kio_metainfo_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+
+noinst_HEADERS = metainfo.h
+
+kdelnk_DATA = metainfo.protocol
+kdelnkdir = $(kde_servicesdir)
+
+#servicetypes_DATA = thumbcreator.desktop
+#servicetypesdir = $(kde_servicetypesdir)
+
+#services_DATA = imagethumbnail.desktop textthumbnail.desktop
+# htmlthumbnail.desktop gsthumbnail.desktop
+#servicesdir = $(kde_servicesdir)
diff --git a/kioslave/metainfo/metainfo.cpp b/kioslave/metainfo/metainfo.cpp
new file mode 100644
index 000000000..0e4814b33
--- /dev/null
+++ b/kioslave/metainfo/metainfo.cpp
@@ -0,0 +1,103 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2002 Rolf Magnus <ramagnus@kde.org>
+
+ 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 version 2.0
+
+ 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 <kdatastream.h> // Do not remove, needed for correct bool serialization
+#include <kurl.h>
+#include <kapplication.h>
+#include <kmimetype.h>
+#include <kdebug.h>
+#include <kfilemetainfo.h>
+#include <klocale.h>
+#include <stdlib.h>
+
+#include "metainfo.h"
+
+// Recognized metadata entries:
+// mimeType - the mime type of the file, so we need not extra determine it
+// what - what to load
+
+using namespace KIO;
+
+extern "C"
+{
+ KDE_EXPORT int kdemain(int argc, char **argv);
+}
+
+int kdemain(int argc, char **argv)
+{
+ KApplication app(argc, argv, "kio_metainfo", false, true);
+
+ if (argc != 4)
+ {
+ kdError() << "Usage: kio_metainfo protocol domain-socket1 domain-socket2" << endl;
+ exit(-1);
+ }
+
+ MetaInfoProtocol slave(argv[2], argv[3]);
+ slave.dispatchLoop();
+
+ return 0;
+}
+
+MetaInfoProtocol::MetaInfoProtocol(const QCString &pool, const QCString &app)
+ : SlaveBase("metainfo", pool, app)
+{
+}
+
+MetaInfoProtocol::~MetaInfoProtocol()
+{
+}
+
+void MetaInfoProtocol::get(const KURL &url)
+{
+ QString mimeType = metaData("mimeType");
+ KFileMetaInfo info(url.path(), mimeType);
+
+ QByteArray arr;
+ QDataStream stream(arr, IO_WriteOnly);
+
+ stream << info;
+
+ data(arr);
+ finished();
+}
+
+void MetaInfoProtocol::put(const KURL& url, int, bool, bool)
+{
+ QString mimeType = metaData("mimeType");
+ KFileMetaInfo info;
+
+ QByteArray arr;
+ readData(arr);
+ QDataStream stream(arr, IO_ReadOnly);
+
+ stream >> info;
+
+ if (info.isValid())
+ {
+ info.applyChanges();
+ }
+ else
+ {
+ error(ERR_NO_CONTENT, i18n("No metainfo for %1").arg(url.path()));
+ return;
+ }
+ finished();
+}
diff --git a/kioslave/metainfo/metainfo.h b/kioslave/metainfo/metainfo.h
new file mode 100644
index 000000000..de2a6b055
--- /dev/null
+++ b/kioslave/metainfo/metainfo.h
@@ -0,0 +1,38 @@
+/* This file is part of the KDE libraries
+ Copyright (C) 2002 Rolf Magnus <ramagnus@kde.org>
+
+ 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 version 2.0
+
+ 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$
+
+#ifndef _METAINFO_H_
+#define _METAINFO_H_
+
+#include <kio/slavebase.h>
+
+class MetaInfoProtocol : public KIO::SlaveBase
+{
+public:
+ MetaInfoProtocol(const QCString &pool, const QCString &app);
+ virtual ~MetaInfoProtocol();
+
+ virtual void get(const KURL &url);
+ virtual void put(const KURL& url, int permissions,
+ bool overwrite, bool resume);
+
+};
+
+#endif
diff --git a/kioslave/metainfo/metainfo.protocol b/kioslave/metainfo/metainfo.protocol
new file mode 100644
index 000000000..f1fa9adac
--- /dev/null
+++ b/kioslave/metainfo/metainfo.protocol
@@ -0,0 +1,9 @@
+[Protocol]
+exec=kio_metainfo
+protocol=metainfo
+input=stream
+output=stream
+reading=true
+writing=true
+source=false
+Icon=help