diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 4aed2c8219774f5d797760606b8489a92ddc5163 (patch) | |
tree | 3f8c130f7d269626bf6a9447407ef6c35954426a /kioslave | |
download | tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.tar.gz tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kioslave')
400 files changed, 62404 insertions, 0 deletions
diff --git a/kioslave/DEBUG.howto b/kioslave/DEBUG.howto new file mode 100644 index 000000000..5275a26eb --- /dev/null +++ b/kioslave/DEBUG.howto @@ -0,0 +1,89 @@ +This document describes how you can debug an io-slave with gdb. + +How does an io-slave get started? +================================= + +Your application request 'klauncher' via DCOP for a slave. If 'klauncher' does +not have an idle slave ready, it will ask kdeinit to start a new one. +kdeinit forks and dlopens the library that contains the io-slave. +Then it calls kdemain() or, if that is not present, main() in the library. + + +Attaching gdb to a io-slave +=========================== + +Due to the above sequence it is rather hard to get an io-slave in your +debugger. But wait there is hope. You can start klauncher in such a way +that slaves for a certain protocol are started in debug mode. + +E.g. to start all 'http' slaves in debug mode, you type: + + KDE_SLAVE_DEBUG_WAIT=http kdeinit + +This will restart 'kdeinit' and 'klauncher'. + +When your application now requests a http slave, the slave will be started +by kdeinit, but before it calls kdemain() (cq. main()) it will suspend the +slave by sending it a SIGSTOP signal. + +In the terminal from which you started kdeinit you will get the following +message: + +kdeinit: Suspending process +kdeinit: 'gdb kdeinit 16779' to debug +kdeinit: 'kill -SIGCONT 16779' to continue + +You can now debug your slave by typing (or pasting) 'gdb kdeinit 16779' in +a terminal. If you don't want to debug a slave you can let it continue by +sending it a SIGCONT by typing 'kill -SIGCONT 16779'. + +Be aware that slaves will not be killed while they are suspended. + +Once you have started gdb, you can set e.g. breakpoints and then resume the +slave by typing 'continue'. The debugger will return immediate with a message +that a SIGSTOP has been received so you will have to type 'continue' a second +time. + + +Debugging io-slaves with valgrind +================================= + +KLauncher can be told to run certain io-slaves through valgrind. The following +command can be used to let klauncher run all https io-slaves via valgrind: + + KDE_SLAVE_VALGRIND=https kdeinit + +The valgrind output will appear as the stderr output of the kdeinit process. +The $VALGRIND_OPTS environment variable can be used to pass options to valgrind. +If you want to use a different skin: + + KDE_SLAVE_VALGRIND_SKIN=calltree ( for example ) + + +How to get debug output +======================= + +It is useful to redirect the debug output of your particular slave to a file +instead of stderr. E.g. I myself use the following lines in +$KDEDIR/share/config/kdebugrc. + + [7113] + InfoOutput=0 + InfoFilename=/tmp/http + [7103] + InfoOutput=0 + InfoFilename=/tmp/http + +This redirects all debug info for areas 7103 and 7113 (as used by kio_http) +to the file /tmp/http. + +To get debug information from the SMB slave you can add the following to +kioslaverc: + +[SMB] +DebugLevel=100 + +This will print additional debug info to the stderr of your kdeinit process, +which typically ends up in ~/.X.err or ~/.xsession-errors + +Happy debugging. diff --git a/kioslave/DESIGN b/kioslave/DESIGN new file mode 100644 index 000000000..92a03b212 --- /dev/null +++ b/kioslave/DESIGN @@ -0,0 +1,43 @@ +What is a kioslave you ask yourself? + +A kioslave is a program designed to be intimately familiar with a certian +protocol, so that a standardized interface can be used to get at data from +any number of places. A few examples are the http and ftp kioslaves, +which using nearly identical methods will retrieve data from an http or +ftp server respectively. + +Well, that's nice. How do they work? + +To understand it, you'll need two ice cubes, a pair of handcuffs, and a +ferret. Some Crisco (or other shortening) is optional. Well, that aside, +this document focuses on the business end of the whole kio library. The +ioslave. See the documentation of the SlaveBase class for the methods +you need to reimplement, and see +http://developer.kde.org/documentation/design/kde/ioslaves/ for more docu +online. + +That's nice, but how can I use it? + +Any time you'd like to use non blocking IO over a high level protocol +(such as HTTP or FTP) a kioslave is for you. + +That's nice, but how do I use it? + +Basically, you create "jobs" by calling a public KIO::blah method +(the correct prototypes, etc, are in kio/job.h). Once this is done, you +connect to the result() signal, and wait for the result. There are +other signals emitted by jobs, see kio/jobclasses.h. Once again, +see the online documentation for more. + + +If you are interested in working on an ioslave, +the following slaves are severely lacking in functionality: + + SMTP + SMB + +------------- + +Original document by Rich. +Updated for KDE 2 by David. + diff --git a/kioslave/Makefile.am b/kioslave/Makefile.am new file mode 100644 index 000000000..67e21cd87 --- /dev/null +++ b/kioslave/Makefile.am @@ -0,0 +1,11 @@ +if include_kioslave_ldap +LDAP_SUBDIR=ldap +endif + +if include_kioslave_smb +SMB_SUBDIR=smb +endif + +SUBDIRS = about cgi floppy filter fish info mac man nfs nntp pop3 smtp \ + sftp tar finger thumbnail $(LDAP_SUBDIR) $(SMB_SUBDIR) settings trash media \ + remote home system diff --git a/kioslave/about/Makefile.am b/kioslave/about/Makefile.am new file mode 100644 index 000000000..9f0e959e1 --- /dev/null +++ b/kioslave/about/Makefile.am @@ -0,0 +1,21 @@ +## Makefile.am of kdebase/kioslave/about + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +####### Files + +kde_module_LTLIBRARIES = kio_about.la + +kio_about_la_SOURCES = kio_about.cpp +kio_about_la_LIBADD = $(LIB_KSYCOCA) +kio_about_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = kio_about.h + +kdelnk_DATA = about.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/kio_about.pot diff --git a/kioslave/about/about.protocol b/kioslave/about/about.protocol new file mode 100644 index 000000000..bc685bfe5 --- /dev/null +++ b/kioslave/about/about.protocol @@ -0,0 +1,9 @@ +[Protocol] +exec=kio_about +protocol=about +input=none +output=filesystem +reading=true +defaultMimetype=text/html +Icon=help_index +Class=:local diff --git a/kioslave/about/kio_about.cpp b/kioslave/about/kio_about.cpp new file mode 100644 index 000000000..226de2f4f --- /dev/null +++ b/kioslave/about/kio_about.cpp @@ -0,0 +1,76 @@ +/* This file is part of the KDE libraries + + Copyright (c) 2002 John Firebaugh <jfirebaugh@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. +*/ + +#include "kio_about.h" + +#include <stdlib.h> +#include <qstring.h> +#include <kinstance.h> +#include <kurl.h> + +using namespace KIO; + +AboutProtocol::AboutProtocol(const QCString &pool_socket, const QCString &app_socket) + : SlaveBase("about", pool_socket, app_socket) +{ +} + +AboutProtocol::~AboutProtocol() +{ +} + +void AboutProtocol::get( const KURL& ) +{ + QByteArray output; + + QTextStream os( output, IO_WriteOnly ); + os.setEncoding( QTextStream::Latin1 ); // In fast ASCII + + os << "<html><head><title>about:blank</title></head><body></body></html>"; + + data( output ); + finished(); +} + +void AboutProtocol::mimetype( const KURL& ) +{ + mimeType("text/html"); + finished(); +} + +extern "C" +{ + int KDE_EXPORT kdemain( int argc, char **argv ) { + + KInstance instance("kio_about"); + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_about protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + AboutProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + return 0; + } +} + diff --git a/kioslave/about/kio_about.h b/kioslave/about/kio_about.h new file mode 100644 index 000000000..29982dbe7 --- /dev/null +++ b/kioslave/about/kio_about.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE libraries + + Copyright (c) 2002 John Firebaugh <jfirebaugh@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 __kio_about_h__ +#define __kio_about_h__ + +#include <qcstring.h> + +#include <kio/global.h> +#include <kio/slavebase.h> + + +class AboutProtocol : public KIO::SlaveBase +{ +public: + AboutProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~AboutProtocol(); + + virtual void get(const KURL& url); + virtual void mimetype(const KURL& url); +}; + +#endif diff --git a/kioslave/cgi/Makefile.am b/kioslave/cgi/Makefile.am new file mode 100644 index 000000000..cc71753e9 --- /dev/null +++ b/kioslave/cgi/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = kcmcgi + +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = kio_cgi.la + +kio_cgi_la_SOURCES = cgi.cpp +kio_cgi_la_LIBADD = $(LIB_KIO) +kio_cgi_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = cgi.h + +METASOURCES = AUTO + +kdelnkdir = $(kde_servicesdir) +kdelnk_DATA = cgi.protocol diff --git a/kioslave/cgi/README b/kioslave/cgi/README new file mode 100644 index 000000000..d68fc9d2c --- /dev/null +++ b/kioslave/cgi/README @@ -0,0 +1,15 @@ +The CGI IO slave provides a way to execute CGI programs without the need to +have a running web server. This can for example be used for local testing of +CGI programs or for using search engines that only provide a CGI frontend like +the one from Doxygen. + +The IO slave implements the cgi: protocol. It uses the filename from the given +URL and searches a configurable list of directories. If it finds an executable +with the given name it executes it, passes the arguments of the URL and sets the +environment variables needed by CGI programs. + +The kcontrol module System/kcmcgi is used to configure the search paths for CGI +programs. + +If you have questions or comments please contact Cornelius Schumacher +<schumacher@kde.org>. diff --git a/kioslave/cgi/cgi.cpp b/kioslave/cgi/cgi.cpp new file mode 100644 index 000000000..011760e0b --- /dev/null +++ b/kioslave/cgi/cgi.cpp @@ -0,0 +1,273 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + Copyright 2006 Michael Pyne <michael.pyne@kde.org> + + This program 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 program 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 program; 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 <qdir.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kprocess.h> +#include <kstandarddirs.h> +#include <kinstance.h> +#include <klocale.h> +#include <kconfig.h> + +#include "cgi.h" + +using namespace KIO; + +CgiProtocol::CgiProtocol( const QCString &pool, const QCString &app ) + : SlaveBase( "cgi", pool, app ) +{ + kdDebug(7124) << "CgiProtocol::CgiProtocol" << endl; + + KConfig cfg( "kcmcgirc" ); + cfg.setGroup( "General" ); + mCgiPaths = cfg.readListEntry( "Paths" ); +} + +CgiProtocol::~CgiProtocol() +{ + kdDebug(7124) << "CgiProtocol::~CgiProtocol" << endl; +} + +/** + * Search in reverse order through a QByteArray for a given character. The position + * of the character is returned, or -1 if it was not found. + */ +static int qByteArrayFindRev( const QByteArray &ba, char c, int startIndex ) +{ + for ( int i = startIndex; i >= 0; --i ) + if ( ba[i] == c ) return i; + + return -1; +} + +/** + * Extract data in ba from start, including no more than len characters from ba. + * Should be exactly comparable to QCString::mid() + */ +static QCString extractQCString( const QByteArray &ba, uint start, uint len = 0xffffffff ) +{ + uint realLen = len; + + if ( ( ba.size() - start ) < len ) + realLen = ba.size() - start; + + return QCString( &ba[ start ], realLen + 1 ); +} + +/** + * Search through a QByteArray for a given string. The position of the string + * is returned, or -1 if it was not found. + */ +static int qByteArrayFindStr( const QByteArray &ba, const char *str ) +{ + int strLen = qstrlen( str ); + int searchLen = ba.size() - strLen; + + for ( int i = 0; i <= searchLen; ++i ) { + QCString temp = extractQCString( ba, i, strLen ); + if ( temp == str ) + return i; + } + + return -1; +} + +void CgiProtocol::get( const KURL& url ) +{ + kdDebug(7124) << "CgiProtocol::get()" << endl; + kdDebug(7124) << " URL: " << url.url() << endl; +#if 0 + kdDebug(7124) << " Path: " << url.path() << endl; + kdDebug(7124) << " Query: " << url.query() << endl; + kdDebug(7124) << " Protocol: " << url.protocol() << endl; + kdDebug(7124) << " Filename: " << url.filename() << endl; +#endif + QCString protocol = "SERVER_PROTOCOL=HTTP"; + putenv( protocol.data() ); + + QCString requestMethod = "REQUEST_METHOD=GET"; + putenv( requestMethod.data() ); + + QCString query = url.query().mid( 1 ).local8Bit(); + query.prepend( "QUERY_STRING=" ); + putenv( query.data() ); + + QString path = url.path(); + + QString file; + + int pos = path.findRev('/'); + if ( pos >= 0 ) file = path.mid( pos + 1 ); + else file = path; + + QString cmd; + + bool stripHeader = false; + bool forwardFile = true; + + QStringList::ConstIterator it; + for( it = mCgiPaths.begin(); it != mCgiPaths.end(); ++it ) { + cmd = *it; + if ( !(*it).endsWith("/") ) + cmd += "/"; + cmd += file; + if ( KStandardDirs::exists( cmd ) ) { + forwardFile = false; + stripHeader = true; + break; + } + } + + FILE *fd; + + if ( forwardFile ) { + kdDebug(7124) << "Forwarding to '" << path << "'" << endl; + + QCString filepath = QFile::encodeName( path ); + + fd = fopen( filepath.data(), "r" ); + + if ( !fd ) { + kdDebug(7124) << "Error opening '" << filepath << "'" << endl; + error( KIO::ERR_CANNOT_OPEN_FOR_READING, filepath ); + return; + } + } else { + kdDebug(7124) << "Cmd: " << cmd << endl; + + fd = popen( QFile::encodeName(KProcess::quote( cmd )).data(), "r" ); + + if ( !fd ) { + kdDebug(7124) << "Error running '" << cmd << "'" << endl; + error( KIO::ERR_CANNOT_OPEN_FOR_READING, cmd ); + return; + } + } + + char buffer[ 4090 ]; + + while ( !feof( fd ) ) + { + int n = fread( buffer, 1, 2048, fd ); + + if ( n == -1 ) + { + // ERROR + if ( forwardFile ) { + fclose( fd ); + } else { + pclose( fd ); + } + return; + } + + buffer[n] = 0; + + if ( stripHeader ) { + QByteArray output; + + // Access the buffer in-place by using setRawData() + output.setRawData( buffer, n ); + + int colon = output.find( ':' ); + int newline = output.find( '\n' ); + int semicolon = qByteArrayFindRev( output, ';', newline ); + int end; + if ( semicolon < 0 ) end = newline; + else end = semicolon; + +#if 0 + kdDebug(7124) << " colon: " << colon << endl; + kdDebug(7124) << " newline: " << newline << endl; + kdDebug(7124) << " semicolon: " << semicolon << endl; + kdDebug(7124) << " end: " << end << endl; +#endif + + QCString contentType = extractQCString( output, colon + 1, end - colon - 1 ); + + contentType = contentType.stripWhiteSpace(); + + kdDebug(7124) << "ContentType: '" << contentType << "'" << endl; + + mimeType( contentType ); + + int start = qByteArrayFindStr( output, "\r\n\r\n" ); + if ( start >= 0 ) start += 4; + else { + start = qByteArrayFindStr( output, "\n\n" ); + if ( start >= 0 ) start += 2; + } + + if ( start < 0 ) + start = 0; + + // We're done with the part of the buffer we're using. + output.resetRawData ( buffer, n ); + + // Now access the part of the buffer after the header. + output.setRawData ( buffer + start, n - start ); + data( output ); + output.resetRawData ( buffer + start, n - start ); + + stripHeader = false; + } else { + QByteArray array; + array.setRawData( buffer, n ); + data( array ); + array.resetRawData( buffer, n ); + } + } + + if ( forwardFile ) { + fclose( fd ); + } else { + pclose( fd ); + } + + finished(); + + kdDebug(7124) << "CgiProtocol::get - done" << endl; +} + +extern "C" { int KDE_EXPORT kdemain( int argc, char **argv ); } + +/*! The kdemain function generates an instance of the ioslave and starts its + * dispatch loop. */ + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_cgi" ); + + kdDebug(7124) << "kio_cgi starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_cgi protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + CgiProtocol slave( argv[2], argv[3] ); + slave.dispatchLoop(); + + return 0; +} diff --git a/kioslave/cgi/cgi.h b/kioslave/cgi/cgi.h new file mode 100644 index 000000000..fec90aa59 --- /dev/null +++ b/kioslave/cgi/cgi.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + + This program 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 program 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 program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#ifndef KIO_CGI_H +#define KIO_CGI_H + +#include <qobject.h> + +#include <kio/slavebase.h> + +class KProcess; + +/*! + This class implements an ioslave for viewing CGI script output without the + need to run a web server. +*/ +class CgiProtocol : public KIO::SlaveBase +{ + public: + CgiProtocol( const QCString &pool, const QCString &app ); + virtual ~CgiProtocol(); + + virtual void get( const KURL& url ); + +// virtual void mimetype( const KURL& url ); + + protected: +// QCString errorMessage(); + + private: + QStringList mCgiPaths; +}; + +#endif diff --git a/kioslave/cgi/cgi.protocol b/kioslave/cgi/cgi.protocol new file mode 100644 index 000000000..9c6cc378e --- /dev/null +++ b/kioslave/cgi/cgi.protocol @@ -0,0 +1,8 @@ +[Protocol] +exec=kio_cgi +protocol=cgi +input=none +output=filesystem +reading=true +DocPath=kioslave/cgi.html +Icon=html diff --git a/kioslave/cgi/kcmcgi/Makefile.am b/kioslave/cgi/kcmcgi/Makefile.am new file mode 100644 index 000000000..abfef594b --- /dev/null +++ b/kioslave/cgi/kcmcgi/Makefile.am @@ -0,0 +1,17 @@ + +kde_module_LTLIBRARIES = kcm_cgi.la + +kcm_cgi_la_SOURCES = kcmcgi.cpp +kcm_cgi_la_LDFLAGS = $(all_libraries) -module -avoid-version -no-undefined +kcm_cgi_la_LIBADD = -lkdeui $(LIB_KIO) + +INCLUDES= $(all_includes) + +kcm_cgi_la_METASOURCES = AUTO + +noinst_HEADERS = kcmcgi.h + +xdg_apps_DATA = kcmcgi.desktop + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kcmcgi.pot diff --git a/kioslave/cgi/kcmcgi/kcmcgi.cpp b/kioslave/cgi/kcmcgi/kcmcgi.cpp new file mode 100644 index 000000000..18436e8d9 --- /dev/null +++ b/kioslave/cgi/kcmcgi/kcmcgi.cpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + + This program 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 program 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 program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include <kconfig.h> +#include <klocale.h> +#include <kglobal.h> +#include <kaboutdata.h> +#include <kfiledialog.h> + +#include <qlayout.h> +#include <qlistbox.h> +#include <qpushbutton.h> +#include <qgroupbox.h> +#include <qhbox.h> + +#include "kcmcgi.h" +#include "kcmcgi.moc" + +extern "C" +{ + KDE_EXPORT KCModule *create_cgi( QWidget *parent, const char * ) + { + KGlobal::locale()->insertCatalogue("kcmcgi"); + return new KCMCgi( parent, "kcmcgi" ); + } +} + + +KCMCgi::KCMCgi(QWidget *parent, const char *name) + : KCModule(parent, name) +{ + setButtons(Default|Apply); + + QVBoxLayout *topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint()); + + QGroupBox *topBox = new QGroupBox( 1, Horizontal, i18n("Paths to Local CGI Programs"), this ); + topLayout->addWidget( topBox ); + + mListBox = new QListBox( topBox ); + + QHBox *buttonBox = new QHBox( topBox ); + buttonBox->setSpacing( KDialog::spacingHint() ); + + mAddButton = new QPushButton( i18n("Add..."), buttonBox ); + connect( mAddButton, SIGNAL( clicked() ), SLOT( addPath() ) ); + + mRemoveButton = new QPushButton( i18n("Remove"), buttonBox ); + connect( mRemoveButton, SIGNAL( clicked() ), SLOT( removePath() ) ); + connect( mListBox, SIGNAL( clicked ( QListBoxItem * )),this, SLOT( slotItemSelected( QListBoxItem *))); + + mConfig = new KConfig("kcmcgirc"); + + load(); + updateButton(); + KAboutData *about = + new KAboutData( I18N_NOOP("kcmcgi"), + I18N_NOOP("CGI KIO Slave Control Module"), + 0, 0, KAboutData::License_GPL, + I18N_NOOP("(c) 2002 Cornelius Schumacher") ); + + about->addAuthor( "Cornelius Schumacher", 0, "schumacher@kde.org" ); + setAboutData(about); +} + +KCMCgi::~KCMCgi() +{ + delete mConfig; +} + +void KCMCgi::slotItemSelected( QListBoxItem * ) +{ + updateButton(); +} + +void KCMCgi::updateButton() +{ + mRemoveButton->setEnabled( mListBox->selectedItem ()); +} + +void KCMCgi::defaults() +{ + mListBox->clear(); + updateButton(); +} + +void KCMCgi::save() +{ + QStringList paths; + + uint i; + for( i = 0; i < mListBox->count(); ++i ) { + paths.append( mListBox->text( i ) ); + } + + mConfig->setGroup( "General" ); + mConfig->writeEntry( "Paths", paths ); + + mConfig->sync(); +} + +void KCMCgi::load() +{ + mConfig->setGroup( "General" ); + QStringList paths = mConfig->readListEntry( "Paths" ); + + mListBox->insertStringList( paths ); +} + +void KCMCgi::addPath() +{ + QString path = KFileDialog::getExistingDirectory( QString::null, this ); + + if ( !path.isEmpty() ) { + mListBox->insertItem( path ); + emit changed( true ); + } + updateButton(); +} + +void KCMCgi::removePath() +{ + int index = mListBox->currentItem(); + if ( index >= 0 ) { + mListBox->removeItem( index ); + emit changed( true ); + } + updateButton(); +} + +QString KCMCgi::quickHelp() const +{ + return i18n("<h1>CGI Scripts</h1> The CGI KIO slave lets you execute " + "local CGI programs without the need to run a web server. " + "In this control module you can configure the paths that " + "are searched for CGI scripts."); +} diff --git a/kioslave/cgi/kcmcgi/kcmcgi.desktop b/kioslave/cgi/kcmcgi/kcmcgi.desktop new file mode 100644 index 000000000..a948a38b4 --- /dev/null +++ b/kioslave/cgi/kcmcgi/kcmcgi.desktop @@ -0,0 +1,231 @@ +[Desktop Entry] +Icon=run +Type=Application +Exec=kcmshell kcmcgi +DocPath= +X-KDE-ModuleType=Library +X-KDE-Library=cgi + +Name=CGI Scripts +Name[af]=CGI Skripte +Name[ar]=نصوص CGI البرمجية +Name[az]=CGI SkriptlÉ™ri +Name[be]=СцÑнары CGI +Name[bg]=CGI Ñкриптове +Name[bn]=সি-জি-আই সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ +Name[br]=Urzhiaouegoù CGI +Name[bs]=CGI skripte +Name[ca]=Scripts CGI +Name[cs]=CGI skripty +Name[csb]=Skriptë CGI +Name[cy]=Sgriptiau CGI +Name[da]=CGI Scripter +Name[de]=CGI-Skripte +Name[el]=ΣενάÏια CGI +Name[eo]=CGI-Skriptaĵoj +Name[es]=Procedimientos CGI +Name[et]=CGI skriptid +Name[eu]=CGI scriptak +Name[fa]=دست‌نوشته‌های CGI +Name[fi]=CGI-komentosarjat +Name[fr]=Scripts CGI +Name[fy]=CGI-skripts +Name[ga]=Scripteanna CGI +Name[gl]=Guións CGI +Name[he]=תסריטי CGI +Name[hi]=सीजीआई सà¥à¤•à¥à¤°à¤¿à¤ªà¥à¤Ÿ +Name[hr]=CGI skripte +Name[hu]=CGI-programok +Name[is]=CGI Skriftur +Name[it]=Script CGI +Name[ja]=CGI スクリプト +Name[ka]=CGI სკრიპტები +Name[kk]=CGI Ñкрипттері +Name[km]=ស្គ្រីប CGI +Name[ko]=CGI 스í¬ë¦½íЏ +Name[lo]=ໂà»àºŠàº¥àº²àº¥àºµàºª +Name[lt]=CGI scenarijai +Name[lv]=CGI Skripts +Name[mk]=CGI-Ñкрипти +Name[mn]=CGI-Скрипт +Name[ms]=Skrip CGI +Name[mt]=Scripts CGI +Name[nb]=CGI-skript +Name[nds]=CGI-Skripten +Name[ne]=CGI सà¥à¤•à¥à¤°à¤¿à¤ªà¥à¤Ÿ +Name[nl]=CGI-scripts +Name[nn]=CGI-skript +Name[nso]=Ditshwaelo tsa CGI +Name[pa]=CGI ਸਕà©à¨°à¨¿à¨ªà¨Ÿà¨¾à¨‚ +Name[pl]=Skrypty CGI +Name[pt]=Programas CGI +Name[pt_BR]=Scripts CGI +Name[ro]=Scripturi CGI +Name[ru]=Сценарии CGI +Name[rw]=Agaporogaramu CGI +Name[se]=CGI-skriptat +Name[sk]=Skripty CGI +Name[sl]=Skripte CGI +Name[sr]=CGI Скрипте +Name[sr@Latn]=CGI Skripte +Name[sv]=CGI-skript +Name[ta]=CGI எழà¯à®¤à¯à®¤à®¾à®•à¯à®•à®™à¯à®•ள௠+Name[te]=సిజిఠసà±à°•à±à°°à°¿à°ªà±à°Ÿà±à°²à± +Name[tg]=ДаÑтнавиÑи CGI +Name[th]=สคริปต์ CGI +Name[tr]=CD Betikleri +Name[tt]=CGI Ämerleklär +Name[uk]=Скрипти CGI +Name[uz]=CGI skriptlar +Name[uz@cyrillic]=CGI Ñкриптлар +Name[ven]=Zwikiriputi zwa CGI +Name[vi]=Văn lệnh CGI +Name[wa]=Scripe CGI +Name[xh]=Amagama ashicilelwe phantsi CGI +Name[zh_CN]=CGI 脚本 +Name[zh_TW]=CGI 命令稿 +Name[zu]=Izikript ze-CGI +Comment=Configure the CGI KIO slave +Comment[af]=Stel die CGI KIO slaaf op +Comment[ar]=تهيئة CGI KIO slave +Comment[be]=ÐаÑтаўленні CGI KIO slave +Comment[bg]=ÐаÑтройване на модула за изпълнение на Ñкриптове без уеб Ñървър - CGI KIO +Comment[bn]=CGI KIO সà§à¦²à§‡à¦ কনফিগার করà§à¦¨ +Comment[bs]=PodeÅ¡avanje CGI KIO slave-a +Comment[ca]=Configura l'esclau KIO CGI +Comment[cs]=Nastavenà CGI pro KDE +Comment[csb]=Kònfigùracëjô procedurë òbsÅ‚użënkù CGI +Comment[cy]=Ffurfweddu'r gwas CGI KIO +Comment[da]=Indstilling af CGI KIO-slaven +Comment[de]=Ein-/Ausgabemodul für CGI einrichten +Comment[el]=Ρυθμίστε το CGI KIO slave +Comment[eo]=Agordu CGI-enel-sklavon +Comment[es]=Configuración del KIO slave de CGI +Comment[et]=CGI KIO mooduli seadistamine +Comment[eu]=CGI KIO slave-a konfiguratu +Comment[fa]=پیکربندی پی‌رو CGI KIO +Comment[fi]=Muokkaa CGI-KIO-palvelun asetuksia +Comment[fr]=Configuration du CGI KIO slave +Comment[fy]=Hjir kinne jo de CGI Kio-slave ynstelle +Comment[ga]=Cumraigh an sclábhaà CGI KIO +Comment[gl]=Configuración do escravo KIO de CGI +Comment[he]=×©×™× ×•×™ הגדרות פרוטוקול ×”Ö¾CGI +Comment[hi]=सीजीआई केआईओ सà¥à¤²à¥‡à¤µ कॉनà¥à¤«à¤¼à¤¿à¤—र करें +Comment[hr]=Konfiguriranje CGI KIO podÄinjenog +Comment[hu]=A CGI KDE-protokoll beállÃtásai +Comment[is]=Stilla CGI þrælinn +Comment[it]=Configura il KIO-slave CGI +Comment[ja]=CGI KIO スレーブã®è¨å®š +Comment[ka]=CGI KIO slave-ის კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ +Comment[kk]=CGI KIO slave-ты баптау +Comment[km]=កំណážáŸ‹â€‹ážšáž…នាសម្ពáŸáž“្ធ​កូនចៅ CGI KIO +Comment[ko]=CGI KIO ìŠ¬ë ˆì´ë¸Œ ì„¤ì • +Comment[lo]=ປັບà»àº•່ງàºà»‰àºàº‡ +Comment[lt]=KonfigÅ«ruoti CGI KIO slave +Comment[lv]=KonfigurÄ“t CGI KIO vergu +Comment[mk]=Конфигурација на CGI KIO Ñлужителот +Comment[mn]=CGI-Оролт/Гаралтын-Модул тохируулах +Comment[ms]=Konfigur hamba CGI KIO +Comment[mt]=Ikkonfigura l-iskjav CGI +Comment[nb]=Tilpass CGI KIO slave +Comment[nds]=Den CGI-In-/Utgaavdeenst instellen +Comment[ne]=CGI KIO सà¥à¤²à¥‡à¤ कनà¥à¤«à¤¿à¤—र गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Hier kunt u de CGI Kio-slave instellen +Comment[nn]=Set opp CGI-KIO-slaven +Comment[nso]=Beakanya lekgoba la KIO ya CGI +Comment[pa]=CGI KIO ਸਲੇਵ ਸੰਰਚਨਾ +Comment[pl]=Konfiguracja procedury obsÅ‚ugi CGI +Comment[pt]=Configuração do KIO slave de CGIs +Comment[pt_BR]=Configurar o KIO (escravo) do CGI +Comment[ro]=Configurează dispozitivul I/O CGI +Comment[ru]=ÐаÑтройка CGI KIO slave +Comment[rw]=Kuboneza CGI KIO umugaragu +Comment[se]=Heivet CGI-KIO-Å¡láva +Comment[sk]=Nastavenie IO klienta CGI +Comment[sl]=Nastavi podrejenega KIO CGI +Comment[sr]=Подешавање CGI KIO slave-а +Comment[sr@Latn]=PodeÅ¡avanje CGI KIO slave-a +Comment[sv]=Anpassa I/O-slaven för CGI +Comment[ta]=CGI KIO slave஠வடிவமை +Comment[tg]=Бандаи CGI KIO-ро танзим кунед +Comment[th]=ปรับà¹à¸•่ง CGI KIO slave +Comment[tr]=CGI KIO aracısını yapılandır +Comment[tt]=CGI KIO slave caylawı +Comment[uk]=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð»ÐµÐ³Ð»Ð¾Ð³Ð¾ KIO CGI +Comment[uz]=CGI KIO sleyvni moslash +Comment[uz@cyrillic]=CGI KIO Ñлейвни моÑлаш +Comment[ven]=Dzudzanyani phuli CGI KIO +Comment[vi]=Cấu hình đà y tá»›CGI KIO +Comment[wa]=Apontyî li mandaye KIO CGI +Comment[xh]=Qwlalsela i CGI KIO slave +Comment[zh_CN]=é…ç½® CGI KIO 仆人 +Comment[zh_TW]=è¨å®š CGI KIO slave +Comment[zu]=Hlanganisela i-CGI KIO slave + +Keywords=CGI,KIO,Slave,Paths +Keywords[ar]=CGI,KIO,Slave,Paths,مسارات +Keywords[az]=CGI,KIO,Slave,Paths,Cığırlar +Keywords[be]=ШлÑÑ…Ñ–,CGI,KIO,Slave,Paths +Keywords[bg]=Ñкриптове, уеб, динамичен, Ñкрипт, Интернет, път, пътища, CGI, KIO, Slave, Paths +Keywords[br]=CGI,KIO,sklav,hentoù +Keywords[ca]=CGI,KIO,Esclau,Rutes +Keywords[cs]=CGI,KIO,slave,cesty +Keywords[csb]=CGI,KIO,procedurë wé/wi,stegnë +Keywords[cy]=CGI,KIO,Gwas,Llwybrau +Keywords[da]=CGI,KIO,Slave,Stier +Keywords[de]=CGI,KIO,Ein-/Ausgabemodul,Pfade +Keywords[el]=CGI,KIO,Slave,ΔιαδÏομÎÏ‚ +Keywords[eo]=CGI,Enel,K-enel,sklavo,servo,vojoj +Keywords[es]=CGI,KIO,Slave,Rutas +Keywords[et]=CGI,KIO,moodul,otsinguteed +Keywords[fa]=CGIØŒ KIOØŒ SlaveØŒ مسیرها +Keywords[fi]=CGI,KIO,KIO-palvelu,palvelu,Polut +Keywords[fr]=CGI,KIO,Slave,Paths,Chemins,Emplacements +Keywords[fy]=cgi,kio,slave,paths,paden +Keywords[ga]=CGI,KIO,SclábhaÃ,Bealaà +Keywords[gl]=CGI,KIO,Escravo,Camiños +Keywords[he]=CGI,KIO,פרוטוקול,× ×ª×™×‘×™×, Slave,Paths +Keywords[hi]=सीजीआई,केआईओ,सà¥à¤²à¥‡à¤µ,पथ +Keywords[hr]=CGI,KIO,Slave,Paths,podÄinjeni,putanje +Keywords[hu]=CGI,KIO,protokoll,elérési utak +Keywords[is]=CGI,KIO,þræll,slóðir +Keywords[it]=CGI,KIO,kioslave,percorsi +Keywords[ja]=CGI,KIO,スレーブ,パス +Keywords[km]=CGI,KIO,កូនចៅ,ផ្លូវ +Keywords[lt]=CGI,KIO,Slave,Paths, keliai +Keywords[lv]=CGI,KIO,vergi,ceļi +Keywords[mk]=CGI,KIO,Slave,Paths,Патеки +Keywords[mn]=CGI,KIO,Оролт/Гаралтын-Модул,Зам +Keywords[mt]=CGI,KIO,Slave,Paths,skjav,passaġġ +Keywords[nb]=CGI,KIO,Slave,slave,stier +Keywords[nds]=CGI,KIO,Slave,IU,In-/Utgaavdeenst,Deenst,Padden +Keywords[ne]=CGI,KIO,सà¥à¤²à¥‡à¤, मारà¥à¤— +Keywords[nl]=cgi,kio,slave,paths,paden +Keywords[nn]=CGI,KIO,slave,stiar +Keywords[nso]=CGI,KIO,Lekgoba,Ditsejana +Keywords[pa]=CGI,KIO,ਸਲੇਵ,ਮਾਰਗ +Keywords[pl]=CGI,KIO,procedury we/wy,Å›cieżki +Keywords[pt]=CGI,KIO,Slave,Localizações +Keywords[pt_BR]=CGI,KIO,Escravo,Caminhos +Keywords[ro]=I/E,IE,CGI,KIO,dispozitiv,căi +Keywords[rw]=CGI,KIO,Umugaragu,Inzira +Keywords[se]=CGI,KIO,Å¡láva,bálgát +Keywords[sk]=CGI,KIO,klient,cesty +Keywords[sl]=CGI,KIO,podrejeni,pot +Keywords[sr]=CGI,KIO,Slave,Путање +Keywords[sr@Latn]=CGI,KIO,Slave,Putanje +Keywords[sv]=CGI,KIO,Slav,Sökvägar +Keywords[ta]=CGI,KIO,ஸà¯à®²à¯‡à®µà¯,பாதைகள௠+Keywords[te]=సిజిà°,కెà°à°’,బానిస,దారà±à°²à± +Keywords[th]=CGI,KIO,Slave,เส้นทาง +Keywords[tr]=CGI,KIO,Aracı,Yollar +Keywords[uk]=CGI,KIO,підлеглий,шлÑÑ… +Keywords[uz]=CGI,KIO,Sleyv,YoÊ»llar +Keywords[uz@cyrillic]=CGI,KIO,Слейв,Йўллар +Keywords[ven]=CGI,KIO,Phuli,Ludila +Keywords[vi]=CGI,KIO,Äà y tá»›,ÄÆ°á»ng dẫn +Keywords[wa]=CGI,KIO,Slave,Paths,tchimins,mandaye +Keywords[zh_CN]=CGI,KIO,Slave,Paths,路径 +Keywords[zh_TW]=CGI,KIO,Slave,Paths,路徑 +Keywords[zu]=CGI,KIO,Slave,Izindlela +Categories=Qt;KDE;X-KDE-settings-webbrowsing; diff --git a/kioslave/cgi/kcmcgi/kcmcgi.h b/kioslave/cgi/kcmcgi/kcmcgi.h new file mode 100644 index 000000000..10e4e3385 --- /dev/null +++ b/kioslave/cgi/kcmcgi/kcmcgi.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + + This program 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 program 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 program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#ifndef KCMCGI_H +#define KCMCGI_H + +#include <kcmodule.h> + +class QListBox; +class QPushButton; + +class KConfig; + +class KCMCgi : public KCModule +{ + Q_OBJECT + public: + KCMCgi( QWidget *parent = 0, const char *name = 0 ); + ~KCMCgi(); + + void load(); + void save(); + void defaults(); + QString quickHelp() const; + + public slots: + + protected slots: + void addPath(); + void removePath(); + void slotItemSelected( QListBoxItem * item ); + private: + void updateButton(); + QListBox *mListBox; + QPushButton *mAddButton; + QPushButton *mRemoveButton; + + KConfig *mConfig; +}; + +#endif diff --git a/kioslave/configure.in.bot b/kioslave/configure.in.bot new file mode 100644 index 000000000..11b66748d --- /dev/null +++ b/kioslave/configure.in.bot @@ -0,0 +1,7 @@ +if test -z "$SASL2_LIBS"; then + echo "" + echo "cyrus-sasl 2 library is missing. The pop3 and smtp ioslaves will lack of a lot of authentication methods." + echo "See http://asg.web.cmu.edu/sasl/sasl-library.html or your distribution's packages." + echo "" + all_tests=bad +fi diff --git a/kioslave/configure.in.in b/kioslave/configure.in.in new file mode 100644 index 000000000..331a300b8 --- /dev/null +++ b/kioslave/configure.in.in @@ -0,0 +1,15 @@ +KDE_CHECK_SSL + +sasl2_header="no" +SASL2_LIBS="" + +KDE_CHECK_HEADERS(sasl.h)dnl SASL1 header is enough for kio_ldap +KDE_CHECK_HEADERS(sasl/sasl.h, sasl2_header="yes") +if test "$sasl2_header" = "yes" ; then + KDE_CHECK_LIB(sasl2, sasl_client_init, SASL2_LIBS="-lsasl2") +fi + +if test "x$SASL2_LIBS" != "x" ; then + AC_DEFINE_UNQUOTED(HAVE_LIBSASL2, 1, [Define if you have cyrus-sasl2 libraries]) +fi +AC_SUBST(SASL2_LIBS) diff --git a/kioslave/filter/Makefile.am b/kioslave/filter/Makefile.am new file mode 100644 index 000000000..f315064c8 --- /dev/null +++ b/kioslave/filter/Makefile.am @@ -0,0 +1,21 @@ +KDE_CPPFLAGS = -DQT_NO_CAST_ASCII + +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = kio_filter.la + +kio_filter_la_SOURCES = filter.cc +kio_filter_la_LIBADD = $(LIB_KSYCOCA) +kio_filter_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = filter.h + +METASOURCES = AUTO + +protocoldir = $(kde_servicesdir) + +if include_BZIP2 +BZIP2FILES=bzip.protocol bzip2.protocol +endif + +protocol_DATA = gzip.protocol $(BZIP2FILES) + diff --git a/kioslave/filter/bzip.protocol b/kioslave/filter/bzip.protocol new file mode 100644 index 000000000..65564992a --- /dev/null +++ b/kioslave/filter/bzip.protocol @@ -0,0 +1,10 @@ +[Protocol] +exec=kio_filter +protocol=bzip +mimetype=application/x-bzip +input=stream +output=stream +reading=true +source=false +DocPath=kioslave/bzip.html +Icon=ark diff --git a/kioslave/filter/bzip2.protocol b/kioslave/filter/bzip2.protocol new file mode 100644 index 000000000..c7d7c782a --- /dev/null +++ b/kioslave/filter/bzip2.protocol @@ -0,0 +1,10 @@ +[Protocol] +exec=kio_filter +protocol=bzip2 +mimetype=application/x-bzip2 +input=stream +output=stream +reading=true +source=false +DocPath=kioslave/bzip2.html +Icon=ark diff --git a/kioslave/filter/configure.in.in b/kioslave/filter/configure.in.in new file mode 100644 index 000000000..5ef522b6f --- /dev/null +++ b/kioslave/filter/configure.in.in @@ -0,0 +1 @@ +AC_FIND_BZIP2 diff --git a/kioslave/filter/filter.cc b/kioslave/filter/filter.cc new file mode 100644 index 000000000..fe7cbc154 --- /dev/null +++ b/kioslave/filter/filter.cc @@ -0,0 +1,167 @@ +/* +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. +*/ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> + +#include <kinstance.h> +#include <kdebug.h> +#include <kmimemagic.h> +#include <kfilterbase.h> + +#include "filter.h" + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char ** argv) +{ + KInstance instance( "kio_filter" ); + + kdDebug(7110) << "Starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_filter protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + FilterProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7110) << "Done" << endl; + return 0; +} + +FilterProtocol::FilterProtocol( const QCString & protocol, const QCString &pool, const QCString &app ) + : KIO::SlaveBase( protocol, pool, app ) +{ + QString mimetype = QString::fromLatin1("application/x-") + QString::fromLatin1(protocol); + filter = KFilterBase::findFilterByMimeType( mimetype ); + Q_ASSERT(filter); +} + +void FilterProtocol::get( const KURL & ) +{ + if (subURL.isEmpty()) + { + error( KIO::ERR_NO_SOURCE_PROTOCOL, mProtocol ); + return; + } + if (!filter) + { + error( KIO::ERR_INTERNAL, mProtocol ); + return; + } + needSubURLData(); + + filter->init(IO_ReadOnly); + + bool bNeedHeader = true; + bool bNeedMimetype = true; + bool bError = true; + int result; + + QByteArray inputBuffer; + QByteArray outputBuffer(8*1024); // Start with a modest buffer + filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); + while(true) + { + if (filter->inBufferEmpty()) + { + dataReq(); // Request data + result = readData( inputBuffer); + kdDebug(7110) << "requestData: got " << result << endl; + if (result <= 0) + { + bError = true; + break; // Unexpected EOF. + } + filter->setInBuffer( inputBuffer.data(), inputBuffer.size() ); + } + if (bNeedHeader) + { + bError = !filter->readHeader(); + if (bError) + break; + bNeedHeader = false; + } + result = filter->uncompress(); + if ((filter->outBufferAvailable() == 0) || (result == KFilterBase::END)) + { + kdDebug(7110) << "avail_out = " << filter->outBufferAvailable() << endl; + if (filter->outBufferAvailable() != 0) + { + // Discard unused space :-) + outputBuffer.resize(outputBuffer.size() - filter->outBufferAvailable()); + } + if (bNeedMimetype) + { + KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( outputBuffer, subURL.fileName() ); + kdDebug(7110) << "Emitting mimetype " << result->mimeType() << endl; + mimeType( result->mimeType() ); + bNeedMimetype = false; + } + data( outputBuffer ); // Send data + filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); + if (result == KFilterBase::END) + break; // Finished. + } + if (result != KFilterBase::OK) + { + bError = true; + break; // Error + } + } + + if (!bError) + { + dataReq(); // Request data + result = readData( inputBuffer); + kdDebug(7110) << "requestData: got " << result << "(expecting 0)" << endl; + data( QByteArray() ); // Send EOF + } + + filter->terminate(); + + if (bError) + { + error(KIO::ERR_COULD_NOT_READ, subURL.url()); + subURL = KURL(); // Clear subURL + return; + } + + subURL = KURL(); // Clear subURL + finished(); +} + +void FilterProtocol::put( const KURL &/*url*/, int, bool /*_overwrite*/, bool /*_resume*/ ) +{ + error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("put")); +} + +void FilterProtocol::setSubURL(const KURL &url) +{ + subURL = url; +} + diff --git a/kioslave/filter/filter.h b/kioslave/filter/filter.h new file mode 100644 index 000000000..51b56061b --- /dev/null +++ b/kioslave/filter/filter.h @@ -0,0 +1,46 @@ +/* +This file is part of KDE + + Copyright (C) 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. +*/ + +#ifndef __filter_h__ +#define __filter_h__ + +#include <qobject.h> +#include <kio/global.h> +#include <kio/slavebase.h> + +class FilterProtocol : public QObject, public KIO::SlaveBase +{ +public: + FilterProtocol( const QCString & protocol, const QCString &pool, const QCString &app ); + + virtual void get( const KURL &url ); + virtual void put( const KURL &url, int _mode, bool _overwrite, + bool _resume ); + virtual void setSubURL(const KURL &url); + +private: + KURL subURL; + KFilterBase * filter; +}; + +#endif diff --git a/kioslave/filter/gzip.protocol b/kioslave/filter/gzip.protocol new file mode 100644 index 000000000..75908e381 --- /dev/null +++ b/kioslave/filter/gzip.protocol @@ -0,0 +1,10 @@ +[Protocol] +exec=kio_filter +protocol=gzip +mimetype=application/gzip +input=stream +output=stream +reading=true +source=false +DocPath=kioslave/gzip.html +Icon=ark diff --git a/kioslave/finger/Makefile.am b/kioslave/finger/Makefile.am new file mode 100644 index 000000000..6ddf78726 --- /dev/null +++ b/kioslave/finger/Makefile.am @@ -0,0 +1,27 @@ +## Makfile.am for kio_finger +## Edit from Makefile.am of kdebase/kioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +####### Files + +kde_module_LTLIBRARIES = kio_finger.la + +kio_finger_la_SOURCES = kio_finger.cpp +kio_finger_la_LIBADD = -lkio +kio_finger_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_finger.h + +kdelnk_DATA = finger.protocol +kdelnkdir = $(kde_servicesdir) + +kio_finger_data_DATA = kio_finger.pl kio_finger.css +kio_finger_datadir = $(kde_datadir)/kio_finger +EXTRA_DIST=$(kio_finger_data_DATA) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_finger.pot diff --git a/kioslave/finger/finger.protocol b/kioslave/finger/finger.protocol new file mode 100644 index 000000000..71d1b8e93 --- /dev/null +++ b/kioslave/finger/finger.protocol @@ -0,0 +1,9 @@ +[Protocol] +exec=kio_finger +protocol=finger +input=none +output=stream +reading=true +defaultMimetype=text/html +DocPath=kioslave/finger.html +Icon=kdmconfig diff --git a/kioslave/finger/kio_finger.cpp b/kioslave/finger/kio_finger.cpp new file mode 100644 index 000000000..c940998b9 --- /dev/null +++ b/kioslave/finger/kio_finger.cpp @@ -0,0 +1,266 @@ + +/*************************************************************************** + kio_finger.cpp - description + ------------------- + begin : Sun Aug 12 2000 + copyright : (C) 2000 by Andreas Schlapbach + email : schlpbch@iam.unibe.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program 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. * + * * + ***************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <string.h> + +#include <qtextstream.h> +#include <qdict.h> +#include <qcstring.h> +#include <qfile.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kurl.h> + +#include "kio_finger.h" + + +using namespace KIO; + +static const QString defaultRefreshRate = "60"; + +extern "C" +{ + KDE_EXPORT int kdemain( int argc, char **argv ) + { + KInstance instance( "kio_finger" ); + + //kdDebug() << "*** Starting kio_finger " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_finger protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + FingerProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + //kdDebug() << "*** kio_finger Done" << endl; + return 0; + } +} + + +/* ---------------------------------------------------------------------------------- */ + + +FingerProtocol::FingerProtocol(const QCString &pool_socket, const QCString &app_socket) + : QObject(), SlaveBase("finger", pool_socket, app_socket) +{ + myStdStream = new QString(); + getProgramPath(); +} + + +/* ---------------------------------------------------------------------------------- */ + + +FingerProtocol::~FingerProtocol() +{ + //kdDebug() << "FingerProtocol::~FingerProtocol()" << endl; + delete myURL; + delete myPerlPath; + delete myFingerPath; + delete myFingerPerlScript; + delete myFingerCSSFile; + delete myStdStream; +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::get(const KURL& url ) +{ + //kdDebug() << "kio_finger::get(const KURL& url)" << endl ; + + this->parseCommandLine(url); + + //kdDebug() << "myURL: " << myURL->prettyURL() << endl; + + // Reset the stream + *myStdStream=""; + + QString query = myURL->query(); + QString refreshRate = defaultRefreshRate; + + //kdDebug() << "query: " << query << endl; + + // Check the validity of the query + + QRegExp regExp("?refreshRate=[0-9][0-9]*", true, true); + if (query.contains(regExp)) { + //kdDebug() << "looks like a valid query" << endl; + QRegExp regExp( "([0-9]+)" ); + regExp.search(query); + refreshRate = regExp.cap(0); + } + + //kdDebug() << "Refresh rate: " << refreshRate << endl; + + myKProcess = new KProcess(); + *myKProcess << *myPerlPath << *myFingerPerlScript + << *myFingerPath << *myFingerCSSFile + << refreshRate << myURL->host() << myURL->user() ; + + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + //connect(myKProcess, SIGNAL(receivedStderr(KProcess *, char *, int)), + // this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + data(QCString(myStdStream->local8Bit())); + + data(QByteArray()); + finished(); + + //clean up + + delete myKProcess; +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::slotGetStdOutput(KProcess* /* p */, char *s, int len) +{ + //kdDebug() << "void FingerProtocol::slotGetStdoutOutput()" << endl; + *myStdStream += QString::fromLocal8Bit(s, len); +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::mimetype(const KURL & /*url*/) +{ + mimeType("text/html"); + finished(); +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::getProgramPath() +{ + //kdDebug() << "kfingerMainWindow::getProgramPath()" << endl; + // Not to sure wether I'm using the right error number here. - schlpbch - + + myPerlPath = new QString(KGlobal::dirs()->findExe("perl")); + if (myPerlPath->isEmpty()) + { + //kdDebug() << "Perl command not found" << endl; + this->error(ERR_CANNOT_LAUNCH_PROCESS, + i18n("Could not find the Perl program on your system, please install.")); + exit(); + } + else + { + //kdDebug() << "Perl command found:" << *myPerlPath << endl; + } + + myFingerPath = new QString(KGlobal::dirs()->findExe("finger")); + if ((myFingerPath->isEmpty())) + { + //kdDebug() << "Finger command not found" << endl; + this->error(ERR_CANNOT_LAUNCH_PROCESS, + i18n("Could not find the Finger program on your system, please install.")); + exit(); + } + else + { + //kdDebug() << "Finger command found:" << *myFingerPath << endl; + } + + myFingerPerlScript = new QString(locate("data","kio_finger/kio_finger.pl")); + if (myFingerPerlScript->isEmpty()) + { + //kdDebug() << "kio_finger.pl script not found" << endl; + this->error(ERR_CANNOT_LAUNCH_PROCESS, + i18n("kio_finger Perl script not found.")); + exit(); + } + else + { + //kdDebug() << "kio_finger perl script found: " << *myFingerPerlScript << endl; + } + + myFingerCSSFile = new QString(locate("data","kio_finger/kio_finger.css")); + if (myFingerCSSFile->isEmpty()) + { + //kdDebug() << "kio_finger.css file not found" << endl; + this->warning(i18n("kio_finger CSS script not found. Output will look ugly.")); + } + else + { + //kdDebug() << "kio_finger CSS file found: " << *myFingerCSSFile << endl; + } +} + + +/* --------------------------------------------------------------------------- */ + + +void FingerProtocol::parseCommandLine(const KURL& url) +{ + myURL = new KURL(url); + + /* + * Generate a valid finger url + */ + + if(myURL->isEmpty() || !myURL->isValid() || + (myURL->user().isEmpty() && myURL->host().isEmpty())) + { + myURL->setProtocol("finger"); + myURL->setUser(""); + myURL->setHost("localhost"); + } + + /* + * If no specific port is specified, set it to 79. + */ + + if(myURL->port() == 0) { + myURL->setPort(79); + } + + /* + * If no refresh rate is given, set it to defaultRefreshRate + */ + + if (myURL->query().isEmpty()) { + myURL->setQuery("?refreshRate="+defaultRefreshRate); + } +} + +/* ---------------------------------------------------------------------------------- */ +#include "kio_finger.moc" +/* ---------------------------------------------------------------------------------- */ + diff --git a/kioslave/finger/kio_finger.css b/kioslave/finger/kio_finger.css new file mode 100644 index 000000000..06deb81aa --- /dev/null +++ b/kioslave/finger/kio_finger.css @@ -0,0 +1,69 @@ +BODY { + color: #FFFFCC; + background-color: #000000; + padding: 2em; + margin: auto; +} + +A:link {color: #82A7D0} +A:visited {color: #999999} +A:active {color: #999999} + +H1 { + color: #999999; + background-color: #000000; + font: 200% Helvetica, sans-serif; + font-variant: normal; + padding: 1em; + margin: auto; +} + +.mainTable { + background-color: #000000; + border: thin solid; + margin: auto; +} + + +.courierText { + color: #FFFFCC; + background-color: #000000; + font: 120% Courier, sans-serif; + font-variant: normal; + text-align: left; + padding: 0em; +} + +.commandText { + color: #FFFFCC; + background-color: #000000; + font: 120% Courier, sans-serif; + font-variant: normal; + text-align: center; + padding: 0.5em; +} + +.niceText { + color: #009999; + background-color: #000000; + font: 120% Arial, sans-serif; + font-variant: normal; + text-align: center; + padding: 0.5em; +} + +.finger { color: #82A7D0} +.domainName { color: #D0A000} +.ipNumber { color: #D0A000} +.os { color: #82A7D0} +.username { color: #82A7D0} +.directory { color: #D0A000} +.shell { color: #D0A000} +.notLoggedIn { color: #00A000} +.loggedIn { color: #B00000} +.newMail { color: #82A7D0} +.plan { color: #D0A000} +.noNewMail { color: #BB0000} +.noPlan { color: #BB0000} + + diff --git a/kioslave/finger/kio_finger.h b/kioslave/finger/kio_finger.h new file mode 100644 index 000000000..8d63236d4 --- /dev/null +++ b/kioslave/finger/kio_finger.h @@ -0,0 +1,64 @@ + +/*************************************************************************** + kio_finger.h - description + ------------------- + begin : Sun Aug 12 2000 + copyright : (C) 2000 by Andreas Schlapbach + email : schlpbch@iam.unibe.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program 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. * + * * + ***************************************************************************/ + + +#ifndef __kio_finger_h__ +#define __kio_finger_h__ + +#include <qstring.h> +#include <qcstring.h> + +#include <kurl.h> +#include <kprocess.h> +#include <kio/global.h> +#include <kio/slavebase.h> + +class FingerProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT + +public: + + FingerProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~FingerProtocol(); + + virtual void mimetype(const KURL& url); + virtual void get(const KURL& url); + +private slots: + void slotGetStdOutput(KProcess*, char*, int); + +private: + KURL *myURL; + + QString *myPerlPath; + QString *myFingerPath; + QString *myFingerPerlScript; + QString *myFingerCSSFile; + + QString *myStdStream; + + + KProcess *myKProcess; + + void getProgramPath(); + void parseCommandLine(const KURL& url); +}; + + +#endif diff --git a/kioslave/finger/kio_finger.pl b/kioslave/finger/kio_finger.pl new file mode 100644 index 000000000..8965ea523 --- /dev/null +++ b/kioslave/finger/kio_finger.pl @@ -0,0 +1,175 @@ +##!/usr/bin/perl +# +# Copyright Andreas Schlapbach, schlpbch@iam.unibe.ch, 2001 +# http://iamexwiwww.unibe.ch/studenten/kfinger +# +# Touch at your own risk. + + +# Highlight mail addresses or url + +$mails = '<A HREF="mailto:'; +$urls = '<A HREF="'; +$urlspace = '">'; +$urlend = '</A>'; + +# Highlight various information, configurable via the CSS file, + +$finger = '<CODE class="finger">'; +$domainName = '<CODE class="domainName">'; +$ipNumber = '<CODE class="ipNumber">'; +$os = '<CODE class="os">'; +$username = '<CODE class="username">'; +$directory = '<CODE class="directory">'; +$shell = '<CODE class="shell">'; +$notLoggedIn = '<CODE class="Login">'; +$loggedIn = '<CODE class="noLogin">'; +$newMail = '<CODE class="newMail">'; +$plan = '<CODE class="plan">'; +$noNewMail = '<CODE class="noNewMail">'; +$noPlan = '<CODE class="noPlan">'; +$close = '</CODE>'; + +# Those names get skipped, so if there's a user with such a name, bad luck. + +@keywords=('Welcome','Login','finger','No'); +$keywordlist = join '|', @keywords; + +$FINGERCMD = "$ARGV[0]"; # The complete path to the finger cmd +$CSSFILE = "$ARGV[1]"; # The complete path to the CSS file +$REFRESHRATE = "$ARGV[2]"; # The intervals in seconds until the page gets updated +$HOST = "$ARGV[3]"; # host name +$USER = "$ARGV[4]"; # user name + +$HOST =~ s/&/&/g; +$HOST =~ s/</</g; +$HOST =~ s/>/>/g; + +$USER =~ s/&/&/g; +$USER =~ s/</</g; +$USER =~ s/>/>/g; + +# HTML Header + +print <<HTMLHeader; +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> + <meta http-equiv="refresh" content="$REFRESHRATE"> + <TITLE>finger $USER\@$HOST</TITLE> + <LINK type="text/css" rel="stylesheet" href="file:$CSSFILE"> +</HEAD> +<BODY> + <TABLE class="mainTable" cellspacing="0"> + <TR> + <TH colspan="1"> + <H1>finger $USER\@$HOST</H1> + </TH> + </TR> + <TR> + <TH> + <TABLE class="courierText" cellpadding="0" cellspacing="2"> +HTMLHeader + +# Run finger command and save it into a buffer + +open(F, "-|") || exec $FINGERCMD, "$USER\@$HOST"; +@lines = <F>; +close(F); + +# Do highlighting using perl regular expressions on every line received. +# Order is important here. + +foreach $output (@lines) + { + $output =~ s/((\w)+\@((\w)+(.))*(\w)+)/$mails$1$urlspace$1$urlend/gi; # Highlight email address + $output =~ s/((http|ftp)(:\/\/)(\S)+)/$urls$1$urlspace$1$urlend/gi; # Highlight urls + $output =~ s/((\d)+\.(\d)+\.(\d)+\.(\d)+)/$ipNumber$1$close/gi; # Highlight IP number + $output =~ s/((\w)+\.(\w)+\.(\w|-)+\s)/$domainName$1$close/gi; # Highlight domain name (\s is important) + $output =~ s/(finger:)/$finger$1$close/gim; # Highlight finger + $output =~ s/(Linux)/$os$1$close/gim; # Highlight Linux + if ($USER) # is $USER nil ? + { + $output =~ s/^Login:\s*(\w*)/Login: $mails$1\@$HOST$urlspace$1$urlend/gi; + $output =~ s/^Login Name:\s*(\w*)/Login Name:$mails$1\@$HOST$urlspace$1$urlend/gi; + $output =~ s/Name:(((\s*)(\w+))+\n)/Name:$username$1$close\n/gi; # Linux + $output =~ s/In real life:(((\s*)(\w+))+\n)/In real life:$username$1$close\n/gi; # Solaris + $output =~ s/^Directory:((\s*)(\/(\w)+)+)/Directory:$directory$1$close/gi; # Highlight Directory + $output =~ s/Shell:((\s*)(\/(\w)+)+)/Shell:$shell$1$close/gi; # Highlight Shell + $output =~ s/(not presently logged)/$notLoggedIn$1$close/gi; + $output =~ s/con (\w*)/con $loggedIn$1$close/gi; + $output =~ s/^(New mail)/$newMail$1$close/gi; + $output =~ s/^(No mail.)/$noNewMail$1$close/gim; + $output =~ s/^(Plan:)/$plan$1$close/gi; + $output =~ s/^(No plan.)/$noPlan$1$close/gim; + } + else + { + $output =~ s/^(\w+)/$mails$1\@$HOST$urlspace$1$urlend/m unless ($output =~ m/$keywordlist/m); + } + # line consists of white space only? + if ($output =~ m/\S/gi) + { + print " <TR><TD><PRE>$output</PRE></TD></TR>\n"; + } + else + { + print " <TR><TD><PRE> </PRE></TD></TR>\n"; + } +} + +print " </TABLE>\n"; +print " </TH>\n"; + +# Finger-Talk options + +if ($USER) # is $USER nil ? +{ +print <<UserQuery; + </TR> + <TR> + <TH class="commandText" colspan="2"> + <A HREF='finger://$USER\@$HOST'>finger</A> + </TH> + </TR> +UserQuery +} +else +{ +print <<HostQueryHead; + <TH> + <TABLE class="courierText" cellpadding="0" cellspacing="2"> +HostQueryHead + + @lines = split /^/m, $buffer; + foreach $output2 (@lines) + { + if ($output2 =~ m/^(\w+)/gi and not ($output2 =~ m/$keywordlist/m)) + { + $USER = $&; + print " <TR><TD><PRE><A HREF='finger://$USER\@$HOST'>finger</A>\n</PRE></TD></TR>\n"; + # - <A HREF='talk://$USER\@$HOST'>talk</A>\n</PRE></TD></TR>\n"; + } + else + { + print " <TR><TD><PRE> </PRE></TD></TR>\n"; + } + } + +print <<HostQueryTail; + </TABLE> + </TH> + </TR> +HostQueryTail +} + +# HTMLTail + +print <<HTMLTail; + <TR> + <TH class="niceText">refresh rate: $REFRESHRATE seconds.</TH> + </TR> +</TABLE> +</BODY> +</HTML> +HTMLTail diff --git a/kioslave/fish/AUTHORS b/kioslave/fish/AUTHORS new file mode 100644 index 000000000..fc4f1567e --- /dev/null +++ b/kioslave/fish/AUTHORS @@ -0,0 +1 @@ +Jörg Walter <trouble@garni.ch> diff --git a/kioslave/fish/COPYING b/kioslave/fish/COPYING new file mode 100644 index 000000000..2d08eab44 --- /dev/null +++ b/kioslave/fish/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + This program 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 program 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 program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kioslave/fish/ChangeLog b/kioslave/fish/ChangeLog new file mode 100644 index 000000000..7f621f3d4 --- /dev/null +++ b/kioslave/fish/ChangeLog @@ -0,0 +1,71 @@ +1.2.3 by Willy De la Court <willy.delacourt@pandora.be> + Changes in the EXEC code as Jörg Walter proposed. + fixed 2 bugs when executing in Shell mode + +1.2.2 by Willy De la Court <willy.delacourt@pandora.be> + Security fix tempfile should not be world readable + bugfix write to the file not the command + +1.2.1 by Willy De la Court <willy.delacourt@pandora.be> + implemented su for fish://localhost/ + fish://root@localhost/ will su to root + fish://someuser@localhost/ will su to someuser + fish://localhost:22/ will still use ssh + strange problem with su when sending password need to wait a while (1 sec) + after reception of the password prompt. + some indentations fixed + i18n all messages + +1.2 by Willy De la Court <willy.delacourt@pandora.be> + implementation of the EXEC function + made sure all the VER lines where the same + used eval and system() for executing the command + Send the VER command directly after the FISH command. + After using kill to close the child process used wait to really make sure the child + has died. If the child took some time to die the select() + returns "Interrupted system call" error. This should solve some hanging problems. + added hasExec so it can be tested. + backport to BRANCH sendCommand(FISH_VER); and wait(NULL) to fix potential bugs. + +1.1.4 by Willy De la Court <willy.delacourt@pandora.be> + fixes Bug 49881: file time differs by 1 hour + and backported to BRANCH + +1.1.3 + removed compression option, which fixes 2 bugs: #45448 and an + untracked user report about ssh version misdetect; also, is + more consistent with overall design policy: leave connection + details to the ssh client + + fixed a bug which made lots of ssh zombie processes hang around + +1.1.2 + fixed a bug which made inserting shell args via fish:-URL possible + +1.1.1 + fixed shell mode symlink display + + made perl server compatible with 5.005 + +1.1 + added a perl server implementation which is transferred + and started automatically if perl is present + + added KDE3 support + + added support for commercial SSH + + modifed shell commands to use file(1)'s -i option (version + 3.37 and up) instead of local hack + + fixed an annoying bug with copying/moving dir trees + + fixed bug which made creating new files fail sometimes + + added support for changing the user name in the pass dialog + +1.0.1 + added #include <sys/resource.h> (needed on some platforms) + +1.0 + initial release diff --git a/kioslave/fish/FAQ b/kioslave/fish/FAQ new file mode 100644 index 000000000..dce0aef41 --- /dev/null +++ b/kioslave/fish/FAQ @@ -0,0 +1,37 @@ +Freqeuently Asked Questions, last updated for kio_fish 1.1 + +Q: Typing fish:/some.host.com does not work +A: It is fish://some.host.com (double slash) + +Q: How can I use a different port? +A: Use regular URL syntax: fish://some.host.com:2222 + +Q: Something isn't working. I get strange/no displays +A: Could be a bug, could be a problem with the remote + tools. Try having perl somewhere in the PATH on the + remote machine, that should work reliably. Shell- + only mode is prone to different tool's opinion about + parameters and what they mean. Shell-only mode is + thouroughly tested only on GNU tools, and has + superficial testing on BSD and Digital Unix. Solaris + seems to have problems. Any reports for shell mode on + non-GNU machines welcome (BTW, if you see a file + .fishsrv.pl in your remote home directory, fish did + use perl mode) + +Q: The connection stays open. How do I disconnect? +A: Just wait. The system default idle timeout is used. + (about a minute or so) + +Q: Why are there no icons? +A: With this release, you should have icons almost always, + but best results are obtained if you install a recent + version of the 'file' utility that supports the '-i' + option. + +Q: How do I specify which program to use for SSH? +A: Not at all, sorry. After evaluating other programs (rsh, + rlogin, telnet) I came to the conclusion it was way too + complex to support these, as only ssh supports both, + password authentication and automatic execution. + diff --git a/kioslave/fish/INSTALL b/kioslave/fish/INSTALL new file mode 100644 index 000000000..02a4a0740 --- /dev/null +++ b/kioslave/fish/INSTALL @@ -0,0 +1,167 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes a while. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Type `make install' to install the programs and any data files and + documentation. + + 4. You can remove the program binaries and object files from the + source code directory by typing `make clean'. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/kioslave/fish/Makefile.am b/kioslave/fish/Makefile.am new file mode 100644 index 000000000..27308245b --- /dev/null +++ b/kioslave/fish/Makefile.am @@ -0,0 +1,33 @@ +kde_module_LTLIBRARIES = kio_fish.la + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +kio_fish_la_SOURCES = fish.cpp +kio_fish_la_LIBADD = $(LIB_KSYCOCA) #$(LIBUTIL) +kio_fish_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = fishcode.h fish.h + +EXTRA_DIST = AUTHORS COPYING ChangeLog INSTALL README TODO FAQ fish.pl + +DISTCLEANFILES = fishcode.h + +kdelnk_DATA = fish.protocol nxfish.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO + +fish.lo: fishcode.h + +fishcode.h: fish.pl + SUM=`$(MD5SUM) $(srcdir)/fish.pl | cut -d ' ' $(MD5SUM_CUT)`; \ + echo '#define CHECKSUM "'$$SUM'"' > $@; \ + echo 'static const char *fishCode(' >> $@; \ + sed -e 's/\\/\\\\/g;s/"/\\"/g;s/^[ ]*/"/;/^"# /d;s/[ ]*$$/\\n"/;/^"\\n"$$/d;s/{CHECKSUM}/'$$SUM'/;' $(srcdir)/fish.pl >> $@; \ + echo ');' >> $@; + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_fish.pot + + + diff --git a/kioslave/fish/README b/kioslave/fish/README new file mode 100644 index 000000000..d1afdc3d1 --- /dev/null +++ b/kioslave/fish/README @@ -0,0 +1,258 @@ +Overview of kio_fish +==================== + + ------------------------------------------------------------------------ + NOTE FOR KDE2 USERS: This is the last release supporting KDE2. However, + you might need to modify Makefiles to get things installed into the + right directories. + ------------------------------------------------------------------------ + + FISH is a protocol to get filesystem access without special server + software, only using a remote shell. (Hence the name: FIles transferred + over SHell protocol). + It was first devised by Pavel Machek <pavel@bug.ucw.cz> and implemented + as a Midnight Commander vfs module in 1998. + + This is a complete client implementation using his original version + 0.0.2 protocol, extending it with 2 commands (which are only optional - + should a real FISH server exist on server side that doesn't understand + them, this ioslave still works, only slower). Moreover, this client does + complete shell metacharacter quoting on all arguments, a fact that is + neccessary but missing from the specs. + Extensions used are: append (APPEND command), copy (COPY command), + lscount (LIST first prints number of files to be listed), lslinks (LIST + shows symlink info instead of info about link targets), lsmime (LIST + determines the MIME type on the server side) + Password and host key queries are handled via dialog boxes. + The goal of this client is to make a remote directory look and feel exactly + like a local directory, with all comfort, only slower. + + NOTE: From version 1.1.3 on, compression is no longer turned on auto- + matically. You have to specify it via ~/.ssh/config or wherever + your local ssh client reads its settings. The same goes for all other + connection parameters. OpenSSH for example has a powerful configuration + file syntax which lets you configure access differently for each host, + something I do not intend to duplicate. Read the ssh_config(5) man page + for details. If someone knows the docs to read for commercial ssh please + tell me so I can include that here as well. + + Included below is the original posting from the mc mailing list archives. + + If perl is installed on the remote machine and in the default PATH, it will + be used to transfer a custom server script which is much faster than + shell-only mode and more predictable as well. The script is stored in a + file called .fishsrv.pl in the working directory directly after login and + will be reused on subsequent connections. + + 2001/10/07 Jörg Walter <trouble@garni.ch> + + + +From: Pavel Machek <pavel@bug.ucw.cz> +Subject: New virtual filesystem - fish +Date: Tue, 15 Sep 1998 22:30:07 +0200 + +Hi! + +New virtual filesystem has been created, which allows you to access +files on remote computer over rsh/ssh connection, with _no_ server +needed on the other side. To use it from mc or any program using +libvfs.so, do + +cd /#sh:user@host.to.connect.to/ + +Note that password authentication will not work, so you must be +authenticated using [rs]hosts or RSA key. + +For protocol, see mc/vfs/README.fish. If someone wants to write +server, it would be good idea, since it works without server but +performance is not optimal. + + Pavel + +PS: Protocol looks like this. If you have any comments, it is time to +speak. + + + FIles transferred over SHell protocol (V 0.0.2) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This protocol was designed for transferring files over secureshell +(ssh) connection. It can be as well used for transfers over rsh, and +there may be other uses. + +Client sends requests of following form: + +#FISH_COMMAND +equivalent shell commands, +which may be multiline + +Only fish commands are defined here, shell equivalents are for your +information only and will probably vary from implementation to +implementation. Fish commands always have priority: server is +expected to execute fish command if it understands it. If it does not, +however, it can try the luck and execute shell command. + +Server's reply is multiline, but alwyas ends with + +### 000<optional text> + +line. ### is prefix to mark this line, 000 is return code. Return +codes are superset to those used in ftp. + +There are few new exit codes defined: + +000 don't know; if there were no previous lines, this marks COMPLETE +success, if they were, it marks failure. + +001 don't know; if there were no previous lines, this marks +PRELIMinary success, if they were, it marks failure + + Connecting + ~~~~~~~~~~ +Client uses "echo FISH:;/bin/sh" as command executed on remote +machine. This should make it possible for server to distinguish FISH +connections from normal rsh/ssh. + + Commands + ~~~~~~~~ +#FISH +echo; start_fish_server; echo '### 200' + +This command is sent at the begining. It marks that client wishes to +talk via FISH protocol. #VER command must follow. If server +understands FISH protocol, it has option to put FISH server somewhere +on system path and name it start_fish_server. + +#VER 0.0.2 <feature1> <feature2> <...> +echo '### 000' + +This command is the second one. It sends client version and extensions +to the server. Server should reply with protocol version to be used, +and list of extensions accepted. + +VER 0.0.0 <feature2> +### 200 + +#PWD +pwd; echo '### 200' + +Server should reply with current directory (in form /abc/def/ghi) +followed by line indicating success. + +#LIST /directory +ls -lLa $1 | grep '^[^cbt]' | ( while read p x u g s m d y n; do echo "P$p $u.$g +S$s +d$m $d $y +:$n +"; done ) +ls -lLa $1 | grep '^[cb]' | ( while read p x u g a i m d y n; do echo "P$p $u.$g +E$a$i +dD$m $d $y +:$n +"; done ) +echo '### 200' + +This allows client to list directory or get status information about +single file. Output is in following form (any line except :<filename> +may be ommited): + +P<unix permissions> <owner>.<group> +S<size> +d<3-letters month name> <day> <year or HH:MM> +D<year> <month> <day> <hour> <minute> <second>[.1234] +E<major-of-device>,<minor> +:<filename> +L<filename symlink points to> +<blank line to separate items> + +Unix permissions are of form X--------- where X is type of +file. Currently, '-' means regular file, 'd' means directory, 'c', 'b' +means character and block device, 'l' means symbolic link, 'p' means +FIFO and 's' means socket. + +'d' has three fields: month (one of strings Jan Feb Mar Apr May Jun +Jul Aug Sep Oct Nov Dec), day of month, and third is either single +number indicating year, or HH:MM field (assume current year in such +case). As you've probably noticed, this is pretty broken; it is for +compatibility with ls listing. + +#RETR /some/name +ls -l /some/name | ( read a b c d x e; echo $x ); echo '### 100'; cat /some/name; echo '### 200' + +Server sends line with filesize on it, followed by line with ### 100 +indicating partial success, then it sends binary data (exactly +filesize bytes) and follows them with (with no preceeding newline) ### +200. + +Note that there's no way to abort running RETR command - except +closing the connection. + +#STOR <size> /file/name +<i><font color="#008000">> /file/name; echo '### 001'; ( dd bs=4096 count=<size/4096>; dd bs=<size%4096> count=1 ) 2>/dev/null | ( cat > %s; cat > /dev/null ); echo '### 200' +</font></i> +This command is for storing /file/name, which is exactly size bytes +big. You probably think I went crazy. Well, I did not: that strange +cat > /dev/null has purpose to discard any extra data which was not +written to disk (due to for example out of space condition). + +[Why? Imagine uploading file with "rm -rf /" line in it.] + +#CWD /somewhere +cd /somewhere; echo '### 000' + +It is specified here, but I'm not sure how wise idea is to use this +one: it breaks stateless-ness of the protocol. + +Following commands should be rather self-explanatory: + +#CHMOD 1234 file +chmod 1234 file; echo '### 000' + +#DELE /some/path +rm -f /some/path; echo '### 000' + +#MKD /some/path +mkdir /some/path; echo '### 000' + +#RMD /some/path +rmdir /some/path; echo '### 000' + +#RENAME /path/a /path/b +mv /path/a /path/b; echo '### 000' + +#LINK /path/a /path/b +ln /path/a /path/b; echo '### 000' + +#SYMLINK /path/a /path/b +ln -s /path/a /path/b; echo '### 000' + +#CHOWN user /file/name +chown user /file/name; echo '### 000' + +#CHGRP group /file/name +chgrp group /file/name; echo '### 000' + +#READ <offset> <size> /path/and/filename +cat /path/and/filename | ( dd bs=4096 count=<offset/4096> > /dev/null; +dd bs=<offset%4096> count=1 > /dev/null; +dd bs=4096 count=<offset/4096>; +dd bs=<offset%4096> count=1; ) + +Returns ### 200 on successfull exit, ### 291 on successfull exit when +reading ended at eof, ### 292 on successfull exit when reading did not +end at eof. + +#WRITE <offset> <size> /path/and/filename + +Hmm, shall we define these ones if we know our client is not going to +use them? + + +That's all, folks! + pavel@ucw.cz + + +-- +I'm really pavel@atrey.karlin.mff.cuni.cz. Pavel +Look at http://atrey.karlin.mff.cuni.cz/~pavel/ ;-). diff --git a/kioslave/fish/TODO b/kioslave/fish/TODO new file mode 100644 index 000000000..ba3bf69bb --- /dev/null +++ b/kioslave/fish/TODO @@ -0,0 +1,10 @@ +L resume (could be very slow in shell mode due to WRITE being slow) +L other remote shells (rlogin, telnet) - difficult, would need a shell prompt detector which is impossible to get 100% accurate. Contributions welcome. +L show host list when called as fish:// +L plug into sidebar, show directory tree there +M employ locking to only show one password dialog, so that loading many files at once from the same host would use a cahced password instead of opening tons of dialog boxes +M more meaningful error messages (need perl server first) +H use rsync or a similar technique (if possible at all) +M proxying via intermediate ssh account +H make it work with charsets other than latin1 + diff --git a/kioslave/fish/configure.in.in b/kioslave/fish/configure.in.in new file mode 100644 index 000000000..086dc0dae --- /dev/null +++ b/kioslave/fish/configure.in.in @@ -0,0 +1,9 @@ +CFLAGS="$CFLAGS -D_GNU_SOURCE" +CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE" + +AC_CHECK_HEADERS(termios.h pty.h libutil.h util.h sys/types.h sys/ioctl.h stropts.h) + +kde_save_LIBS="$LIBS" +LIBS="$LIBS $LIBUTIL" +AC_CHECK_FUNCS(getpt openpty isastream) +LIBS="$kde_save_LIBS" diff --git a/kioslave/fish/fish.cpp b/kioslave/fish/fish.cpp new file mode 100644 index 000000000..3967bcd6b --- /dev/null +++ b/kioslave/fish/fish.cpp @@ -0,0 +1,1661 @@ +/*************************************************************************** + fish.cpp - a FISH kioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001-2003 by J�rg Walter + email : jwalt-kde@garni.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program 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, version 2 of the License * + * * + ***************************************************************************/ + +/* + This code contains fragments and ideas from the ftp kioslave + done by David Faure <faure@kde.org>. + + Structure is a bit complicated, since I made the mistake to use + KProcess... now there is a lightweight homebrew async IO system + inside, but if signals/slots become available for ioslaves, switching + back to KProcess should be easy. +*/ + +#include "config.h" + +#include <qcstring.h> +#include <qfile.h> +#include <qsocket.h> +#include <qdatetime.h> +#include <qbitarray.h> +#include <qregexp.h> + +#include <stdlib.h> +#ifdef HAVE_PTY_H +#include <pty.h> +#endif +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif +#include <math.h> +#include <unistd.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#ifdef HAVE_STROPTS +#include <stropts.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#ifdef HAVE_UTIL_H +#include <util.h> +#endif + +#include <kdebug.h> +#include <kmessagebox.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kremoteencoding.h> +#include <kurl.h> +#include <ksock.h> +#include <stdarg.h> +#include <time.h> +#include <sys/stat.h> +#include <kmimetype.h> +#include <kmimemagic.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <errno.h> +#include <sys/resource.h> + +#include "fish.h" +#include "fishcode.h" + +#ifndef NDEBUG +#define myDebug(x) kdDebug(7127) << __LINE__ << ": " x +#define connected() do{myDebug( << "_______ emitting connected()" << endl); connected();}while(0) +#define dataReq() do{myDebug( << "_______ emitting dataReq()" << endl); dataReq();}while(0) +#define needSubURLData() do{myDebug( << "_______ emitting needSubURLData()" << endl); needSubURLData();}while(0) +#define slaveStatus(x,y) do{myDebug( << "_______ emitting slaveStatus(" << x << ", " << y << ")" << endl); slaveStatus(x,y);}while(0) +#define statEntry(x) do{myDebug( << "_______ emitting statEntry("<<x.size()<<")" << endl); statEntry(x);}while(0) +#define listEntries(x) do{myDebug( << "_______ emitting listEntries(...)" << endl); listEntries(x);}while(0) +#define canResume(x) do{myDebug( << "_______ emitting canResume("<<(int)x<<")" << endl); canResume(x);}while(0) +#define totalSize(x) do{myDebug( << "_______ emitting totalSize("<<(int)x<<")" << endl); totalSize(x);}while(0) +#define processedSize(x) do{myDebug( << "_______ emitting processedSize("<<x<<")" << endl); processedSize(x);}while(0) +#define speed(x) do{myDebug( << "_______ emitting speed("<<(int)x<<")" << endl); speed(x);}while(0) +#define redirection(x) do{myDebug( << "_______ emitting redirection("<<x<<")" << endl); redirection(x);}while(0) +#define errorPage() do{myDebug( << "_______ emitting errorPage()" << endl); errorPage();}while(0) +#define sendmimeType(x) do{myDebug( << "_______ emitting mimeType("<<x<<")" << endl); mimeType(x);}while(0) +#define warning(x) do{myDebug( << "_______ emitting warning("<<x<<")" << endl); warning(x);}while(0) +#define infoMessage(x) do{myDebug( << "_______ emitting infoMessage("<<x<<")" << endl); infoMessage(x);}while(0) +#else +#define myDebug(x) +#define sendmimeType(x) mimeType(x) +#endif + +static char *sshPath = NULL; +static char *suPath = NULL; +// disabled: currently not needed. Didn't work reliably. +// static int isOpenSSH = 0; + +static int isNXFish = 0; + +#define E(x) ((const char*)remoteEncoding()->encode(x).data()) + +using namespace KIO; +extern "C" { + +static void ripper(int) +{ + while (waitpid(-1,0,WNOHANG) > 0) { + // do nothing, go on + } +} + +int KDE_EXPORT kdemain( int argc, char **argv ) +{ + KLocale::setMainCatalogue("kio_fish"); + KInstance instance("fish"); + + myDebug( << "*** Starting fish " << endl); + if (argc != 4) { + myDebug( << "Usage: fish protocol domain-socket1 domain-socket2" << endl); + exit(-1); + } + + setenv("TZ", "UTC", true); + + struct sigaction act; + memset(&act,0,sizeof(act)); + act.sa_handler = ripper; + act.sa_flags = 0 +#ifdef SA_NOCLDSTOP + | SA_NOCLDSTOP +#endif +#ifdef SA_RESTART + | SA_RESTART +#endif + ; + sigaction(SIGCHLD,&act,NULL); + + if (qstrcmp(argv[1],"nxfish")==0) { + // Set NXFish - Mode + isNXFish=1; + } + + fishProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + myDebug( << "*** fish Done" << endl); + return 0; +} + +} + +const struct fishProtocol::fish_info fishProtocol::fishInfo[] = { + { ("FISH"), 0, + ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM " 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while(<STDIN>) { last if /^__END__/; $code.=$_; } exit(eval($code));' 2>/dev/null;"), + 1 }, + { ("VER 0.0.3 copy append lscount lslinks lsmime exec stat"), 0, + ("echo 'VER 0.0.3 copy append lscount lslinks lsmime exec stat'"), + 1 }, + { ("PWD"), 0, + ("pwd"), + 1 }, + { ("LIST"), 1, + ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -Lla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" + "ls -Lla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"), + 0 }, + { ("STAT"), 1, + ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -dLla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" + "ls -dLla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"), + 0 }, + { ("RETR"), 1, + ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"), + 1 }, + { ("STOR"), 2, + ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;" + "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"), + 0 }, + { ("CWD"), 1, + ("cd %1"), + 0 }, + { ("CHMOD"), 2, + ("chmod %1 %2"), + 0 }, + { ("DELE"), 1, + ("rm -f %1"), + 0 }, + { ("MKD"), 1, + ("mkdir %1"), + 0 }, + { ("RMD"), 1, + ("rmdir %1"), + 0 }, + { ("RENAME"), 2, + ("mv -f %1 %2"), + 0 }, + { ("LINK"), 2, + ("ln -f %1 %2"), + 0 }, + { ("SYMLINK"), 2, + ("ln -sf %1 %2"), + 0 }, + { ("CHOWN"), 2, + ("chown %1 %2"), + 0 }, + { ("CHGRP"), 2, + ("chgrp %1 %2"), + 0 }, + { ("READ"), 3, + ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;" + "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;" + "dd bs=%2 count=1; ) 2>/dev/null;"), + 0 }, + // Yes, this is "ibs=1", since dd "count" is input blocks. + // On network connections, read() may not fill the buffer + // completely (no more data immediately available), but dd + // does ignore that fact by design. Sorry, writes are slow. + // OTOH, WRITE is not used by the current ioslave methods, + // we use APPEND. + { ("WRITE"), 3, + (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | " + "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"), + 0 }, + { ("COPY"), 2, + ("if [ -L %1 ]; then if cp -pdf %1 %2 2>/dev/null; then :; else LINK=\"`readlink %1`\"; ln -sf $LINK %2; fi; else cp -pf %1 %2; fi"), + 0 }, + { ("APPEND"), 2, + (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"), + 0 }, + { ("EXEC"), 2, + ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"), + 0 } +}; + +fishProtocol::fishProtocol(const QCString &pool_socket, const QCString &app_socket) + : SlaveBase("fish", pool_socket, app_socket), mimeBuffer(1024), + mimeTypeSent(false) +{ + myDebug( << "fishProtocol::fishProtocol()" << endl); + if (sshPath == NULL) { + // disabled: currently not needed. Didn't work reliably. + // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null"); + if (isNXFish) + sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("nxfish"))); + else + sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("ssh"))); + } + if (suPath == NULL) { + suPath = strdup(QFile::encodeName(KStandardDirs::findExe("su"))); + } + childPid = 0; + connectionPort = 0; + isLoggedIn = false; + writeReady = true; + isRunning = false; + firstLogin = true; + errorCount = 0; + rawRead = 0; + rawWrite = -1; + recvLen = -1; + sendLen = -1; + setMultipleAuthCaching( true ); + connectionAuth.keepPassword = true; + connectionAuth.url.setProtocol("fish"); + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + typeAtom.m_uds = UDS_FILE_TYPE; + typeAtom.m_long = 0; + mimeAtom.m_uds = UDS_MIME_TYPE; + mimeAtom.m_long = 0; + mimeAtom.m_str = QString::null; + + hasAppend = false; + + isStat = false; // FIXME: just a workaround for konq deficiencies + redirectUser = ""; // FIXME: just a workaround for konq deficiencies + redirectPass = ""; // FIXME: just a workaround for konq deficiencies + fishCodeLen = strlen(fishCode); +} +/* ---------------------------------------------------------------------------------- */ + + +fishProtocol::~fishProtocol() +{ + myDebug( << "fishProtocol::~fishProtocol()" << endl); + shutdownConnection(true); +} + +/* --------------------------------------------------------------------------- */ + +/** +Connects to a server and logs us in via SSH. Then starts FISH protocol. +*/ +void fishProtocol::openConnection() { + if (childPid) return; + + if (connectionHost.isEmpty() && !isNXFish) + { + error( KIO::ERR_UNKNOWN_HOST, QString::null ); + return; + } + + infoMessage(i18n("Connecting...")); + + myDebug( << "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort << endl); + sendCommand(FISH_FISH); + sendCommand(FISH_VER); + if (connectionStart()) { + error(ERR_COULD_NOT_CONNECT,connectionHost); + shutdownConnection(); + return; + }; + myDebug( << "subprocess is running" << endl); +} + +static int open_pty_pair(int fd[2]) +{ +#if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY) +/** with kind regards to The GNU C Library +Reference Manual for Version 2.2.x of the GNU C Library */ + int master, slave; + char *name; + struct ::termios ti; + memset(&ti,0,sizeof(ti)); + + ti.c_cflag = CLOCAL|CREAD|CS8; + ti.c_cc[VMIN] = 1; + +#ifdef HAVE_GETPT + master = getpt(); +#else + master = open("/dev/ptmx", O_RDWR); +#endif + if (master < 0) return 0; + + if (grantpt(master) < 0 || unlockpt(master) < 0) goto close_master; + + name = ptsname(master); + if (name == NULL) goto close_master; + + slave = open(name, O_RDWR); + if (slave == -1) goto close_master; + +#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) + if (isastream(slave) && + (ioctl(slave, I_PUSH, "ptem") < 0 || + ioctl(slave, I_PUSH, "ldterm") < 0)) + goto close_slave; +#endif + + tcsetattr(slave, TCSANOW, &ti); + fd[0] = master; + fd[1] = slave; + return 0; + +#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) +close_slave: +#endif + close(slave); + +close_master: + close(master); + return -1; +#else +#ifdef HAVE_OPENPTY + struct ::termios ti; + memset(&ti,0,sizeof(ti)); + + ti.c_cflag = CLOCAL|CREAD|CS8; + ti.c_cc[VMIN] = 1; + + return openpty(fd,fd+1,NULL,&ti,NULL); +#else +#ifdef __GNUC__ +#warning "No tty support available. Password dialog won't work." +#endif + return socketpair(PF_UNIX,SOCK_STREAM,0,fd); +#endif +#endif +} +/** +creates the subprocess +*/ +bool fishProtocol::connectionStart() { + int fd[2]; + int rc, flags; + thisFn = QString::null; + + rc = open_pty_pair(fd); + if (rc == -1) { + myDebug( << "socketpair failed, error: " << strerror(errno) << endl); + return true; + } + + if (!requestNetwork()) return true; + myDebug( << "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser << endl); + childPid = fork(); + if (childPid == -1) { + myDebug( << "fork failed, error: " << strerror(errno) << endl); + close(fd[0]); + close(fd[1]); + childPid = 0; + dropNetwork(); + return true; + } + if (childPid == 0) { + // taken from konsole, see TEPty.C for details + // note: if we're running on socket pairs, + // this will fail, but thats what we expect + + for (int sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL); + + struct rlimit rlp; + getrlimit(RLIMIT_NOFILE, &rlp); + for (int i = 0; i < (int)rlp.rlim_cur; i++) + if (i != fd[1]) close(i); + + dup2(fd[1],0); + dup2(fd[1],1); + dup2(fd[1],2); + if (fd[1] > 2) close(fd[1]); + + setsid(); + +#if defined(TIOCSCTTY) + ioctl(0, TIOCSCTTY, 0); +#endif + + int pgrp = getpid(); +#if defined( _AIX) || defined( __hpux) + tcsetpgrp(0, pgrp); +#else + ioctl(0, TIOCSPGRP, (char *)&pgrp); +#endif + + const char *dev = ttyname(0); + setpgid(0,0); + if (dev) close(open(dev, O_WRONLY, 0)); + setpgid(0,0); + + if (local) { + execl(suPath, "su", "-", connectionUser.latin1(), "-c", "cd ~;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0); + } else { + #define common_args "-l", connectionUser.latin1(), "-x", "-e", "none", \ + "-q", connectionHost.latin1(), \ + "echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0 + // disabled: leave compression up to the client. + // (isOpenSSH?"-C":"+C"), + + if (connectionPort) + execl(sshPath, "ssh", "-p", QString::number(connectionPort).latin1(), common_args); + else + execl(sshPath, "ssh", common_args); + #undef common_args + } + myDebug( << "could not exec! " << strerror(errno) << endl); + ::exit(-1); + } + close(fd[1]); + rc = fcntl(fd[0],F_GETFL,&flags); + rc = fcntl(fd[0],F_SETFL,flags|O_NONBLOCK); + childFd = fd[0]; + + fd_set rfds, wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + char buf[32768]; + int offset = 0; + while (!isLoggedIn) { + FD_SET(childFd,&rfds); + FD_ZERO(&wfds); + if (outBufPos >= 0) FD_SET(childFd,&wfds); + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + rc = select(childFd+1, &rfds, &wfds, NULL, &timeout); + if (rc < 0) { + if (errno == EINTR) + continue; + myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl); + return true; + } + if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) { + if (outBuf) rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos); + else rc = 0; + if (rc >= 0) outBufPos += rc; + else { + if (errno == EINTR) + continue; + myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl); + outBufPos = -1; + //return true; + } + if (outBufPos >= outBufLen) { + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + } + } + if (FD_ISSET(childFd,&rfds)) { + rc = read(childFd,buf+offset,32768-offset); + if (rc > 0) { + int noff = establishConnection(buf,rc+offset); + if (noff < 0) return false; + if (noff > 0) memmove(buf,buf+offset+rc-noff,noff); + offset = noff; + } else { + if (errno == EINTR) + continue; + myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl); + return true; + } + } + } + return false; +} + +/** +writes one chunk of data to stdin of child process +*/ +void fishProtocol::writeChild(const char *buf, KIO::fileoffset_t len) { + if (outBufPos >= 0 && outBuf) { +#if 0 + QString debug; + debug.setLatin1(outBuf,outBufLen); + if (len > 0) myDebug( << "write request while old one is pending, throwing away input (" << outBufLen << "," << outBufPos << "," << debug.left(10) << "...)" << endl); +#endif + return; + } + outBuf = buf; + outBufPos = 0; + outBufLen = len; +} + +/** +manages initial communication setup including password queries +*/ +int fishProtocol::establishConnection(char *buffer, KIO::fileoffset_t len) { + QString buf; + buf.setLatin1(buffer,len); + int pos; + // Strip trailing whitespace + while (buf.length() && (buf[buf.length()-1] == ' ')) + buf.truncate(buf.length()-1); + + myDebug( << "establishing: got " << buf << endl); + while (childPid && ((pos = buf.find('\n')) >= 0 || + buf.endsWith(":") || buf.endsWith("?"))) { + pos++; + QString str = buf.left(pos); + buf = buf.mid(pos); + if (str == "\n") + continue; + if (str == "FISH:\n") { + thisFn = QString::null; + infoMessage(i18n("Initiating protocol...")); + if (!connectionAuth.password.isEmpty()) { + connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length()-1); + cacheAuthentication(connectionAuth); + } + isLoggedIn = true; + return 0; + } else if (!str.isEmpty()) { + thisFn += str; + } else if (buf.endsWith(":")) { + if (!redirectUser.isEmpty() && connectionUser != redirectUser) { + KURL dest = url; + dest.setUser(redirectUser); + dest.setPass(redirectPass); + redirection(dest); + commandList.clear(); + commandCodes.clear(); + finished(); + redirectUser = ""; + redirectPass = ""; + return -1; + } else if (!connectionPassword.isEmpty()) { + myDebug( << "sending cpass" << endl); + connectionAuth.password = connectionPassword+"\n"; + connectionPassword = QString::null; + // su does not like receiving a password directly after sending + // the password prompt so we wait a while. + if (local) + sleep(1); + writeChild(connectionAuth.password.latin1(),connectionAuth.password.length()); + } else { + myDebug( << "sending mpass" << endl); + connectionAuth.prompt = thisFn+buf; + if (local) + connectionAuth.caption = i18n("Local Login") + " - " + url.user() + "@" + url.host(); + else + connectionAuth.caption = i18n("SSH Authorization") + " - " + url.user() + "@" + url.host(); + if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) { + connectionAuth.password = QString::null; // don't prefill + if ( !openPassDlg(connectionAuth)) { + error(ERR_USER_CANCELED,connectionHost); + shutdownConnection(); + return -1; + } + } + firstLogin = false; + connectionAuth.password += "\n"; + if (connectionAuth.username != connectionUser) { + KURL dest = url; + dest.setUser(connectionAuth.username); + dest.setPass(connectionAuth.password); + redirection(dest); + if (isStat) { // FIXME: just a workaround for konq deficiencies + redirectUser = connectionAuth.username; + redirectPass = connectionAuth.password; + } + commandList.clear(); + commandCodes.clear(); + finished(); + return -1; + } + myDebug( << "sending pass" << endl); + if (local) + sleep(1); + writeChild(connectionAuth.password.latin1(),connectionAuth.password.length()); + } + thisFn = QString::null; + return 0; + } else if (buf.endsWith("?")) { + int rc = messageBox(QuestionYesNo,thisFn+buf); + if (rc == KMessageBox::Yes) { + writeChild("yes\n",4); + } else { + writeChild("no\n",3); + } + thisFn = QString::null; + return 0; + } else { + myDebug( << "unmatched case in initial handling! shouldn't happen!" << endl); + } + } + return buf.length(); +} +/** +sets connection information for subsequent commands +*/ +void fishProtocol::setHost(const QString & host, int port, const QString & u, const QString & pass){ + QString user(u); + + if (isNXFish) + local = 0; + else + local = (host == "localhost" && port == 0); + + if (port <= 0) port = 0; + if (user.isEmpty()) user = getenv("LOGNAME"); + + if (host == connectionHost && port == connectionPort && user == connectionUser) + return; + myDebug( << "setHost " << u << "@" << host << endl); + + if (childPid) shutdownConnection(); + + connectionHost = host; + connectionAuth.url.setHost(host); + + connectionUser = user; + connectionAuth.username = user; + connectionAuth.url.setUser(user); + + connectionPort = port; + connectionPassword = pass; + firstLogin = true; +} + +/** +Forced close of the connection + +This function gets called from the application side of the universe, +it shouldn't send any response. + */ +void fishProtocol::closeConnection(){ + myDebug( << "closeConnection()" << endl); + shutdownConnection(true); +} + +/** +Closes the connection + */ +void fishProtocol::shutdownConnection(bool forced){ + if (childPid) { + kill(childPid,SIGTERM); // We may not have permission... + childPid = 0; + close(childFd); // ...in which case this should do the trick + childFd = -1; + if (!forced) + { + dropNetwork(); + infoMessage(i18n("Disconnected.")); + } + } + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + qlist.clear(); + commandList.clear(); + commandCodes.clear(); + isLoggedIn = false; + writeReady = true; + isRunning = false; + rawRead = 0; + rawWrite = -1; + recvLen = -1; + sendLen = -1; +} +/** +builds each FISH request and sets the error counter +*/ +bool fishProtocol::sendCommand(fish_command_type cmd, ...) { + const fish_info &info = fishInfo[cmd]; + myDebug( << "queueing: cmd="<< cmd << "['" << info.command << "'](" << info.params <<"), alt=['" << info.alt << "'], lines=" << info.lines << endl); + + va_list list; + va_start(list, cmd); + QString realCmd = info.command; + QString realAlt = info.alt; + static QRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]"); + for (int i = 0; i < info.params; i++) { + QString arg(va_arg(list, const char *)); + int pos = -2; + while ((pos = rx.search(arg,pos+2)) >= 0) { + arg.replace(pos,0,QString("\\")); + } + //myDebug( << "arg " << i << ": " << arg << endl); + realCmd.append(" ").append(arg); + realAlt.replace(QRegExp("%"+QString::number(i+1)),arg); + } + QString s("#"); + s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 000'\n"); + if (realCmd == "FISH") + s.prepend(" "); + commandList.append(s); + commandCodes.append(cmd); + return true; +} + +/** +checks response string for result code, converting 000 and 001 appropriately +*/ +int fishProtocol::handleResponse(const QString &str){ + myDebug( << "handling: " << str << endl); + if (str.startsWith("### ")) { + bool isOk = false; + int result = str.mid(4,3).toInt(&isOk); + if (!isOk) result = 500; + if (result == 0) result = (errorCount != 0?500:200); + if (result == 1) result = (errorCount != 0?500:100); + myDebug( << "result: " << result << ", errorCount: " << errorCount << endl); + return result; + } else { + errorCount++; + return 0; + } +} + +int fishProtocol::makeTimeFromLs(const QString &monthStr, const QString &dayStr, const QString &timeyearStr) +{ + QDateTime dt(QDate::currentDate(Qt::UTC)); + int year = dt.date().year(); + int month = dt.date().month(); + int currentMonth = month; + int day = dayStr.toInt(); + + static const char * const monthNames[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + for (int i=0; i < 12; i++) if (monthStr.startsWith(monthNames[i])) { + month = i+1; + break; + } + + int pos = timeyearStr.find(':'); + if (timeyearStr.length() == 4 && pos == -1) { + year = timeyearStr.toInt(); + } else if (pos == -1) { + return 0; + } else { + if (month > currentMonth + 1) year--; + dt.time().setHMS(timeyearStr.left(pos).toInt(),timeyearStr.mid(pos+1).toInt(),0); + } + dt.date().setYMD(year,month,day); + + return dt.toTime_t(); +} + +/** +parses response from server and acts accordingly +*/ +void fishProtocol::manageConnection(const QString &l) { + QString line(l); + int rc = handleResponse(line); + UDSAtom atom; + QDateTime dt; + KIO::filesize_t fsize; + int pos, pos2, pos3; + bool isOk = false; + if (!rc) { + switch (fishCommand) { + case FISH_VER: + if (line.startsWith("VER 0.0.3")) { + line.append(" "); + hasAppend = line.contains(" append "); + } else { + error(ERR_UNSUPPORTED_PROTOCOL,line); + shutdownConnection(); + } + break; + case FISH_PWD: + url.setPath(line); + redirection(url); + break; + case FISH_LIST: + myDebug( << "listReason: " << listReason << endl); + /* Fall through */ + case FISH_STAT: + if (line.length() > 0) { + switch (line[0].cell()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + fsize = line.toULongLong(&isOk); + if (fsize > 0 && isOk) errorCount--; + if ((fishCommand == FISH_LIST) && (listReason == LIST)) + totalSize(fsize); + break; + + case 'P': + errorCount--; + if (line[1] == 'd') { + mimeAtom.m_str = "inode/directory"; + typeAtom.m_long = S_IFDIR; + } else { + if (line[1] == '-') { + typeAtom.m_long = S_IFREG; + } else if (line[1] == 'l') { + typeAtom.m_long = S_IFLNK; + } else if (line[1] == 'c') { + typeAtom.m_long = S_IFCHR; + } else if (line[1] == 'b') { + typeAtom.m_long = S_IFBLK; + } else if (line[1] == 's') { + typeAtom.m_long = S_IFSOCK; + } else if (line[1] == 'p') { + typeAtom.m_long = S_IFIFO; + } else { + myDebug( << "unknown file type: " << line[1].cell() << endl); + errorCount++; + break; + } + } + //myDebug( << "file type: " << atom.m_long << endl); + //udsEntry.append(atom); + + atom.m_uds = UDS_ACCESS; + atom.m_long = 0; + if (line[2] == 'r') atom.m_long |= S_IRUSR; + if (line[3] == 'w') atom.m_long |= S_IWUSR; + if (line[4] == 'x' || line[4] == 's') atom.m_long |= S_IXUSR; + if (line[4] == 'S' || line[4] == 's') atom.m_long |= S_ISUID; + if (line[5] == 'r') atom.m_long |= S_IRGRP; + if (line[6] == 'w') atom.m_long |= S_IWGRP; + if (line[7] == 'x' || line[7] == 's') atom.m_long |= S_IXGRP; + if (line[7] == 'S' || line[7] == 's') atom.m_long |= S_ISGID; + if (line[8] == 'r') atom.m_long |= S_IROTH; + if (line[9] == 'w') atom.m_long |= S_IWOTH; + if (line[10] == 'x' || line[10] == 't') atom.m_long |= S_IXOTH; + if (line[10] == 'T' || line[10] == 't') atom.m_long |= S_ISVTX; + udsEntry.append(atom); + + atom.m_uds = UDS_USER; + atom.m_long = 0; + pos = line.find('.',12); + if (pos < 0) { + errorCount++; + break; + } + atom.m_str = line.mid(12,pos-12); + udsEntry.append(atom); + + atom.m_uds = UDS_GROUP; + atom.m_long = 0; + atom.m_str = line.mid(pos+1); + udsEntry.append(atom); + break; + + case 'd': + atom.m_uds = UDS_MODIFICATION_TIME; + pos = line.find(' '); + pos2 = line.find(' ',pos+1); + if (pos < 0 || pos2 < 0) break; + errorCount--; + atom.m_long = makeTimeFromLs(line.mid(1,pos-1), line.mid(pos+1,pos2-pos), line.mid(pos2+1)); + udsEntry.append(atom); + break; + + case 'D': + atom.m_uds = UDS_MODIFICATION_TIME; + pos = line.find(' '); + pos2 = line.find(' ',pos+1); + pos3 = line.find(' ',pos2+1); + if (pos < 0 || pos2 < 0 || pos3 < 0) break; + dt.setDate(QDate(line.mid(1,pos-1).toInt(),line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt())); + pos = pos3; + pos2 = line.find(' ',pos+1); + pos3 = line.find(' ',pos2+1); + if (pos < 0 || pos2 < 0 || pos3 < 0) break; + dt.setTime(QTime(line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt(),line.mid(pos3+1).toInt())); + errorCount--; + atom.m_long = dt.toTime_t(); + udsEntry.append(atom); + break; + + case 'S': + atom.m_uds = UDS_SIZE; + atom.m_long = line.mid(1).toULongLong(&isOk); + if (!isOk) break; + errorCount--; + udsEntry.append(atom); + break; + + case 'E': + errorCount--; + break; + + case ':': + atom.m_uds = UDS_NAME; + atom.m_long = 0; + pos = line.findRev('/'); + atom.m_str = thisFn = line.mid(pos < 0?1:pos+1); + if (fishCommand == FISH_LIST) + udsEntry.append(atom); + // By default, the mimetype comes from the extension + // We'll use the file(1) result only as fallback [like the rest of KDE does] + { + KURL kurl("fish://host/"); + kurl.setFileName(thisFn); // properly encode special chars + KMimeType::Ptr mime = KMimeType::findByURL(kurl); + if ( mime->name() != KMimeType::defaultMimeType() ) + mimeAtom.m_str = mime->name(); + } + errorCount--; + break; + + case 'M': { + QString type = line.mid(1); + + // First thing's first. If remote says this is a directory, throw out any + // name-based file type guesses. + if (type == "inode/directory" && mimeAtom.m_str != type) { + mimeAtom.m_str = type; + typeAtom.m_long = S_IFDIR; + } + // This is getting ugly. file(1) makes some uneducated + // guesses, so we must try to ignore them (#51274) + else if (mimeAtom.m_str.isEmpty() && line.right(8) != "/unknown" && + (thisFn.find('.') < 0 || (line.left(8) != "Mtext/x-" + && line != "Mtext/plain"))) { + mimeAtom.m_str = type; + } + errorCount--; + break; + } + + case 'L': + atom.m_uds = UDS_LINK_DEST; + atom.m_long = 0; + atom.m_str = line.mid(1); + udsEntry.append(atom); + if (!typeAtom.m_long) typeAtom.m_long = S_IFLNK; + errorCount--; + break; + } + } else { + if (!mimeAtom.m_str.isNull()) + udsEntry.append(mimeAtom); + mimeAtom.m_str = QString::null; + + udsEntry.append(typeAtom); + typeAtom.m_long = 0; + + if (fishCommand == FISH_STAT) + udsStatEntry = udsEntry; + else if (listReason == LIST) { + listEntry(udsEntry, false); //1 + } else if (listReason == CHECK) checkExist = true; //0 + errorCount--; + udsEntry.clear(); + } + break; + + case FISH_RETR: + if (line.length() == 0) { + error(ERR_IS_DIRECTORY,url.prettyURL()); + recvLen = 0; + break; + } + recvLen = line.toLongLong(&isOk); + if (!isOk) { + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + break; + } + break; + default : break; + } + + } else if (rc == 100) { + switch (fishCommand) { + case FISH_FISH: + writeChild(fishCode, fishCodeLen); + break; + case FISH_READ: + recvLen = 1024; + /* fall through */ + case FISH_RETR: + myDebug( << "reading " << recvLen << endl); + if (recvLen == -1) { + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + } else { + rawRead = recvLen; + dataRead = 0; + mimeTypeSent = false; + if (recvLen == 0) + { + mimeType("application/x-zerosize"); + mimeTypeSent = true; + } + } + break; + case FISH_STOR: + case FISH_WRITE: + case FISH_APPEND: + rawWrite = sendLen; + //myDebug( << "sending " << sendLen << endl); + writeChild(NULL,0); + break; + default : break; + } + } else if (rc/100 != 2) { + switch (fishCommand) { + case FISH_STOR: + case FISH_WRITE: + case FISH_APPEND: + error(ERR_COULD_NOT_WRITE,url.prettyURL()); + shutdownConnection(); + break; + case FISH_RETR: + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + break; + case FISH_READ: + if ( rc == 501 ) + { + mimeType("inode/directory"); + mimeTypeSent = true; + recvLen = 0; + finished(); + } + else + { + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + } + break; + case FISH_FISH: + case FISH_VER: + error(ERR_SLAVE_DEFINED,line); + shutdownConnection(); + break; + case FISH_PWD: + case FISH_CWD: + error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL()); + break; + case FISH_LIST: + myDebug( << "list error. reason: " << listReason << endl); + if (listReason == LIST) error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL()); + else if (listReason == CHECK) { + checkExist = false; + finished(); + } + break; + case FISH_STAT: + error(ERR_DOES_NOT_EXIST,url.prettyURL()); + udsStatEntry.clear(); + break; + case FISH_CHMOD: + error(ERR_CANNOT_CHMOD,url.prettyURL()); + break; + case FISH_CHOWN: + case FISH_CHGRP: + error(ERR_ACCESS_DENIED,url.prettyURL()); + break; + case FISH_MKD: + if ( rc == 501 ) + error(ERR_DIR_ALREADY_EXIST,url.prettyURL()); + else + error(ERR_COULD_NOT_MKDIR,url.prettyURL()); + break; + case FISH_RMD: + error(ERR_COULD_NOT_RMDIR,url.prettyURL()); + break; + case FISH_DELE: + error(ERR_CANNOT_DELETE,url.prettyURL()); + break; + case FISH_RENAME: + error(ERR_CANNOT_RENAME,url.prettyURL()); + break; + case FISH_COPY: + case FISH_LINK: + case FISH_SYMLINK: + error(ERR_COULD_NOT_WRITE,url.prettyURL()); + break; + default : break; + } + } else { + if (fishCommand == FISH_STOR) fishCommand = (hasAppend?FISH_APPEND:FISH_WRITE); + if (fishCommand == FISH_FISH) { + connected(); + } else if (fishCommand == FISH_LIST) { + if (listReason == LIST) { + listEntry(UDSEntry(),true); + } else if (listReason == CHECK) { + if (!checkOverwrite && checkExist) + { + error(ERR_FILE_ALREADY_EXIST,url.prettyURL()); + return; // Don't call finished! + } + } + } else if (fishCommand == FISH_STAT) { + UDSAtom atom; + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = url.fileName(); + udsStatEntry.append( atom ); + statEntry(udsStatEntry); + } else if (fishCommand == FISH_APPEND) { + dataReq(); + if (readData(rawData) > 0) sendCommand(FISH_APPEND,E(QString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); + sendLen = rawData.size(); + } else if (fishCommand == FISH_WRITE) { + dataReq(); + if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(QString::number(putPos)),E(QString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); + putPos += rawData.size(); + sendLen = rawData.size(); + } else if (fishCommand == FISH_RETR) { + data(QByteArray()); + } + finished(); + } +} + +void fishProtocol::writeStdin(const QString &line) +{ + qlist.append(line); + + if (writeReady) { + writeReady = false; + //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl); + myDebug( << "Writing: " << qlist.first() << endl); + myDebug( << "---------" << endl); + writeChild((const char *)qlist.first().latin1(), qlist.first().length()); + } +} + +void fishProtocol::sent() +{ + if (rawWrite > 0) { + myDebug( << "writing raw: " << rawData.size() << "/" << rawWrite << endl); + writeChild(rawData.data(),(rawWrite > rawData.size()?rawData.size():rawWrite)); + rawWrite -= rawData.size(); + if (rawWrite > 0) { + dataReq(); + if (readData(rawData) <= 0) { + shutdownConnection(); + } + } + return; + } else if (rawWrite == 0) { + // workaround: some dd's insist in reading multiples of + // 8 bytes, swallowing up to seven bytes. Sending + // newlines is safe even when a sane dd is used + writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",15); + rawWrite = -1; + return; + } + if (qlist.count() > 0) qlist.remove(qlist.begin()); + if (qlist.count() == 0) { + writeReady = true; + } else { + //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl); + myDebug( << "Writing: " << qlist.first() << endl); + myDebug( << "---------" << endl); + writeChild((const char *)qlist.first().latin1(),qlist.first().length()); + } +} + +int fishProtocol::received(const char *buffer, KIO::fileoffset_t buflen) +{ + int pos = 0; + do { + if (buflen <= 0) break; + + if (rawRead > 0) { + //myDebug( << "processedSize " << dataRead << ", len " << buflen << "/" << rawRead << endl); + int dataSize = (rawRead > buflen?buflen:rawRead); + if (!mimeTypeSent) + { + int mimeSize = QMIN(dataSize, (int)mimeBuffer.size()-dataRead); + memcpy(mimeBuffer.data()+dataRead,buffer,mimeSize); + dataRead += mimeSize; + rawRead -= mimeSize; + buffer += mimeSize; + buflen -= mimeSize; + if (rawRead == 0) // End of data + mimeBuffer.resize(dataRead); + if (dataRead < (int)mimeBuffer.size()) + { + myDebug( << "wait for more" << endl); + break; + } + + // We need a KMimeType::findByNameAndContent(filename,data) + // 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 ) + { + KMimeType::Ptr p_mimeType = KMimeType::findByContent(mimeBuffer); + if ( p_mimeType && p_mimeType->name() != KMimeType::defaultMimeType() ) + mime = p_mimeType; + } + + sendmimeType(mime->name()); + + + mimeTypeSent = true; + if (fishCommand != FISH_READ) { + totalSize(dataRead + rawRead); + data(mimeBuffer); + processedSize(dataRead); + } + mimeBuffer.resize(1024); + pos = 0; + continue; // Process rest of buffer/buflen + } + + QByteArray bdata; + bdata.duplicate(buffer,dataSize); + data(bdata); + + dataRead += dataSize; + rawRead -= dataSize; + processedSize(dataRead); + if (rawRead <= 0) { + buffer += dataSize; + buflen -= dataSize; + } else { + return 0; + } + } + + if (buflen <= 0) break; + + pos = 0; + // Find newline + while((pos < buflen) && (buffer[pos] != '\n')) + ++pos; + + if (pos < buflen) + { + QString s = remoteEncoding()->decode(QCString(buffer,pos+1)); + + buffer += pos+1; + buflen -= pos+1; + + manageConnection(s); + + pos = 0; + // Find next newline + while((pos < buflen) && (buffer[pos] != '\n')) + ++pos; + } + } while (childPid && buflen && (rawRead > 0 || pos < buflen)); + return buflen; +} +/** get a file */ +void fishProtocol::get(const KURL& u){ + myDebug( << "@@@@@@@@@ get " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + recvLen = -1; + sendCommand(FISH_RETR,E(url.path())); + } + run(); +} + +/** put a file */ +void fishProtocol::put(const KURL& u, int permissions, bool overwrite, bool /*resume*/){ + myDebug( << "@@@@@@@@@ put " << u << " " << permissions << " " << overwrite << " " /* << resume */ << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + putPerm = permissions; + checkOverwrite = overwrite; + checkExist = false; + putPos = 0; + listReason = CHECK; + sendCommand(FISH_LIST,E(url.path())); + sendCommand(FISH_STOR,"0",E(url.path())); + } + run(); +} +/** executes next command in sequence or calls finished() if all is done */ +void fishProtocol::finished() { + if (commandList.count() > 0) { + fishCommand = (fish_command_type)commandCodes.first(); + errorCount = -fishInfo[fishCommand].lines; + rawRead = 0; + rawWrite = -1; + udsEntry.clear(); + udsStatEntry.clear(); + writeStdin(commandList.first()); + //if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending "+(commandList.first().mid(1,commandList.first().find("\n")-1))+"..."); + commandList.remove(commandList.begin()); + commandCodes.remove(commandCodes.begin()); + } else { + myDebug( << "_______ emitting finished()" << endl); + SlaveBase::finished(); + isRunning = false; + } +} +/** aborts command sequence and calls error() */ +void fishProtocol::error(int type, const QString &detail) { + commandList.clear(); + commandCodes.clear(); + myDebug( << "ERROR: " << type << " - " << detail << endl); + SlaveBase::error(type,detail); + isRunning = false; +} +/** executes a chain of commands */ +void fishProtocol::run() { + if (!isRunning) { + int rc; + isRunning = true; + finished(); + fd_set rfds, wfds; + FD_ZERO(&rfds); + char buf[32768]; + int offset = 0; + while (isRunning) { + FD_SET(childFd,&rfds); + FD_ZERO(&wfds); + if (outBufPos >= 0) FD_SET(childFd,&wfds); + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + rc = select(childFd+1, &rfds, &wfds, NULL, &timeout); + if (rc < 0) { + if (errno == EINTR) + continue; + myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl); + error(ERR_CONNECTION_BROKEN,connectionHost); + shutdownConnection(); + return; + } + if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) { +#if 0 + QString debug; + debug.setLatin1(outBuf+outBufPos,outBufLen-outBufPos); + myDebug( << "now writing " << (outBufLen-outBufPos) << " " << debug.left(40) << "..." << endl); +#endif + if (outBufLen-outBufPos > 0) rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos); + else rc = 0; + if (rc >= 0) outBufPos += rc; + else { + if (errno == EINTR) + continue; + myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl); + error(ERR_CONNECTION_BROKEN,connectionHost); + shutdownConnection(); + return; + } + if (outBufPos >= outBufLen) { + outBufPos = -1; + outBuf = NULL; + sent(); + } + } + else if (FD_ISSET(childFd,&rfds)) { + rc = read(childFd,buf+offset,32768-offset); + //myDebug( << "read " << rc << " bytes" << endl); + if (rc > 0) { + int noff = received(buf,rc+offset); + if (noff > 0) memmove(buf,buf+offset+rc-noff,noff); + //myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset) << endl); + offset = noff; + } else { + if (errno == EINTR) + continue; + myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl); + error(ERR_CONNECTION_BROKEN,connectionHost); + shutdownConnection(); + return; + } + } + if (wasKilled()) + return; + } + } +} +/** stat a file */ +void fishProtocol::stat(const KURL& u){ + myDebug( << "@@@@@@@@@ stat " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + isStat = true; // FIXME: just a workaround for konq deficiencies + openConnection(); + isStat = false; // FIXME: just a workaround for konq deficiencies + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + sendCommand(FISH_STAT,E(url.path(-1))); + } + run(); +} +/** find mimetype for a file */ +void fishProtocol::mimetype(const KURL& u){ + myDebug( << "@@@@@@@@@ mimetype " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + recvLen = 1024; + sendCommand(FISH_READ,"0","1024",E(url.path())); + } + run(); +} +/** list a directory */ +void fishProtocol::listDir(const KURL& u){ + myDebug( << "@@@@@@@@@ listDir " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + listReason = LIST; + sendCommand(FISH_LIST,E(url.path())); + } + run(); +} +/** create a directory */ +void fishProtocol::mkdir(const KURL& u, int permissions) { + myDebug( << "@@@@@@@@@ mkdir " << u << " " << permissions << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + sendCommand(FISH_MKD,E(url.path())); + if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); + } + run(); +} +/** rename a file */ +void fishProtocol::rename(const KURL& s, const KURL& d, bool overwrite) { + myDebug( << "@@@@@@@@@ rename " << s << " " << d << " " << overwrite << endl); + if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) { + error(ERR_UNSUPPORTED_ACTION,s.prettyURL()); + return; + } + setHost(s.host(),s.port(),s.user(),s.pass()); + url = d; + openConnection(); + if (!isLoggedIn) return; + KURL src = s; + url.cleanPath(); + src.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (!overwrite) { + listReason = CHECK; + checkOverwrite = false; + sendCommand(FISH_LIST,E(url.path())); + } + sendCommand(FISH_RENAME,E(src.path()),E(url.path())); + } + run(); +} +/** create a symlink */ +void fishProtocol::symlink(const QString& target, const KURL& u, bool overwrite) { + myDebug( << "@@@@@@@@@ symlink " << target << " " << u << " " << overwrite << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (!overwrite) { + listReason = CHECK; + checkOverwrite = false; + sendCommand(FISH_LIST,E(url.path())); + } + sendCommand(FISH_SYMLINK,E(target),E(url.path())); + } + run(); +} +/** change file permissions */ +void fishProtocol::chmod(const KURL& u, int permissions){ + myDebug( << "@@@@@@@@@ chmod " << u << " " << permissions << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); + } + run(); +} +/** copies a file */ +void fishProtocol::copy(const KURL &s, const KURL &d, int permissions, bool overwrite) { + myDebug( << "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << overwrite << endl); + if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) { + error(ERR_UNSUPPORTED_ACTION,s.prettyURL()); + return; + } + //myDebug( << s << endl << d << endl); + setHost(s.host(),s.port(),s.user(),s.pass()); + url = d; + openConnection(); + if (!isLoggedIn) return; + KURL src = s; + url.cleanPath(); + src.cleanPath(); + if (!src.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (!overwrite) { + listReason = CHECK; + checkOverwrite = false; + sendCommand(FISH_LIST,E(url.path())); + } + sendCommand(FISH_COPY,E(src.path()),E(url.path())); + if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); + } + run(); +} +/** removes a file or directory */ +void fishProtocol::del(const KURL &u, bool isFile){ + myDebug( << "@@@@@@@@@ del " << u << " " << isFile << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + sendCommand((isFile?FISH_DELE:FISH_RMD),E(url.path())); + } + run(); +} +/** special like background execute */ +void fishProtocol::special( const QByteArray &data ){ + int tmp; + + QDataStream stream(data, IO_ReadOnly); + + stream >> tmp; + switch (tmp) { + case FISH_EXEC_CMD: // SSH EXEC + { + KURL u; + QString command; + QString tempfile; + stream >> u; + stream >> command; + myDebug( << "@@@@@@@@@ exec " << u << " " << command << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + sendCommand(FISH_EXEC,E(command),E(url.path())); + run(); + break; + } + default: + // Some command we don't understand. + error(ERR_UNSUPPORTED_ACTION,QString().setNum(tmp)); + break; + } +} +/** report status */ +void fishProtocol::slave_status() { + myDebug( << "@@@@@@@@@ slave_status" << endl); + if (childPid > 0) + slaveStatus(connectionHost,isLoggedIn); + else + slaveStatus(QString::null,false); +} diff --git a/kioslave/fish/fish.h b/kioslave/fish/fish.h new file mode 100644 index 000000000..e2665a320 --- /dev/null +++ b/kioslave/fish/fish.h @@ -0,0 +1,211 @@ +/*************************************************************************** + fish.h - a FISH kioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001 by Jörg Walter + email : trouble@garni.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * This program 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, version 2 of the License * + * * + ***************************************************************************/ +#ifndef __fish_h__ +#define __fish_h__ + +#include <qstring.h> +#include <qcstring.h> + + +#include <kurl.h> +#include <kio/global.h> +#include <kio/slavebase.h> +#include <kprocess.h> +#include <kio/authinfo.h> +#include <time.h> + +#define FISH_EXEC_CMD 'X' + +class fishProtocol : public KIO::SlaveBase +{ +public: + fishProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~fishProtocol(); + + /** +Connects to a server and logs us in via SSH. Then starts FISH protocol. +@ref isConnected is set to true if logging on was successful. +It is set to false if the connection becomes closed. + + */ + void openConnection(); + + /** + Clean up connection + */ + void shutdownConnection(bool forced=false); + /** sets connection information for subsequent commands */ + void setHost(const QString & host, int port, const QString & user, const QString & pass); + /** Forced close of the connection */ + void closeConnection(); + /** get a file */ + void get(const KURL& url); + /** put a file */ + void put(const KURL& url, int permissions, bool overwrite, bool resume); + /** aborts command sequence and calls error() */ + void error(int type, const QString &detail); + /** executes next command in sequence or calls finished() if all is done */ + void finished(); + /** stat a file */ + void stat(const KURL& url); + /** find mimetype for a file */ + void mimetype(const KURL& url); + /** list a directory */ + void listDir(const KURL& url); + /** create a directory */ + void mkdir(const KURL&url, int permissions); + /** rename a file */ + void rename(const KURL& src, const KURL& dest, bool overwrite); + /** create a symlink */ + void symlink(const QString& target, const KURL& dest, bool overwrite); + /** change file permissions */ + void chmod(const KURL& url, int permissions); + /** copies a file */ + void copy(const KURL &src, const KURL &dest, int permissions, bool overwrite); + /** report status */ + void slave_status(); + /** removes a file or directory */ + void del(const KURL &u, bool isfile); + /** special like background execute */ + void special( const QByteArray &data ); + +private: // Private attributes + /** the SSH process used to communicate with the remote end */ + pid_t childPid; + /** fd for reading and writing to the process */ + int childFd; + /** buffer for data to be written */ + const char *outBuf; + /** current write position in buffer */ + KIO::fileoffset_t outBufPos; + /** length of buffer */ + KIO::fileoffset_t outBufLen; + /** use su if true else use ssh */ + bool local; + /** // FIXME: just a workaround for konq deficiencies */ + bool isStat; + /** // FIXME: just a workaround for konq deficiencies */ + QString redirectUser, redirectPass; + +protected: // Protected attributes + /** for LIST/STAT */ + KIO::UDSEntry udsEntry; + /** for LIST/STAT */ + KIO::UDSEntry udsStatEntry; + /** for LIST/STAT */ + KIO::UDSAtom typeAtom; + /** for LIST/STAT */ + KIO::UDSAtom mimeAtom; + /** for LIST/STAT */ + QString thisFn; + /** for STAT */ + QString wantedFn; + QString statPath; + /** url of current request */ + KURL url; + /** true if connection is logged in successfully */ + bool isLoggedIn; + /** host name of current connection */ + QString connectionHost; + /** user name of current connection */ + QString connectionUser; + /** port of current connection */ + int connectionPort; + /** password of current connection */ + QString connectionPassword; + /** AuthInfo object used for logging in */ + KIO::AuthInfo connectionAuth; + /** number of lines received, == 0 -> everything went ok */ + int errorCount; + /** queue for lines to be sent */ + QStringList qlist; + /** queue for commands to be sent */ + QStringList commandList; + /** queue for commands to be sent */ + QValueList<int> commandCodes; + /** bytes still to be read in raw mode */ + KIO::fileoffset_t rawRead; + /** bytes still to be written in raw mode */ + KIO::fileoffset_t rawWrite; + /** data bytes to read in next read command */ + KIO::fileoffset_t recvLen; + /** data bytes to write in next write command */ + KIO::fileoffset_t sendLen; + /** true if the last write operation was finished */ + bool writeReady; + /** true if a command stack is currently executing */ + bool isRunning; + /** reason of LIST command */ + enum { CHECK, LIST } listReason; + /** true if FISH server understands APPEND command */ + bool hasAppend; + /** permission of created file */ + int putPerm; + /** true if file may be overwritten */ + bool checkOverwrite; + /** current position of write */ + KIO::fileoffset_t putPos; + /** true if file already existed */ + bool checkExist; + /** true if this is the first login attempt (== use cached password) */ + bool firstLogin; + /** write buffer */ + QByteArray rawData; + /** buffer for storing bytes used for MimeMagic */ + QByteArray mimeBuffer; + /** whther the mimetype has been sent already */ + bool mimeTypeSent; + /** number of bytes read so far */ + KIO::fileoffset_t dataRead; + /** details about each fishCommand */ + static const struct fish_info { + const char *command; + int params; + const char *alt; + int lines; + } fishInfo[]; + /** last FISH command sent to server */ + enum fish_command_type { FISH_FISH, FISH_VER, FISH_PWD, FISH_LIST, FISH_STAT, + FISH_RETR, FISH_STOR, + FISH_CWD, FISH_CHMOD, FISH_DELE, FISH_MKD, FISH_RMD, + FISH_RENAME, FISH_LINK, FISH_SYMLINK, FISH_CHOWN, + FISH_CHGRP, FISH_READ, FISH_WRITE, FISH_COPY, FISH_APPEND, FISH_EXEC } fishCommand; + int fishCodeLen; +protected: // Protected methods + /** manages initial communication setup including password queries */ + int establishConnection(char *buffer, KIO::fileoffset_t buflen); + int received(const char *buffer, KIO::fileoffset_t buflen); + void sent(); + /** builds each FISH request and sets the error counter */ + bool sendCommand(fish_command_type cmd, ...); + /** checks response string for result code, converting 000 and 001 appropriately */ + int handleResponse(const QString &str); + /** parses a ls -l time spec */ + int makeTimeFromLs(const QString &dayStr, const QString &monthStr, const QString &timeyearStr); + /** executes a chain of commands */ + void run(); + /** creates the subprocess */ + bool connectionStart(); + /** writes one chunk of data to stdin of child process */ + void writeChild(const char *buf, KIO::fileoffset_t len); + /** parses response from server and acts accordingly */ + void manageConnection(const QString &line); + /** writes to process */ + void writeStdin(const QString &line); +}; + + +#endif diff --git a/kioslave/fish/fish.pl b/kioslave/fish/fish.pl new file mode 100755 index 000000000..1ba539f9f --- /dev/null +++ b/kioslave/fish/fish.pl @@ -0,0 +1,369 @@ +#!/usr/bin/perl +# This program 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, version 2 of the License +=pod +This file was transferred by kio_fish, a network client part of the +KDE project. You may safely delete it, it will be transferred again +when needed. It's only purpose is to make kio_fish access faster and +more reliable. +=cut + +use Fcntl; + +$|++; +#open(DEBUG,">/tmp/kio_fish.debug.$$.log"); +# save code in initial directory if just transferred +if (defined $code) { + unlink('.fishsrv.pl'); + sysopen(FH,'.fishsrv.pl',O_WRONLY|O_CREAT|O_EXCL); + print FH $code; + close(FH); + chmod(0444,'.fishsrv.pl'); +# request new code if it changed (checksum mismatch) +# for automatic upgrades +} elsif ($ARGV[0] ne "{CHECKSUM}") { + $|=1; + print "### 100 transfer fish server\n"; + while(<STDIN>) { + last if /^__END__/; + $code.=$_; + } + exit(eval($code)); +} + +# we are up and running. +print "### 200\n"; +use strict; +use POSIX qw(getcwd dup2 strftime); +$SIG{'CHLD'} = 'IGNORE'; +$| = 1; +MAIN: while (<STDIN>) { + chomp; + chomp; + next if !length($_) || substr($_,0,1) ne '#'; +#print DEBUG "$_\n"; + s/^#//; + /^VER / && do { + # We do not advertise "append" capability anymore, as "write" is + # as fast in perl mode and more reliable (overlapping writes) + print "VER 0.0.3 copy lscount lslinks lsmime exec stat\n### 200\n"; + next; + }; + /^PWD$/ && do { + print getcwd(),"\n### 200\n"; + next; + }; + /^SYMLINK\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + print (symlink($ofn,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^COPY\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + my ($size) = (stat($ofn))[7]; + my $read = 1; + if (-l $ofn) { + my $dest = readlink($ofn); + unlink($fn); + symlink($dest,$fn) || ($read = 0); + } else { + sysopen(FH,$ofn,O_RDONLY) || do { print "### 500 $!\n"; next; }; + sysopen(OFH,$fn,O_WRONLY|O_CREAT|O_TRUNC) || do { close(FH); print "### 500 $!\n"; next; }; + local $/ = undef; + my $buffer = ''; + while ($size > 32768 && ($read = sysread(FH,$buffer,32768)) > 0) { + $size -= $read; + if (syswrite(OFH,$buffer,$read) != $read) { + close(FH); close(OFH); + print "### 500 $!\n"; + next MAIN; + } + + } + while ($size > 0 && ($read = sysread(FH,$buffer,$size)) > 0) { + $size -= $read; + if (syswrite(OFH,$buffer,$read) != $read) { + close(FH); close(OFH); + print "### 500 $!\n"; + next MAIN; + } + } + close(FH); + close(OFH); + } + if ($read > 0) { + print "### 200\n"; + } else { + print "### 500 $!\n"; + } + next; + }; + /^LINK\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + print (link($ofn,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^RENAME\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + print (rename($ofn,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^CHGRP\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($2); + print (chown(-1,int($1),$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^CHOWN\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($2); + print (chown(int($1),-1,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^CHMOD\s+([0-7]+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($2); + print (chmod(oct($1),$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^DELE\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($1); + print (unlink($fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^RMD\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $dn = unquote($1); + print (rmdir($dn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^MKD\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $dn = unquote($1); + if (mkdir($dn,0777)) { + print "### 200\n"; + } else { + my $err = $!; + print (chdir($dn)?"### 501 $err\n":"### 500 $err\n"); + } + next; + }; + /^CWD\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $dn = unquote($1); + print (chdir($dn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^LIST\s+((?:\\.|[^\\])*?)\s*$/ && do { + list($1, 1); + next; + }; + /^STAT\s+((?:\\.|[^\\])*?)\s*$/ && do { + list($1, 0); + next; + }; + /^WRITE\s+(\d+)\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + write_loop($2,$3,O_WRONLY|O_CREAT,$1); + next; + }; + /^APPEND\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + write_loop($1,$2,O_WRONLY|O_APPEND); + next; + }; + /^STOR\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + write_loop($1,$2,O_WRONLY|O_CREAT|O_TRUNC); + next; + }; + /^RETR\s+((?:\\.|[^\\])*?)\s*$/ && do { + read_loop($1); + next; + }; + /^READ\s+(\d+)\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + read_loop($3,$2,$1); + next; + }; + /^EXEC\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $tempfile = unquote($2); + my $command = unquote($1); + $command = $command . ";echo \"###RESULT: \$?\""; + print("### 500 $!\n"), next + if (!sysopen(FH,$tempfile,O_CREAT|O_EXCL|O_WRONLY,0600)); + my $pid = fork(); + print("### 500 $!\n"), next + if (!defined $pid); + if ($pid == 0) { + open(STDOUT,'>>&FH'); + open(STDERR,'>>&FH'); + open(STDIN,'</dev/null'); # not sure here, ms windows anyone? + exec('/bin/sh','-c',$command); + print STDERR "Couldn't exec /bin/sh: $!\n"; + exit(255); + } + waitpid($pid,0); + close(FH); + print "### 200\n"; + next; + }; +} +exit(0); + +sub list { + my $dn = unquote($_[0]); + my @entries; + if (!-e $dn) { + print "### 404 File does not exist\n"; + return; + } elsif ($_[1] && -d _) { + opendir(DIR,$dn) || do { print "### 500 $!\n"; return; }; + @entries = readdir(DIR); + closedir(DIR); + } else { + ($dn, @entries) = $dn =~ m{(.*)/(.*)}; + $dn = '/' if (!length($dn)); + } + print scalar(@entries),"\n### 100\n"; + my $cwd = getcwd(); + chdir($dn) || do { print "### 500 $!\n"; return; }; + foreach (@entries) { + my $link = readlink; + my ($mode,$uid,$gid,$size,$mtime) = (lstat)[2,4,5,7,9]; + print filetype($mode,$link,$uid,$gid); + print "S$size\n"; + print strftime("D%Y %m %d %H %M %S\n",localtime($mtime)); + print ":$_\n"; + print "L$link\n" if defined $link; + print mimetype($_); + print "\n"; + } + chdir($cwd); + print "### 200\n"; +} + +sub read_loop { + my $fn = unquote($_[0]); + my ($size) = ($_[1]?int($_[1]):(stat($fn))[7]); + my $error = ''; + print "### 501 Is directory\n" and return if -d $fn; + sysopen(FH,$fn,O_RDONLY) || ($error = $!); + if ($_[2]) { + sysseek(FH,int($_[2]),0) || do { close(FH); $error ||= $!; }; + } + print "### 500 $error\n" and return if $error; + if (@_ < 2) { + print "$size\n"; + } + print "### 100\n"; + my $buffer = ''; + my $read = 1; + while ($size > 32768 && ($read = sysread(FH,$buffer,32768)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + print $buffer; + } + while ($size > 0 && ($read = sysread(FH,$buffer,$size)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + print $buffer; + } + while ($size > 0) { + print ' '; + $size--; + } + $error ||= $! if $read <= 0; + close(FH); + if (!$error) { + print "### 200\n"; + } else { + print "### 500 $error\n"; + } +} + +sub write_loop { + my $size = int($_[0]); + my $fn = unquote($_[1]); +#print DEBUG "write_loop called $size size, $fn fn, $_[2]\n"; + my $error = ''; + sysopen(FH,$fn,$_[2]) || do { print "### 400 $!\n"; return; }; + eval { flock(FH,2); }; + if ($_[3]) { + sysseek(FH,int($_[3]),0) || do { close(FH);print "### 400 $!\n"; return; }; + } + <STDIN>; + print "### 100\n"; + my $buffer = ''; + my $read = 1; + while ($size > 32768 && ($read = read(STDIN,$buffer,32768)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + $error ||= $! if (syswrite(FH,$buffer,$read) != $read); + } + while ($size > 0 && ($read = read(STDIN,$buffer,$size)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + $error ||= $! if (syswrite(FH,$buffer,$read) != $read); + } + close(FH); + if (!$error) { + print "### 200\n"; + } else { + print "### 500 $error\n"; + } +} + +sub unquote { $_ = shift; s/\\(.)/$1/g; return $_; } + +sub filetype { + my ($mode,$link,$uid,$gid) = @_; + my $result = 'P'; + while (1) { + -f _ && do { $result .= '-'; last; }; + -d _ && do { $result .= 'd'; last; }; + defined($link) && do { $result .= 'l'; last; }; + -c _ && do { $result .= 'c'; last; }; + -b _ && do { $result .= 'b'; last; }; + -S _ && do { $result .= 's'; last; }; + -p _ && do { $result .= 'p'; last; }; + $result .= '?'; last; + } + $result .= ($mode & 0400?'r':'-'); + $result .= ($mode & 0200?'w':'-'); + $result .= ($mode & 0100?($mode&04000?'s':'x'):($mode&04000?'S':'-')); + $result .= ($mode & 0040?'r':'-'); + $result .= ($mode & 0020?'w':'-'); + $result .= ($mode & 0010?($mode&02000?'s':'x'):($mode&02000?'S':'-')); + $result .= ($mode & 0004?'r':'-'); + $result .= ($mode & 0002?'w':'-'); + $result .= ($mode & 0001?($mode&01000?'t':'x'):($mode&01000?'T':'-')); + + $result .= ' '; + $result .= (getpwuid($uid)||$uid); + $result .= '.'; + $result .= (getgrgid($gid)||$gid); + $result .= "\n"; + return $result; +} + +sub mimetype { + my $fn = shift; + return "Minode/directory\n" if -d $fn; + pipe(IN,OUT); + my $pid = fork(); + return '' if (!defined $pid); + if ($pid) { + close(OUT); + my $type = <IN>; + close(IN); + chomp $type; + chomp $type; + $type =~ s/[,; ].*//; + return '' if ($type !~ m/\//); + return "M$type\n" + } + close(IN); + sysopen(NULL,'/dev/null',O_RDWR); + dup2(fileno(NULL),fileno(STDIN)); + dup2(fileno(OUT),fileno(STDOUT)); + dup2(fileno(NULL),fileno(STDERR)); + exec('/usr/bin/file','-i','-b','-L',$fn); + exit(0); +} +__END__ diff --git a/kioslave/fish/fish.protocol b/kioslave/fish/fish.protocol new file mode 100644 index 000000000..c14599d50 --- /dev/null +++ b/kioslave/fish/fish.protocol @@ -0,0 +1,81 @@ +[Protocol] +exec=kio_fish +protocol=fish +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link, +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +Icon=remote +Description=A kioslave for the FISH protocol +Description[af]='n Kioslave vir die FISH protokol +Description[be]=Kioslave Ð´Ð»Ñ Ð¿Ñ€Ð°Ñ‚Ð°ÐºÐ¾Ð»Ð° FISH +Description[bn]=ফিশ (FISH) পà§à¦°à§‹à¦Ÿà§‹à¦•ল-à¦à¦° জনà§à¦¯ à¦à¦•টি kioslave +Description[br]=Ur c'hioslave evit ar c'homenad FISH +Description[bs]=kioslave za FISH protokol +Description[ca]=Un kioslave pel protocol FISH +Description[cs]=Pomocný protokol pro FISH +Description[csb]=Plugins protokòłu FISH +Description[da]=En kioslave for FISH-protokollen +Description[de]=Ein-/Ausgabemodul für das FISH-Protokoll +Description[el]=Ένας kioslave για το Ï€Ïωτόκολλο FISH +Description[eo]=K-enel-sklavo por la FISH protokolo +Description[es]=Un kioslave para el protocolo FISH +Description[et]=FISH protokolli IO-moodul +Description[eu]=FISH protokolorako kioslavea +Description[fa]=یک kioslave برای قرارداد FISH +Description[fi]=Liitäntä FISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole FISH +Description[fy]=In kioslave foar it FISH protokol +Description[ga]=kioslave le haghaidh an phrótacail FISH +Description[gl]=Un kioslave para o protocolo FISH +Description[he]=ממשק kioslave עבור פרוטוקול FISH +Description[hi]=फिश पà¥à¤°à¥‹à¤Ÿà¥‹à¤•ॉल हेतॠके-आई-ओ-सà¥à¤²à¥‡à¤µ +Description[hr]=Kioslave za FISH protokol +Description[hu]=KDE-protokoll a FISH protokollhoz +Description[is]=kioslave fyrir FISH samskiptaregluna +Description[it]=Un kioslave per il protocollo FISH +Description[ja]=FISH プãƒãƒˆã‚³ãƒ«ã®ãŸã‚ã® kioslave +Description[ka]=kioslave FISH პრáƒáƒ¢áƒáƒ™áƒáƒšáƒ˜áƒ¡áƒ—ვის +Description[kk]=FISH протоколға арналған файл жүйеÑінің модулі +Description[km]=kioslave សម្រាប់​ពិធីការ FISH +Description[ko]=FISH í”„ë¡œí† ì½œì„ ìœ„í•œ KIO ìŠ¬ë ˆì´ë¸Œ +Description[lt]=Kiovergas FISH protokolui +Description[lv]=KIO vergs FISH protokolam +Description[mk]=КИО-Ñлужител за протоколот FISH +Description[ms]=Hamba kio untuk protokol FISH +Description[nb]=En IU-slave for FISH-protokollen +Description[nds]=En In-/Utgaavdeenst för dat FISH-Protokoll +Description[ne]=FISH पà¥à¤°à¥‹à¤Ÿà¥‹à¤•लका लागि à¤à¤‰à¤Ÿà¤¾ किओसà¥à¤²à¥‡à¤ +Description[nl]=Een kioslave voor het protocol FISH +Description[nn]=Ein IU-slave for FISH-protokollen +Description[pa]=FISH ਪਰੋਟੋਕਾਲ ਲਈ kioslave +Description[pl]=Wtyczka protokoÅ‚u FISH +Description[pt]=Um 'kioslave' para o protocolo FISH +Description[pt_BR]=Uma implementação para o protocolo FISH +Description[ro]=Un dispozitiv de I/E pentru protocolul FISH +Description[ru]=Модуль файловой ÑиÑтемы Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ð° FISH +Description[rw]=kio-umugaragu ya Porotokole FISH +Description[se]=SO-Å¡láva FISH-protokolla várás +Description[sk]=kioslave pre protokol FISH +Description[sl]=kioslave za protokol FISH +Description[sr]=Kioslave за протокол FISH +Description[sr@Latn]=Kioslave za protokol FISH +Description[sv]=En I/O-slav för protokollet FISH +Description[ta]=FISH நெறிமà¯à®±à¯ˆà®•à¯à®•ான ஒர௠கà¯à®¯à¯‹à®¸à¯à®²à¯‡à®µà¯ +Description[te]=à°«à°¿à°·à± à°ªà±à°°à±Šà°Ÿà±Šà°•ాలౠకొరకౠà°à°’ బానిస +Description[th]=ตัวนำข้à¸à¸¡à¸¹à¸¥à¹€à¸‚้า-à¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹‚ปรโตคà¸à¸¥ FISH +Description[tr]=FISH protokolü için kioslave +Description[tt]=FISH protokolı öçen birem sistemeneñ modulı +Description[uk]=Підлеглий B/Ð’ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ñƒ FISH +Description[uz]=FISH protokoli uchun KCH-sleyv +Description[uz@cyrillic]=FISH протоколи учун КЧ-Ñлейв +Description[vi]=A kioslave (đà y tá»› và o ra KDE) cho giao thức FISH +Description[wa]=On kioslave pol protocole FISH +Description[zh_CN]=FISH å议的 KIO 仆人 +Description[zh_TW]=用於 FISH 通訊å”定的 kioslave +DocPath=kioslave/fish.html diff --git a/kioslave/fish/nxfish.protocol b/kioslave/fish/nxfish.protocol new file mode 100644 index 000000000..f050282af --- /dev/null +++ b/kioslave/fish/nxfish.protocol @@ -0,0 +1,74 @@ +[Protocol] +exec=kio_fish +protocol=nxfish +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link, +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +Icon=remote +Description=A kioslave for the NXFISH protocol +Description[af]='n Kioslave vir die NXFISH protokol +Description[be]=Kioslave Ð´Ð»Ñ Ð¿Ñ€Ð°Ñ‚Ð°ÐºÐ¾Ð»Ð° NXFISH +Description[br]=Ur c'hioslave evit ar c'homenad NXFISH +Description[bs]=kioslave za NXFISH protokol +Description[ca]=Un kioslave pel protocol NXFISH +Description[cs]=Pomocný protokol pro NXFISH +Description[csb]=Plugins protokòłu NXFISH +Description[da]=En kioslave for NXFISH-protokollen +Description[de]=Ein-/Ausgabemodul für das NXFISH-Protokoll +Description[el]=Ένας kioslave για το Ï€Ïωτόκολλο NXFISH +Description[eo]=K-enel-sklavo por la NXFISH protokolo +Description[es]=Un kioslave para el protocolo NXFISH +Description[et]=NXFISH protokolli IO-moodul +Description[eu]=NXFISH protokolorako kioslavea +Description[fa]=یک kioslave برای قرارداد NXFISH +Description[fi]=Liitäntä NXFISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole NXFISH +Description[fy]=In kioslave foar it protokol NXFISH +Description[ga]=kioslave le haghaidh an phrótacail NXFISH +Description[gl]=Un kioslave para o protocolo NXFISH +Description[he]=ממשק kioslave עבור פרוטוקול NXFISH +Description[hr]=Kioslave za NXFISH protokol +Description[hu]=KDE-protokoll az NXFISH protokollhoz +Description[is]=kioslave fyrir NXFISH samskiptaregluna +Description[it]=Un kioslave per il protocollo NXFISH +Description[ja]=NXFISH プãƒãƒˆã‚³ãƒ«ã®ãŸã‚ã® kioslave +Description[ka]=kioslave NXFISH áƒáƒ¥áƒ›áƒ˜áƒ¡áƒ—ვის +Description[kk]=NXFISH протоколы үшін kioslave +Description[km]=kioslave សម្រាប់​ពិធីការ NXFISH +Description[ko]=FISH í”„ë¡œí† ì½œì„ ìœ„í•œ KIO ìŠ¬ë ˆì´ë¸Œ +Description[lt]=PagalbinÄ— kio programa NXFISH protokolui +Description[mk]=КИО-Ñлужител за протоколот NXFISH +Description[nb]=En kioskslave for NXFISH-protokollen +Description[nds]=En In-/Utgaavdeenst för dat NXFISH-Protokoll +Description[ne]=NXFISH पà¥à¤°à¥‹à¤Ÿà¥‹à¤•लका लागि किओसà¥à¤²à¥‡à¤ +Description[nl]=Een kioslave voor het protocol NXFISH +Description[nn]=Ein IU-slave for NXFISH-protokollen +Description[pa]=NXFISH ਪਰੋਟੋਕਾਲ ਲਈ kioslave +Description[pl]=Wtyczka protokoÅ‚u NXFISH +Description[pt]=Um 'kioslave' para o protocolo NXFISH +Description[pt_BR]=Uma implementação para o protocolo NXFISH +Description[ro]=Un kioslave pentru protocolul NXFISH +Description[ru]=Модуль файловой ÑиÑтемы Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ð° NXFISH +Description[se]=SO-Å¡láva NXFISH-protokolla várás +Description[sk]=kioslave pre protokol NXFISH +Description[sl]=kioslave za protokol NXFISH +Description[sr]=Kioslave за протокол NXFISH +Description[sr@Latn]=Kioslave za protokol NXFISH +Description[sv]=En I/O-slav för protokollet NXFISH +Description[te]=ఎనౠఎకà±à°¸à± à°«à°¿à°·à± à°ªà±à°°à±Šà°Ÿà±Šà°•ాలౠకొరకౠà°à°’ బానిస +Description[th]=ตัวนำข้à¸à¸¡à¸¹à¸¥à¹€à¸‚้า-à¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹‚ปรโตคà¸à¸¥ NXFISH +Description[tr]=NXFISH protokolü için kioslave +Description[uk]=Підлеглий B/Ð’ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ñƒ NXFISH +Description[uz]=NXFISH protokoli uchun KCH-sleyvi +Description[uz@cyrillic]=NXFISH протоколи учун КЧ-Ñлейви +Description[vi]=A kioslave (đà y tá»› và o ra KDE) cho giao thức NXFISH +Description[wa]=On kioslave pol protocole NXFISH +Description[zh_CN]=NXFISH å议的 KIO 仆人 +Description[zh_TW]=用於 NXFISH 通訊å”定的 kioslave +DocPath=kioslave/fish.html diff --git a/kioslave/floppy/AUTHORS b/kioslave/floppy/AUTHORS new file mode 100644 index 000000000..062f9d6cb --- /dev/null +++ b/kioslave/floppy/AUTHORS @@ -0,0 +1,2 @@ +Written and maintained by: +Alexander Neundorf, neundorf@kde.org diff --git a/kioslave/floppy/Makefile.am b/kioslave/floppy/Makefile.am new file mode 100644 index 000000000..5c2533fda --- /dev/null +++ b/kioslave/floppy/Makefile.am @@ -0,0 +1,20 @@ +## Makefile.am of kdebase/kioslave/floppy + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) + +####### Files + +kde_module_LTLIBRARIES = kio_floppy.la + +kio_floppy_la_SOURCES = kio_floppy.cpp program.cpp +kio_floppy_la_LIBADD = $(LIB_KIO) +kio_floppy_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +kdelnk_DATA = floppy.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_floppy.pot diff --git a/kioslave/floppy/README b/kioslave/floppy/README new file mode 100644 index 000000000..99d87bd71 --- /dev/null +++ b/kioslave/floppy/README @@ -0,0 +1,7 @@ +this is an ioslave for KDE 2/3 for accessing fat/vfat floppies without +mounting. +It is a wrapper around the mtools. + + +Alex +neundorf@kde.org diff --git a/kioslave/floppy/TODO b/kioslave/floppy/TODO new file mode 100644 index 000000000..f4f1e679e --- /dev/null +++ b/kioslave/floppy/TODO @@ -0,0 +1,3 @@ +-error handling (will be done until new year) + +Alex diff --git a/kioslave/floppy/floppy.protocol b/kioslave/floppy/floppy.protocol new file mode 100644 index 000000000..3dcae5c78 --- /dev/null +++ b/kioslave/floppy/floppy.protocol @@ -0,0 +1,14 @@ +[Protocol] +exec=kio_floppy +protocol=floppy +input=none +output=filesystem +listing=Name,Type,Size,Date +reading=true +writing=true +makedir=true +deleting=true +moving=true +Icon=3floppy_mount +DocPath=kioslave/floppy.html +Class=:local diff --git a/kioslave/floppy/kio_floppy.cpp b/kioslave/floppy/kio_floppy.cpp new file mode 100644 index 000000000..ef3d6e6f2 --- /dev/null +++ b/kioslave/floppy/kio_floppy.cpp @@ -0,0 +1,1169 @@ +/* This file is part of the KDE project + + Copyright (C) 2000 Alexander Neundorf <neundorf@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. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif + +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <time.h> +#include <sys/time.h> +#include <sys/types.h> + +#include <qtextstream.h> +#include <qcstring.h> +#include <qfile.h> + +#include "kio_floppy.h" + +#include <kinstance.h> +#include <kdebug.h> +#include <kio/global.h> +#include <klocale.h> + +using namespace KIO; + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_floppy" ); + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_floppy protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + kdDebug(7101) << "Floppy: kdemain: starting" << endl; + + FloppyProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; +} + +void getDriveAndPath(const QString& path, QString& drive, QString& rest) +{ + drive=QString::null; + rest=QString::null; + QStringList list=QStringList::split("/",path); + for (QStringList::Iterator it=list.begin(); it!=list.end(); it++) + { + if (it==list.begin()) + drive=(*it)+":"; + else + rest=rest+"/"+(*it); + } +} + +FloppyProtocol::FloppyProtocol (const QCString &pool, const QCString &app ) +:SlaveBase( "floppy", pool, app ) +,m_mtool(0) +,m_stdoutBuffer(0) +,m_stderrBuffer(0) +,m_stdoutSize(0) +,m_stderrSize(0) +{ + kdDebug(7101)<<"Floppy::Floppy: -"<<pool<<"-"<<endl; +} + +FloppyProtocol::~FloppyProtocol() +{ + delete [] m_stdoutBuffer; + delete [] m_stderrBuffer; + delete m_mtool; + m_mtool=0; + m_stdoutBuffer=0; + m_stderrBuffer=0; +} + +int FloppyProtocol::readStdout() +{ + //kdDebug(7101)<<"Floppy::readStdout"<<endl; + if (m_mtool==0) return 0; + + char buffer[16*1024]; + int length=::read(m_mtool->stdoutFD(),buffer,16*1024); + if (length<=0) return 0; + + //+1 gives us room for a terminating 0 + char *newBuffer=new char[length+m_stdoutSize+1]; + kdDebug(7101)<<"Floppy::readStdout(): length: "<<length<<" m_tsdoutSize: "<<m_stdoutSize<<" +1="<<length+m_stdoutSize+1<<endl; + if (m_stdoutBuffer!=0) + { + memcpy(newBuffer, m_stdoutBuffer, m_stdoutSize); + } + memcpy(newBuffer+m_stdoutSize, buffer, length); + m_stdoutSize+=length; + newBuffer[m_stdoutSize]='\0'; + + delete [] m_stdoutBuffer; + m_stdoutBuffer=newBuffer; + //kdDebug(7101)<<"Floppy::readStdout(): -"<<m_stdoutBuffer<<"-"<<endl; + + //kdDebug(7101)<<"Floppy::readStdout ends"<<endl; + return length; +} + +int FloppyProtocol::readStderr() +{ + //kdDebug(7101)<<"Floppy::readStderr"<<endl; + if (m_mtool==0) return 0; + + /*struct timeval tv; + tv.tv_sec=0; + tv.tv_usec=1000*300; + ::select(0,0,0,0,&tv);*/ + + char buffer[16*1024]; + int length=::read(m_mtool->stderrFD(),buffer,16*1024); + kdDebug(7101)<<"Floppy::readStderr(): read "<<length<<" bytes"<<endl; + if (length<=0) return 0; + + //+1 gives us room for a terminating 0 + char *newBuffer=new char[length+m_stderrSize+1]; + memcpy(newBuffer, m_stderrBuffer, m_stderrSize); + memcpy(newBuffer+m_stderrSize, buffer, length); + m_stderrSize+=length; + newBuffer[m_stderrSize]='\0'; + delete [] m_stderrBuffer; + m_stderrBuffer=newBuffer; + kdDebug(7101)<<"Floppy::readStderr(): -"<<m_stderrBuffer<<"-"<<endl; + + return length; +} + +void FloppyProtocol::clearBuffers() +{ + kdDebug(7101)<<"Floppy::clearBuffers()"<<endl; + m_stdoutSize=0; + m_stderrSize=0; + delete [] m_stdoutBuffer; + m_stdoutBuffer=0; + delete [] m_stderrBuffer; + m_stderrBuffer=0; + //kdDebug(7101)<<"Floppy::clearBuffers() ends"<<endl; +} + +void FloppyProtocol::terminateBuffers() +{ + //kdDebug(7101)<<"Floppy::terminateBuffers()"<<endl; + //append a terminating 0 to be sure + if (m_stdoutBuffer!=0) + { + m_stdoutBuffer[m_stdoutSize]='\0'; + } + if (m_stderrBuffer!=0) + { + m_stderrBuffer[m_stderrSize]='\0'; + } + //kdDebug(7101)<<"Floppy::terminateBuffers() ends"<<endl; +} + +bool FloppyProtocol::stopAfterError(const KURL& url, const QString& drive) +{ + if (m_stderrSize==0) + return true; + //m_stderrBuffer[m_stderrSize]='\0'; + + QString outputString(m_stderrBuffer); + QTextIStream output(&outputString); + QString line=output.readLine(); + kdDebug(7101)<<"line: -"<<line<<"-"<<endl; + if (line.find("resource busy") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access drive %1.\nThe drive is still busy.\nWait until it is inactive and then try again.").arg(drive)); + } + else if ((line.find("Disk full") > -1) || (line.find("No free cluster") > -1)) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not write to file %1.\nThe disk in drive %2 is probably full.").arg(url.prettyURL(),drive)); + } + //file not found + else if (line.find("not found") > -1) + { + error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + //no disk + else if (line.find("not configured") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThere is probably no disk in the drive %2").arg(url.prettyURL(),drive)); + } + else if (line.find("No such device") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThere is probably no disk in the drive %2 or you do not have enough permissions to access the drive.").arg(url.prettyURL(),drive)); + } + else if (line.find("not supported") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThe drive %2 is not supported.").arg(url.prettyURL(),drive)); + } + //not supported or no such drive + else if (line.find("Permission denied") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nMake sure the floppy in drive %2 is a DOS-formatted floppy disk \nand that the permissions of the device file (e.g. /dev/fd0) are set correctly (e.g. rwxrwxrwx).").arg(url.prettyURL(),drive)); + } + else if (line.find("non DOS media") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThe disk in drive %2 is probably not a DOS-formatted floppy disk.").arg(url.prettyURL(),drive)); + } + else if (line.find("Read-only") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Access denied.\nCould not write to %1.\nThe disk in drive %2 is probably write-protected.").arg(url.prettyURL(),drive)); + } + else if ((outputString.find("already exists") > -1) || (outputString.find("Skipping ") > -1)) + { + error( KIO::ERR_FILE_ALREADY_EXIST,url.prettyURL()); + //return false; + } + else if (outputString.find("could not read boot sector") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not read boot sector for %1.\nThere is probably not any disk in drive %2.").arg(url.prettyURL(),drive)); + //return false; + } + else + { + error( KIO::ERR_UNKNOWN, outputString); + } + return true; +} + +void FloppyProtocol::listDir( const KURL& _url) +{ + kdDebug(7101)<<"Floppy::listDir() "<<_url.path()<<endl; + KURL url(_url); + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + url.setPath("/a/"); + redirection(url); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + QStringList args; + + args<<"mdir"<<"-a"<<(drive+floppyPath); + if (m_mtool!=0) + delete m_mtool; + m_mtool=new Program(args); + + clearBuffers(); + + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mdir"); + return; + } + + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + //now mdir has finished + //let's parse the output + terminateBuffers(); + + if (errorOccured) + return; + + QString outputString(m_stdoutBuffer); + QTextIStream output(&outputString); + QString line; + + int totalNumber(0); + int mode(0); + UDSEntry entry; + + while (!output.atEnd()) + { + line=output.readLine(); + kdDebug(7101)<<"Floppy::listDir(): line: -"<<line<<"- length: "<<line.length()<<endl; + + if (mode==0) + { + if (line.isEmpty()) + { + kdDebug(7101)<<"Floppy::listDir(): switching to mode 1"<<endl; + mode=1; + } + } + else if (mode==1) + { + if (line[0]==' ') + { + kdDebug(7101)<<"Floppy::listDir(): ende"<<endl; + totalSize(totalNumber); + break; + } + entry.clear(); + StatInfo info=createStatInfo(line); + if (info.isValid) + { + createUDSEntry(info,entry); + //kdDebug(7101)<<"Floppy::listDir(): creating UDSEntry"<<endl; + listEntry( entry, false); + totalNumber++; + } + } + } + listEntry( entry, true ); // ready + finished(); + //kdDebug(7101)<<"Floppy::listDir() ends"<<endl; +} + +void FloppyProtocol::errorMissingMToolsProgram(const QString& name) +{ + error(KIO::ERR_SLAVE_DEFINED,i18n("Could not start program \"%1\".\nEnsure that the mtools package is installed correctly on your system.").arg(name)); + } + +void FloppyProtocol::createUDSEntry(const StatInfo& info, UDSEntry& entry) +{ + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = info.name; + entry.append( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = info.size; + entry.append(atom); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = info.time; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long=info.mode; + entry.append( atom ); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long =(info.isDir?S_IFDIR:S_IFREG); + entry.append( atom ); +} + +StatInfo FloppyProtocol::createStatInfo(const QString line, bool makeStat, const QString& dirName) +{ + //kdDebug(7101)<<"Floppy::createUDSEntry()"<<endl; + QString name; + QString size; + bool isDir(false); + QString day,month, year; + QString hour, minute; + StatInfo info; + + if (line.length()==41) + { + int nameLength=line.find(' '); + kdDebug(7101)<<"Floppy::createStatInfo: line find: "<<nameLength <<"= -"<<line<<"-"<<endl; + if (nameLength>0) + { + name=line.mid(0,nameLength); + QString ext=line.mid(9,3); + ext=ext.stripWhiteSpace(); + if (!ext.isEmpty()) + name+="."+ext; + } + kdDebug(7101)<<"Floppy::createStatInfo() name 8.3= -"<<name<<"-"<<endl; + } + else if (line.length()>41) + { + name=line.mid(42); + kdDebug(7101)<<"Floppy::createStatInfo() name vfat: -"<<name<<"-"<<endl; + } + if ((name==".") || (name=="..")) + { + if (makeStat) + name=dirName; + else + { + info.isValid=false; + return info; + } + } + + if (line.mid(13,5)=="<DIR>") + { + //kdDebug(7101)<<"Floppy::createUDSEntry() isDir"<<endl; + size="1024"; + isDir=true; + } + else + { + size=line.mid(13,9); + //kdDebug(7101)<<"Floppy::createUDSEntry() size: -"<<size<<"-"<<endl; + } + + //TEEKANNE JPG 70796 01-02-2003 17:47 Teekanne.jpg + if (line[25]=='-') + { + month=line.mid(23,2); + day=line.mid(26,2); + year=line.mid(29,4); + } + else //SETUP PKG 1019 1997-09-25 10:31 setup.pkg + { + year=line.mid(23,4); + month=line.mid(28,2); + day=line.mid(31,2); + } + hour=line.mid(35,2); + minute=line.mid(38,2); + //kdDebug(7101)<<"Floppy::createUDSEntry() day: -"<<day<<"-"<<month<<"-"<<year<<"- -"<<hour<<"-"<<minute<<"-"<<endl; + + if (name.isEmpty()) + { + info.isValid=false; + return info; + } + + info.name=name; + info.size=size.toInt(); + + QDateTime date(QDate(year.toInt(),month.toInt(),day.toInt()),QTime(hour.toInt(),minute.toInt())); + info.time=date.toTime_t(); + + if (isDir) + info.mode = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH| S_IWOTH|S_IWGRP|S_IWUSR ; + else + info.mode = S_IRUSR | S_IRGRP | S_IROTH| S_IWOTH|S_IWGRP|S_IWUSR; + + info.isDir=isDir; + + info.isValid=true; + //kdDebug(7101)<<"Floppy::createUDSEntry() ends"<<endl; + return info; +} + +StatInfo FloppyProtocol::_stat(const KURL& url) +{ + StatInfo info; + + QString path(url.path()); + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + if (floppyPath.isEmpty()) + { + kdDebug(7101)<<"Floppy::_stat(): floppyPath.isEmpty()"<<endl; + info.name=path; + info.size=1024; + info.time=0; + info.mode=S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH| S_IWOTH|S_IWGRP|S_IWUSR; + info.isDir=true; + info.isValid=true; + + return info; + } + + //kdDebug(7101)<<"Floppy::_stat(): delete m_mtool"<<endl; + if (m_mtool!=0) + delete m_mtool; + + QStringList args; + args<<"mdir"<<"-a"<<(drive+floppyPath); + + //kdDebug(7101)<<"Floppy::_stat(): create m_mtool"<<endl; + m_mtool=new Program(args); + + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mdir"); + return info; + } + + + clearBuffers(); + + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + //kdDebug(7101)<<"Floppy::_stat(): delete m_mtool"<<endl; + delete m_mtool; + m_mtool=0; + //now mdir has finished + //let's parse the output + terminateBuffers(); + + if (errorOccured) + { + info.isValid=false; + return info; + } + + if (m_stdoutSize==0) + { + info.isValid=false; + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; + } + + kdDebug(7101)<<"Floppy::_stat(): parse stuff"<<endl; + QString outputString(m_stdoutBuffer); + QTextIStream output(&outputString); + QString line; + for (int lineNumber=0; !output.atEnd(); lineNumber++) + { + line=output.readLine(); + if ( (lineNumber<3) || (line.isEmpty()) ) + continue; + StatInfo info=createStatInfo(line,true,url.fileName()); + if (info.isValid==false) + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; + } + if (info.isValid==false) + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; +} + +int FloppyProtocol::freeSpace(const KURL& url) +{ + QString path(url.path()); + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + //kdDebug(7101)<<"Floppy::freeSpace(): delete m_mtool"<<endl; + if (m_mtool!=0) + delete m_mtool; + + QStringList args; + args<<"mdir"<<"-a"<<drive; + + //kdDebug(7101)<<"Floppy::freeSpace(): create m_mtool"<<endl; + m_mtool=new Program(args); + + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mdir"); + return -1; + } + + + clearBuffers(); + + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + //kdDebug(7101)<<"Floppy::freeSpace(): delete m_mtool"<<endl; + delete m_mtool; + m_mtool=0; + //now mdir has finished + //let's parse the output + terminateBuffers(); + + if (errorOccured) + { + return -1; + } + + if (m_stdoutSize==0) + { + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return -1; + } + + kdDebug(7101)<<"Floppy::freeSpace(): parse stuff"<<endl; + QString outputString(m_stdoutBuffer); + QTextIStream output(&outputString); + QString line; + int lineNumber(0); + while (!output.atEnd()) + { + line=output.readLine(); + if (line.find("bytes free")==36) + { + QString tmp=line.mid(24,3); + tmp=tmp.stripWhiteSpace(); + tmp+=line.mid(28,3); + tmp=tmp.stripWhiteSpace(); + tmp+=line.mid(32,3); + tmp=tmp.stripWhiteSpace(); + + return tmp.toInt(); + } + lineNumber++; + } + return -1; +} + +void FloppyProtocol::stat( const KURL & _url) +{ + kdDebug(7101)<<"Floppy::stat() "<<_url.path()<<endl; + KURL url(_url); + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + url.setPath("/a/"); + redirection(url); + finished(); + return; + } + StatInfo info=this->_stat(url); + if (info.isValid) + { + UDSEntry entry; + createUDSEntry(info,entry); + statEntry( entry ); + finished(); + //kdDebug(7101)<<"Floppy::stat(): ends"<<endl; + return; + } + //otherwise the error() was already reported in _stat() +} + +void FloppyProtocol::mkdir( const KURL& url, int) +{ + kdDebug(7101)<<"FloppyProtocol::mkdir()"<<endl; + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + + args<<"mmd"<<(drive+floppyPath); + kdDebug(7101)<<"Floppy::mkdir(): executing: mmd -"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mmd"); + return; + } + + + clearBuffers(); + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + terminateBuffers(); + if (errorOccured) + return; + finished(); +} + +void FloppyProtocol::del( const KURL& url, bool isfile) +{ + kdDebug(7101)<<"FloppyProtocol::del()"<<endl; + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + + bool usingmdel; + + if (isfile) + { + args<<"mdel"<<(drive+floppyPath); + usingmdel=true; + } + else + { + args<<"mrd"<<(drive+floppyPath); + usingmdel=false; + } + + kdDebug(7101)<<"Floppy::del(): executing: " << (usingmdel ? QString("mdel") : QString("mrd") ) << "-"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram(usingmdel ? QString("mdel") : QString("mrd")); + return; + } + + + clearBuffers(); + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + terminateBuffers(); + if (errorOccured) + return; + finished(); +} + +void FloppyProtocol::rename( const KURL &src, const KURL &dest, bool _overwrite ) +{ + QString srcPath(src.path()); + QString destPath(dest.path()); + + kdDebug(7101)<<"Floppy::rename() -"<<srcPath<<"- to -"<<destPath<<"-"<<endl; + + if ((srcPath.isEmpty()) || (srcPath=="/")) + srcPath="/a/"; + + if ((destPath.isEmpty()) || (destPath=="/")) + destPath="/a/"; + + QString srcDrive; + QString srcFloppyPath; + getDriveAndPath(srcPath,srcDrive,srcFloppyPath); + if (srcFloppyPath.isEmpty()) + { + finished(); + return; + } + + QString destDrive; + QString destFloppyPath; + getDriveAndPath(destPath,destDrive,destFloppyPath); + if (destFloppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + + if (_overwrite) + args<<"mren"<<"-o"<<(srcDrive+srcFloppyPath)<<(destDrive+destFloppyPath); + else + args<<"mren"<<"-D"<<"s"<<(srcDrive+srcFloppyPath)<<(destDrive+destFloppyPath); + + kdDebug(7101)<<"Floppy::move(): executing: mren -"<<(srcDrive+srcFloppyPath)<<" "<<(destDrive+destFloppyPath)<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mren"); + return; + } + + + clearBuffers(); + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(src,srcDrive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + terminateBuffers(); + if (errorOccured) + return; + finished(); +} + +void FloppyProtocol::get( const KURL& url ) +{ + QString path(url.path()); + kdDebug(7101)<<"Floppy::get() -"<<path<<"-"<<endl; + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + StatInfo info=this->_stat(url); + //the error was already reported in _stat() + if (info.isValid==false) + return; + + totalSize( info.size); + + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + args<<"mcopy"<<(drive+floppyPath)<<"-"; + + kdDebug(7101)<<"Floppy::get(): executing: mcopy -"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mcopy"); + return; + } + + clearBuffers(); + int result; + int bytesRead(0); + QByteArray array; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + { + delete [] m_stdoutBuffer; + m_stdoutBuffer=0; + m_stdoutSize=0; + if (readStdout()>0) + { + kdDebug(7101)<<"Floppy::get(): m_stdoutSize:"<<m_stdoutSize<<endl; + bytesRead+=m_stdoutSize; + array.setRawData(m_stdoutBuffer, m_stdoutSize); + data( array ); + array.resetRawData(m_stdoutBuffer, m_stdoutSize); + + } + else + { + loopFinished=true; + } + } + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + errorOccured=true; + loopFinished=true; + } + } + } while (!loopFinished); + + //kdDebug(7101)<<"Floppy::get(): deleting m_mtool"<<endl; + delete m_mtool; + m_mtool=0; + if (errorOccured) + return; + + //kdDebug(7101)<<"Floppy::get(): finishing"<<endl; + data( QByteArray() ); + finished(); +} + +void FloppyProtocol::put( const KURL& url, int , bool overwrite, bool ) +{ + QString path(url.path()); + kdDebug(7101)<<"Floppy::put() -"<<path<<"-"<<endl; + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + int freeSpaceLeft=freeSpace(url); + if (freeSpaceLeft==-1) + return; + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + if (overwrite) + args<<"mcopy"<<"-o"<<"-"<<(drive+floppyPath); + else + args<<"mcopy"<<"-s"<<"-"<<(drive+floppyPath); + + kdDebug(7101)<<"Floppy::put(): executing: mcopy -"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mcopy"); + return; + } + + + clearBuffers(); + int result(0); + int bytesRead(0); + QByteArray array; + + //from file.cc + // Loop until we got 0 (end of data) + do + { + bool stdoutEvent; + bool stderrEvent; + kdDebug(7101)<<"Floppy::put(): select()..."<<endl; + m_mtool->select(0,100,stdoutEvent, stderrEvent); + if (stdoutEvent) + { + if (readStdout()==0) + result=0; + } + if (stderrEvent) + { + if (readStderr()==0) + result=0; + else + if (stopAfterError(url,drive)) + result=-1; + kdDebug(7101)<<"Floppy::put(): error: result=="<<result<<endl; + } + else + { + QByteArray buffer; + dataReq(); // Request for data + //kdDebug(7101)<<"Floppy::put(): after dataReq()"<<endl; + result = readData( buffer ); + //kdDebug(7101)<<"Floppy::put(): after readData(), read "<<result<<" bytes"<<endl; + if (result > 0) + { + bytesRead+=result; + kdDebug(7101)<<"Floppy::put() bytesRead: "<<bytesRead<<" space: "<<freeSpaceLeft<<endl; + if (bytesRead>freeSpaceLeft) + { + result=0; + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not write to file %1.\nThe disk in drive %2 is probably full.").arg(url.prettyURL(),drive)); + } + else + { + //kdDebug(7101)<<"Floppy::put(): writing..."<<endl; + result=::write(m_mtool->stdinFD(),buffer.data(), buffer.size()); + kdDebug(7101)<<"Floppy::put(): after write(), wrote "<<result<<" bytes"<<endl; + } + } + } + } + while ( result > 0 ); + + if (result<0) + { + perror("writing to stdin"); + error( KIO::ERR_CANNOT_OPEN_FOR_WRITING, url.prettyURL()); + return; + } + + delete m_mtool; + m_mtool=0; + + finished(); +} + diff --git a/kioslave/floppy/kio_floppy.h b/kioslave/floppy/kio_floppy.h new file mode 100644 index 000000000..bfc003b9f --- /dev/null +++ b/kioslave/floppy/kio_floppy.h @@ -0,0 +1,78 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Alexander Neundorf <neundorf@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 KIO_FLOPPY_H +#define KIO_FLOPPY_H + +#include <kio/slavebase.h> +#include <kio/global.h> + +#include "program.h" + +#include <qstring.h> + +struct StatInfo +{ + StatInfo():name(""),time(0),size(0),mode(0),freeSpace(0),isDir(false),isValid(false) {;} + QString name; + time_t time; + int size; + int mode; + int freeSpace; + bool isDir:1; + bool isValid:1; +}; + + +class FloppyProtocol : public KIO::SlaveBase +{ + public: + FloppyProtocol (const QCString &pool, const QCString &app ); + virtual ~FloppyProtocol(); + + virtual void listDir( const KURL& url); + virtual void stat( const KURL & url); + virtual void mkdir( const KURL& url, int); + virtual void del( const KURL& url, bool isfile); + virtual void rename(const KURL &src, const KURL &dest, bool overwrite); + virtual void get( const KURL& url ); + virtual void put( const KURL& url, int _mode,bool overwrite, bool _resume ); + //virtual void copy( const KURL& src, const KURL &dest, int, bool overwrite ); + protected: + Program *m_mtool; + int readStdout(); + int readStderr(); + + StatInfo createStatInfo(const QString line, bool makeStat=false, const QString& dirName=""); + void createUDSEntry(const StatInfo& info, KIO::UDSEntry& entry); + StatInfo _stat(const KURL& _url); + int freeSpace(const KURL& url); + + bool stopAfterError(const KURL& url, const QString& drive); + void errorMissingMToolsProgram(const QString& name); + + void clearBuffers(); + void terminateBuffers(); + char *m_stdoutBuffer; + char *m_stderrBuffer; + int m_stdoutSize; + int m_stderrSize; +}; + +#endif diff --git a/kioslave/floppy/program.cpp b/kioslave/floppy/program.cpp new file mode 100644 index 000000000..7cfd4989a --- /dev/null +++ b/kioslave/floppy/program.cpp @@ -0,0 +1,201 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 Alexander Neundorf <neundorf@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. +*/ + +#include <config.h> +#include "program.h" +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <signal.h> + +#include <kdebug.h> + +Program::Program(const QStringList &args) +:m_pid(0) +,mArgs(args) +,mStarted(false) +{ +} + +Program::~Program() +{ + if (m_pid!=0) + { + ::close(mStdin[0]); + ::close(mStdout[0]); + ::close(mStderr[0]); + + ::close(mStdin[1]); + ::close(mStdout[1]); + ::close(mStderr[1]); + + int s(0); + //::wait(&s); + ::waitpid(m_pid,&s,0); + this->kill(); + ::waitpid(m_pid,&s,WNOHANG); + }; +} + +bool Program::start() +{ + if (mStarted) return false; + if (pipe(mStdout)==-1) return false; + if (pipe(mStdin )==-1) return false; + if (pipe(mStderr )==-1) return false; + + int notificationPipe[2]; + if (pipe(notificationPipe )==-1) return false; + + m_pid=fork(); + + if (m_pid>0) + { + //parent + ::close(mStdin[0]); + ::close(mStdout[1]); + ::close(mStderr[1]); + ::close(notificationPipe[1]); + mStarted=true; + fd_set notifSet; + FD_ZERO(¬ifSet); + FD_SET(notificationPipe[0],¬ifSet); + struct timeval tv; + //wait up to five seconds + + kdDebug(7101)<<"**** waiting for notification"<<endl; + //0.2 sec + tv.tv_sec=0; + tv.tv_usec=1000*200; + int result=::select(notificationPipe[0]+1,¬ifSet,0,0,&tv); +/* if (result<1) + { + kdDebug(7101)<<"**** waiting for notification: failed "<<result<<endl; + return false; + } + else*/ + if(result==1) + { + char buf[256]; + result=::read(notificationPipe[0],buf,256); + //if execvp() failed the child sends us "failed" + if (result>0) + return false; + }; + kdDebug(7101)<<"**** waiting for notification: succeeded"<<result<<endl; + return true; + } + else if (m_pid==-1) + { + //failed + return false; + } + else if (m_pid==0) + { + ::close(notificationPipe[0]); + + //child + ::close(0); // close the stdios + ::close(1); + ::close(2); + + dup(mStdin[0]); + dup(mStdout[1]); + dup(mStderr[1]); + + ::close(mStdin[1]); + ::close(mStdout[0]); + ::close(mStderr[0]); + + fcntl(mStdin[0], F_SETFD, FD_CLOEXEC); + fcntl(mStdout[1], F_SETFD, FD_CLOEXEC); + fcntl(mStderr[1], F_SETFD, FD_CLOEXEC); + + char **arglist=(char**)malloc((mArgs.count()+1)*sizeof(char*)); + int c=0; + + for (QStringList::Iterator it=mArgs.begin(); it!=mArgs.end(); ++it) + { + arglist[c]=(char*)malloc((*it).length()+1); + strcpy(arglist[c], (*it).latin1()); + c++; + } + arglist[mArgs.count()]=0; + //make parsing easier + putenv(strdup("LANG=C")); + execvp(arglist[0], arglist); + //we only get here if execvp() failed + ::write(notificationPipe[1],"failed",strlen("failed")); + ::close(notificationPipe[1]); + _exit(-1); + }; + return false; +} + +bool Program::isRunning() +{ + return mStarted; +} + +int Program::select(int secs, int usecs, bool& stdoutReceived, bool& stderrReceived/*, bool& stdinWaiting*/) +{ + stdoutReceived=false; + stderrReceived=false; + + struct timeval tv; + tv.tv_sec=secs; + tv.tv_usec=usecs; + + fd_set readFDs; + FD_ZERO(&readFDs); + FD_SET(stdoutFD(),&readFDs); + FD_SET(stderrFD(),&readFDs); + + int maxFD=stdoutFD(); + if (stderrFD()>maxFD) maxFD=stderrFD(); + + /*fd_set writeFDs; + FD_ZERO(&writeFDs); + FD_SET(stdinFD(),&writeFDs); + if (stdinFD()>maxFD) maxFD=stdinFD();*/ + maxFD++; + + int result=::select(maxFD,&readFDs,/*&writeFDs*/0,0,&tv); + if (result>0) + { + stdoutReceived=FD_ISSET(stdoutFD(),&readFDs); + stderrReceived=FD_ISSET(stderrFD(),&readFDs); + //stdinWaiting=(FD_ISSET(stdinFD(),&writeFDs)); + }; + return result; +} + +int Program::kill() +{ + if (m_pid==0) + return -1; + return ::kill(m_pid, SIGTERM); + //::kill(m_pid, SIGKILL); +} + diff --git a/kioslave/floppy/program.h b/kioslave/floppy/program.h new file mode 100644 index 000000000..29ea634ad --- /dev/null +++ b/kioslave/floppy/program.h @@ -0,0 +1,53 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 Alexander Neundorf <neundorf@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 PROGRAM_H +#define PROGRAM_H + +#include <qstringlist.h> + +/** + * start programs and write to thieir stdin, stderr, + * and read from stdout + **/ +class Program +{ +public: + Program(const QStringList &args); + ~Program(); + bool start(); + bool isRunning(); + + int stdinFD() {return mStdin[1];} + int stdoutFD() {return mStdout[0];} + int stderrFD() {return mStderr[0];} + int pid() {return m_pid;} + int kill(); + int select(int secs, int usecs, bool& stdoutReceived, bool& stderrReceived/*, bool& stdinWaiting*/); +protected: + int mStdout[2]; + int mStdin[2]; + int mStderr[2]; + int m_pid; + QStringList mArgs; + bool mStarted; +}; + +#endif + diff --git a/kioslave/home/Makefile.am b/kioslave/home/Makefile.am new file mode 100644 index 000000000..be10e5bcc --- /dev/null +++ b/kioslave/home/Makefile.am @@ -0,0 +1,32 @@ +SUBDIRS= . kdedmodule +# wizard + +INCLUDES = $(all_includes) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kio_home.la + +kio_home_la_SOURCES = dummy.cpp +kio_home_la_LIBADD = libkiohome.la $(LIB_KIO) +kio_home_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined + +dummy.cpp: + echo > dummy.cpp + +kde_services_DATA = home.protocol + +noinst_LTLIBRARIES = libkiohome.la +libkiohome_la_SOURCES = kio_home.cpp homeimpl.cpp + +check_PROGRAMS = testhome +testhome_SOURCES = testhome.cpp +testhome_LDADD = libkiohome.la $(LIB_KIO) +testhome_LDFLAGS = $(all_libraries) + +## TODO in unsermake: TESTS = testhome +check: testhome + ./testhome + +messages: + $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/kio_home.pot + diff --git a/kioslave/home/dummy.cpp b/kioslave/home/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/kioslave/home/dummy.cpp @@ -0,0 +1 @@ + diff --git a/kioslave/home/home.protocol b/kioslave/home/home.protocol new file mode 100644 index 000000000..59f6cba5d --- /dev/null +++ b/kioslave/home/home.protocol @@ -0,0 +1,19 @@ +[Protocol] +exec=kio_home +protocol=home +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 +Icon=folder_home +maxInstances=4 +#TODO DocPath=kioslave/file.html +Class=:local +Parent=system:/ +deleteRecursive=true +fileNameUsedForCopying=Name diff --git a/kioslave/home/homeimpl.cpp b/kioslave/home/homeimpl.cpp new file mode 100644 index 000000000..7e86173ba --- /dev/null +++ b/kioslave/home/homeimpl.cpp @@ -0,0 +1,228 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "homeimpl.h" + +#include <kdebug.h> +#include <qapplication.h> +#include <qeventloop.h> + +#include <sys/stat.h> + +#define MINIMUM_UID 500 + +HomeImpl::HomeImpl() +{ + KUser user; + m_effectiveUid = user.uid(); +} + +bool HomeImpl::parseURL(const KURL &url, QString &name, QString &path) const +{ + QString url_path = url.path(); + + int i = url_path.find('/', 1); + if (i > 0) + { + name = url_path.mid(1, i-1); + path = url_path.mid(i+1); + } + else + { + name = url_path.mid(1); + path = QString::null; + } + + return name != QString::null; +} + +bool HomeImpl::realURL(const QString &name, const QString &path, KURL &url) +{ + KUser user(name); + + if ( user.isValid() ) + { + KURL res; + res.setPath( user.homeDir() ); + res.addPath(path); + url = res; + return true; + } + + return false; +} + + +bool HomeImpl::listHomes(QValueList<KIO::UDSEntry> &list) +{ + kdDebug() << "HomeImpl::listHomes" << endl; + + KUser current_user; + QValueList<KUserGroup> groups = current_user.groups(); + QValueList<int> uid_list; + + QValueList<KUserGroup>::iterator groups_it = groups.begin(); + QValueList<KUserGroup>::iterator groups_end = groups.end(); + + for(; groups_it!=groups_end; ++groups_it) + { + QValueList<KUser> users = (*groups_it).users(); + + QValueList<KUser>::iterator it = users.begin(); + QValueList<KUser>::iterator users_end = users.end(); + + for(; it!=users_end; ++it) + { + if ((*it).uid()>=MINIMUM_UID + && !uid_list.contains( (*it).uid() ) ) + { + uid_list.append( (*it).uid() ); + KIO::UDSEntry entry; + createHomeEntry(entry, *it); + list.append(entry); + } + } + } + + return true; +} + +static void addAtom(KIO::UDSEntry &entry, unsigned int ID, long l, + const QString &s = QString::null) +{ + KIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void HomeImpl::createTopLevelEntry(KIO::UDSEntry &entry) const +{ + entry.clear(); + addAtom(entry, KIO::UDS_NAME, 0, "."); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_ACCESS, 0555); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, KIO::UDS_ICON_NAME, 0, "kfm_home"); + addAtom(entry, KIO::UDS_USER, 0, "root"); + addAtom(entry, KIO::UDS_GROUP, 0, "root"); +} + +void HomeImpl::createHomeEntry(KIO::UDSEntry &entry, + const KUser &user) +{ + kdDebug() << "HomeImpl::createHomeEntry" << endl; + + entry.clear(); + + QString full_name = user.loginName(); + + if (!user.fullName().isEmpty()) + { + full_name = user.fullName()+" ("+user.loginName()+")"; + } + + full_name = KIO::encodeFileName( full_name ); + + addAtom(entry, KIO::UDS_NAME, 0, full_name); + addAtom(entry, KIO::UDS_URL, 0, "home:/"+user.loginName()); + + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + + QString icon_name = "folder_home2"; + + if (user.uid()==m_effectiveUid) + { + icon_name = "folder_home"; + } + + addAtom(entry, KIO::UDS_ICON_NAME, 0, icon_name); + + KURL url; + url.setPath(user.homeDir()); + entry += extractUrlInfos(url); +} + +bool HomeImpl::statHome(const QString &name, KIO::UDSEntry &entry) +{ + kdDebug() << "HomeImpl::statHome: " << name << endl; + + KUser user(name); + + if (user.isValid()) + { + createHomeEntry(entry, user); + return true; + } + + return false; +} + +void HomeImpl::slotStatResult(KIO::Job *job) +{ + if ( job->error() == 0) + { + KIO::StatJob *stat_job = static_cast<KIO::StatJob *>(job); + m_entryBuffer = stat_job->statResult(); + } + + qApp->eventLoop()->exitLoop(); +} + +KIO::UDSEntry HomeImpl::extractUrlInfos(const KURL &url) +{ + m_entryBuffer.clear(); + + KIO::StatJob *job = KIO::stat(url, false); + connect( job, SIGNAL( result(KIO::Job *) ), + this, SLOT( slotStatResult(KIO::Job *) ) ); + qApp->eventLoop()->enterLoop(); + + KIO::UDSEntry::iterator it = m_entryBuffer.begin(); + KIO::UDSEntry::iterator end = m_entryBuffer.end(); + + KIO::UDSEntry infos; + + for(; it!=end; ++it) + { + switch( (*it).m_uds ) + { + case KIO::UDS_ACCESS: + case KIO::UDS_USER: + case KIO::UDS_GROUP: + case KIO::UDS_CREATION_TIME: + case KIO::UDS_MODIFICATION_TIME: + case KIO::UDS_ACCESS_TIME: + infos.append(*it); + break; + default: + break; + } + } + + addAtom(infos, KIO::UDS_LOCAL_PATH, 0, url.path()); + + return infos; +} + +#include "homeimpl.moc" + diff --git a/kioslave/home/homeimpl.h b/kioslave/home/homeimpl.h new file mode 100644 index 000000000..8c4ace279 --- /dev/null +++ b/kioslave/home/homeimpl.h @@ -0,0 +1,57 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + 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 HOMEIMPL_H +#define HOMEIMPL_H + +#include <kio/global.h> +#include <kio/job.h> +#include <kurl.h> +#include <kuser.h> + +#include <qstring.h> + +class HomeImpl : public QObject +{ +Q_OBJECT + +public: + HomeImpl(); + bool parseURL(const KURL &url, QString &name, QString &path) const; + bool realURL(const QString &name, const QString &path, KURL &url); + + bool statHome(const QString &name, KIO::UDSEntry &entry); + bool listHomes(QValueList<KIO::UDSEntry> &list); + + void createTopLevelEntry(KIO::UDSEntry &entry) const; + +private slots: + void slotStatResult(KIO::Job *job); + +private: + void createHomeEntry(KIO::UDSEntry& entry, const KUser &user); + + KIO::UDSEntry extractUrlInfos(const KURL &url); + KIO::UDSEntry m_entryBuffer; + + + long m_effectiveUid; +}; + +#endif diff --git a/kioslave/home/kdedmodule/Makefile.am b/kioslave/home/kdedmodule/Makefile.am new file mode 100644 index 000000000..3d7a54f9b --- /dev/null +++ b/kioslave/home/kdedmodule/Makefile.am @@ -0,0 +1,13 @@ +kde_module_LTLIBRARIES = kded_homedirnotify.la + +METASOURCES = AUTO +INCLUDES = $(all_includes) + +kded_homedirnotify_la_SOURCES = homedirnotify.cpp homedirnotify.skel homedirnotifymodule.cpp homedirnotifymodule.skel +kded_homedirnotify_la_LDFLAGS = $(all_libraries) -module -avoid-version +kded_homedirnotify_la_LIBADD = $(LIB_KSYCOCA) + + +servicesdir = $(kde_servicesdir)/kded +services_DATA = homedirnotify.desktop + diff --git a/kioslave/home/kdedmodule/homedirnotify.cpp b/kioslave/home/kdedmodule/homedirnotify.cpp new file mode 100644 index 000000000..e4eab44bb --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotify.cpp @@ -0,0 +1,185 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "homedirnotify.h" + +#include <kdebug.h> +#include <kuser.h> + +#include <kdirnotify_stub.h> + +#define MINIMUM_UID 500 + +HomeDirNotify::HomeDirNotify() +: mInited( false ) +{ +} + +void HomeDirNotify::init() +{ + if( mInited ) + return; + mInited = true; + + KUser current_user; + QValueList<KUserGroup> groups = current_user.groups(); + QValueList<int> uid_list; + + QValueList<KUserGroup>::iterator groups_it = groups.begin(); + QValueList<KUserGroup>::iterator groups_end = groups.end(); + + for(; groups_it!=groups_end; ++groups_it) + { + QValueList<KUser> users = (*groups_it).users(); + + QValueList<KUser>::iterator it = users.begin(); + QValueList<KUser>::iterator users_end = users.end(); + + for(; it!=users_end; ++it) + { + if ((*it).uid()>=MINIMUM_UID + && !uid_list.contains( (*it).uid() ) ) + { + uid_list.append( (*it).uid() ); + + QString name = (*it).loginName(); + KURL url; + url.setPath( (*it).homeDir() ); + + m_homeFoldersMap[name] = url; + } + } + } +} + +KURL HomeDirNotify::toHomeURL(const KURL &url) +{ + kdDebug() << "HomeDirNotify::toHomeURL(" << url << ")" << endl; + + init(); + QMap<QString,KURL>::iterator it = m_homeFoldersMap.begin(); + QMap<QString,KURL>::iterator end = m_homeFoldersMap.end(); + + for (; it!=end; ++it) + { + QString name = it.key(); + KURL base = it.data(); + + if ( base.isParentOf(url) ) + { + QString path = KURL::relativePath(base.path(), + url.path()); + KURL result("home:/"+name+"/"+path); + result.cleanPath(); + kdDebug() << "result => " << result << endl; + return result; + } + } + + kdDebug() << "result => KURL()" << endl; + return KURL(); +} + +KURL::List HomeDirNotify::toHomeURLList(const KURL::List &list) +{ + init(); + KURL::List new_list; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL url = toHomeURL(*it); + + if (url.isValid()) + { + new_list.append(url); + } + } + + return new_list; +} + +ASYNC HomeDirNotify::FilesAdded(const KURL &directory) +{ + kdDebug() << "HomeDirNotify::FilesAdded" << endl; + + KURL new_dir = toHomeURL(directory); + + if (new_dir.isValid()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesAdded( new_dir ); + } +} + +// This hack is required because of the way we manage .desktop files with +// Forwarding Slaves, their URL is out of the ioslave (some home:/ files +// have a file:/ based UDS_URL so that they are executed correctly. +// Hence, FilesRemoved and FilesChanged does nothing... We're forced to use +// FilesAdded to re-list the modified directory. +inline void evil_hack(const KURL::List &list) +{ + KDirNotify_stub notifier("*", "*"); + + KURL::List notified; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL url = (*it).upURL(); + + if (!notified.contains(url)) + { + notifier.FilesAdded(url); + notified.append(url); + } + } +} + + +ASYNC HomeDirNotify::FilesRemoved(const KURL::List &fileList) +{ + kdDebug() << "HomeDirNotify::FilesRemoved" << endl; + + KURL::List new_list = toHomeURLList(fileList); + + if (!new_list.isEmpty()) + { + //KDirNotify_stub notifier("*", "*"); + //notifier.FilesRemoved( new_list ); + evil_hack(new_list); + } +} + +ASYNC HomeDirNotify::FilesChanged(const KURL::List &fileList) +{ + kdDebug() << "HomeDirNotify::FilesChanged" << endl; + + KURL::List new_list = toHomeURLList(fileList); + + if (!new_list.isEmpty()) + { + //KDirNotify_stub notifier("*", "*"); + //notifier.FilesChanged( new_list ); + evil_hack(new_list); + } +} diff --git a/kioslave/home/kdedmodule/homedirnotify.desktop b/kioslave/home/kdedmodule/homedirnotify.desktop new file mode 100644 index 000000000..c931748d1 --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotify.desktop @@ -0,0 +1,60 @@ +[Desktop Entry] +Type=Service +Name=KDED Home Base URL Notifier +Name[af]=KDED tuis URL inkennissteller +Name[be]=Праверка зменаў мÑÑцовых файлаў KDED +Name[bs]=KDED lokalno obavjeÅ¡tenje o baznom URLu +Name[ca]=Notificador KDED de l'URL d'inici +Name[cs]=Démon upozorňovánà na domovské URL +Name[csb]=Dôwanié wiédzë o URL-ach domôcegò katalogù dlô KDED +Name[da]=KDED Hjemmebasis-URL pÃ¥mindelser +Name[de]=Überwachung für persönliche Ordner +Name[el]=KDED ειδοποιητής URL αÏχικής βάσης +Name[eo]=KDED Hejmo Bazo URL Avertilo +Name[es]=Notificador de URL de KDED +Name[et]=KDED kodu baas-URLi teadustaja +Name[eu]=KDED hasierako URL oinarriaren iragarlea +Name[fa]=اخطاردهندۀ نشانی وب پایۀ آغازۀ KDED +Name[fi]=KDED:in etä-verkko-osoitteen ilmaisin +Name[fr]=Notification de l'URL de base KDED +Name[fy]=KDED Thúsbasis-URL-adres notifikaasje +Name[gl]=Notificador de URL base de KDED +Name[hr]=KDED URL obavjeÅ¡tavanje domaće baze +Name[hu]=ÉrtesÃtÅ‘ a KDE saját URL-hez +Name[is]=KDED grunnslóðar tilkynnari +Name[it]=Notifica KDED Home Base URL +Name[ja]=KDED ホームベース URL Nofitier +Name[ka]=KDED ძირითáƒáƒ“ი თáƒáƒ•ფურცლის URL შემტყáƒáƒ‘ინებელი +Name[kk]=KDED Home Base URL құлақтандыру +Name[km]=KDED Remote Base URL Notifier +Name[ko]=KDED ì›ê²© 기반 URL 알리미 +Name[lt]=KDED pagrindinio namų URL priminiklis +Name[nb]=KDED-pÃ¥minner for eksterne nettadresser +Name[nds]=KDED-Narichten för Tohuusorner-URLs +Name[ne]=KDED गृह आधारित यूआरà¤à¤² सूचक +Name[nl]=KDED Thuisbasis-URL-adres notificatie +Name[nn]=KDED-varsel for heimebase +Name[pa]=KDED ਮà©à©±à¨– ਅਧਾਰ URL ਸੂਚਕ +Name[pl]=Powiadamianie o URL-ach katalogu domowego dla KDED +Name[pt]=Notificador de URLs de Base Remotos do KDED +Name[pt_BR]=Serviço de Notificação da URL do KDED +Name[ro]=Notificare KDED pentru URL acasă +Name[ru]=Уведомление о Ñмене базового адреÑа KDED +Name[sk]=KDED notifikátor domovskej URL +Name[sl]=Obvestilnik KDED domaÄega osnovnega URL-ja +Name[sr]=Обавештавач о домаћем базном URL-у, KDED +Name[sr@Latn]=ObaveÅ¡tavaÄ o domaćem baznom URL-u, KDED +Name[sv]=KDED-meddelande om hembaswebbadresser +Name[th]=ตัวà¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™ Home Base URL KDED +Name[tr]=KDED Yerel Tabanlı URL Hatırlatıcı +Name[uk]=Сповіщувач домашньої базової адреÑи URL KDED +Name[vi]=Trình thông báo URL trong máy KDED +Name[wa]=Notifieu di l' URL di bÃ¥ze del mÃ¥jhon KDED +Name[zh_CN]=KDED 主页基 URL 通知器 +Name[zh_TW]=KDED 家用基礎 URL é€šçŸ¥ç¨‹å¼ +ServiceTypes=KDEDModule +X-KDE-ModuleType=Library +X-KDE-Library=homedirnotify +X-KDE-FactoryName=homedirnotify +X-KDE-Kded-load-on-demand=true +X-KDE-Kded-autoload=true diff --git a/kioslave/home/kdedmodule/homedirnotify.h b/kioslave/home/kdedmodule/homedirnotify.h new file mode 100644 index 000000000..14655a1cd --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotify.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 HOMEDIRNOTIFY_H +#define HOMEDIRNOTIFY_H + +#include <kurl.h> +#include <kdirnotify.h> + +#include <qmap.h> + +class HomeDirNotify : public KDirNotify +{ +K_DCOP + +public: + HomeDirNotify(); + +k_dcop: + virtual ASYNC FilesAdded (const KURL &directory); + virtual ASYNC FilesRemoved (const KURL::List &fileList); + virtual ASYNC FilesChanged (const KURL::List &fileList); + +private: + void init(); + KURL toHomeURL(const KURL &url); + KURL::List toHomeURLList(const KURL::List &list); + + QMap<QString,KURL> m_homeFoldersMap; + bool mInited; +}; + +#endif diff --git a/kioslave/home/kdedmodule/homedirnotifymodule.cpp b/kioslave/home/kdedmodule/homedirnotifymodule.cpp new file mode 100644 index 000000000..1b91ccc31 --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotifymodule.cpp @@ -0,0 +1,37 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "homedirnotifymodule.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> + +HomeDirNotifyModule::HomeDirNotifyModule(const QCString &obj) + : KDEDModule(obj) +{ +} + +extern "C" { + KDE_EXPORT KDEDModule *create_homedirnotify(const QCString &obj) + { + KGlobal::locale()->insertCatalogue("kio_home"); + return new HomeDirNotifyModule(obj); + } +} + diff --git a/kioslave/home/kdedmodule/homedirnotifymodule.h b/kioslave/home/kdedmodule/homedirnotifymodule.h new file mode 100644 index 000000000..159670fb6 --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotifymodule.h @@ -0,0 +1,36 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 HOMEDIRNOTIFYMODULE_H +#define HOMEDIRNOTIFYMODULE_H + +#include <kdedmodule.h> + +#include "homedirnotify.h" + +class HomeDirNotifyModule : public KDEDModule +{ +K_DCOP + +public: + HomeDirNotifyModule(const QCString &obj); +private: + HomeDirNotify notifier; +}; + +#endif diff --git a/kioslave/home/kio_home.cpp b/kioslave/home/kio_home.cpp new file mode 100644 index 000000000..36a3161d8 --- /dev/null +++ b/kioslave/home/kio_home.cpp @@ -0,0 +1,186 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <stdlib.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <kcmdlineargs.h> +#include <kglobal.h> + + +#include "kio_home.h" + +static const KCmdLineOptions options[] = +{ + { "+protocol", I18N_NOOP( "Protocol name" ), 0 }, + { "+pool", I18N_NOOP( "Socket name" ), 0 }, + { "+app", I18N_NOOP( "Socket name" ), 0 }, + KCmdLineLastOption +}; + +extern "C" { + int KDE_EXPORT kdemain( int argc, char **argv ) + { + // KApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + KCmdLineArgs::init(argc, argv, "kio_home", 0, 0, 0, 0); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + HomeProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +HomeProtocol::HomeProtocol(const QCString &protocol, + const QCString &pool, const QCString &app) + : ForwardingSlaveBase(protocol, pool, app) +{ +} + +HomeProtocol::~HomeProtocol() +{ +} + +bool HomeProtocol::rewriteURL(const KURL &url, KURL &newUrl) +{ + QString name, path; + + if ( !m_impl.parseURL(url, name, path) ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return false; + } + + + if ( !m_impl.realURL(name, path, newUrl) ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return false; + } + + return true; +} + + +void HomeProtocol::listDir(const KURL &url) +{ + kdDebug() << "HomeProtocol::listDir: " << url << endl; + + if ( url.path().length() <= 1 ) + { + listRoot(); + return; + } + + QString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + ForwardingSlaveBase::listDir(url); +} + +void HomeProtocol::listRoot() +{ + KIO::UDSEntry entry; + + KIO::UDSEntryList home_entries; + bool ok = m_impl.listHomes(home_entries); + + if (!ok) // can't happen + { + error(KIO::ERR_UNKNOWN, ""); + return; + } + + totalSize(home_entries.count()+1); + + m_impl.createTopLevelEntry(entry); + listEntry(entry, false); + + KIO::UDSEntryListIterator it = home_entries.begin(); + KIO::UDSEntryListIterator end = home_entries.end(); + + for(; it!=end; ++it) + { + listEntry(*it, false); + } + + entry.clear(); + listEntry(entry, true); + + finished(); +} + +void HomeProtocol::stat(const KURL &url) +{ + kdDebug() << "HomeProtocol::stat: " << url << endl; + + QString path = url.path(); + if ( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + KIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + QString name; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if( path.isEmpty() ) + { + KIO::UDSEntry entry; + + if ( m_impl.statHome(name, entry) ) + { + statEntry(entry); + finished(); + } + else + { + error(KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + } + else + { + ForwardingSlaveBase::stat(url); + } +} diff --git a/kioslave/home/kio_home.h b/kioslave/home/kio_home.h new file mode 100644 index 000000000..1d5e237ea --- /dev/null +++ b/kioslave/home/kio_home.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + 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 KIO_HOME_H +#define KIO_HOME_H + +#include <kio/forwardingslavebase.h> +#include "homeimpl.h" + +class HomeProtocol : public KIO::ForwardingSlaveBase +{ +public: + HomeProtocol(const QCString &protocol, const QCString &pool, + const QCString &app); + virtual ~HomeProtocol(); + + virtual bool rewriteURL(const KURL &url, KURL &newUrl); + + virtual void listDir(const KURL &url); + virtual void stat(const KURL &url); + +private: + void listRoot(); + + HomeImpl m_impl; +}; + +#endif diff --git a/kioslave/home/testhome.cpp b/kioslave/home/testhome.cpp new file mode 100644 index 000000000..e9d64ec68 --- /dev/null +++ b/kioslave/home/testhome.cpp @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kio_home.h" +#include "testhome.h" + +#include <config.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#include <stdlib.h> + +static bool check(const QString& txt, QString a, QString b) +{ + if (a.isEmpty()) + a = QString::null; + if (b.isEmpty()) + b = QString::null; + if (a == b) { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +int main(int argc, char *argv[]) +{ + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc,argv,"testhome", 0, 0, 0, 0); + KApplication app; + + TestHome test; + test.setup(); + test.runAll(); + kdDebug() << "All tests OK." << endl; + return 0; // success. The exit(1) in check() is what happens in case of failure. +} + +void TestHome::setup() +{ + +} + +void TestHome::runAll() +{ + +} + diff --git a/kioslave/home/testhome.h b/kioslave/home/testhome.h new file mode 100644 index 000000000..dd8b257e3 --- /dev/null +++ b/kioslave/home/testhome.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 TESTHOME_H +#define TESTHOME_H + +class TestHome +{ +public: + TestHome() {} + void setup(); + void runAll(); + + // tests + +}; + +#endif diff --git a/kioslave/info/LICENSE b/kioslave/info/LICENSE new file mode 100644 index 000000000..9cb70ac77 --- /dev/null +++ b/kioslave/info/LICENSE @@ -0,0 +1,22 @@ +The following license is applicable to all files in this directory, with the +exception of kde-info2html and kde-info2html.conf which are licensed under the GPL, +since they are based on GPL work. + +LICENSE: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/info/Makefile.am b/kioslave/info/Makefile.am new file mode 100644 index 000000000..8db4a20ec --- /dev/null +++ b/kioslave/info/Makefile.am @@ -0,0 +1,21 @@ +## Makefile.am of kdebase/kioslave/info + +INCLUDES = $(all_includes) + +METASOURCES = AUTO + +####### Files + +kde_module_LTLIBRARIES = kio_info.la + +kio_info_la_SOURCES = info.cc +kio_info_la_LIBADD = $(LIB_KIO) +kio_info_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = info.h + +kdelnk_DATA = info.protocol +kdelnkdir = $(kde_servicesdir) + +kio_info_data_DATA = kde-info2html.conf +kio_info_data_SCRIPTS = kde-info2html +kio_info_datadir = $(kde_datadir)/kio_info diff --git a/kioslave/info/info.cc b/kioslave/info/info.cc new file mode 100644 index 000000000..6b829ec1c --- /dev/null +++ b/kioslave/info/info.cc @@ -0,0 +1,261 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <qdir.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kprocess.h> +#include <kstandarddirs.h> +#include <kiconloader.h> +#include <kinstance.h> +#include <klocale.h> + +#include "info.h" + +using namespace KIO; + +InfoProtocol::InfoProtocol( const QCString &pool, const QCString &app ) + : SlaveBase( "info", pool, app ) + , m_page( "" ) + , m_node( "" ) +{ + kdDebug( 7108 ) << "InfoProtocol::InfoProtocol" << endl; + + m_perl = KGlobal::dirs()->findExe( "perl" ); + m_infoScript = locate( "data", "kio_info/kde-info2html" ); + m_infoConf = locate("data", "kio_info/kde-info2html.conf"); + + if( m_perl.isNull() || m_infoScript.isNull() || m_infoConf.isNull() ) { + kdError( 7108 ) << "Critical error: Cannot locate files for HTML-conversion" << endl; + QString errorStr; + if ( m_perl.isNull() ) { + errorStr = "perl."; + } else { + QString missing =m_infoScript.isNull() ? "kio_info/kde-info2html" : "kio_info/kde-info2html.conf"; + errorStr = "kde-info2html" + i18n( "\nUnable to locate file %1 which is necessary to run this service. " + "Please check your software installation" ).arg( missing ); + } + error( KIO::ERR_CANNOT_LAUNCH_PROCESS, errorStr ); + exit(); + } + + kdDebug( 7108 ) << "InfoProtocol::InfoProtocol - done" << endl; +} + +InfoProtocol::~InfoProtocol() +{ + kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol" << endl; + + kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol - done" << endl; +} + +void InfoProtocol::get( const KURL& url ) +{ + kdDebug( 7108 ) << "InfoProtocol::get" << endl; + kdDebug( 7108 ) << "URL: " << url.prettyURL() << " , Path :" << url.path() << endl; + + if (url.path()=="/") + { + KURL newUrl("info:/dir"); + redirection(newUrl); + finished(); + return; + }; + + // some people write info://autoconf instead of info:/autoconf + if (!url.host().isEmpty()) { + KURL newURl(url); + newURl.setPath(url.host()+url.path()); + newURl.setHost(QString::null); + redirection(newURl); + finished(); + return; + } + + if ( url.path().right(1) == "/" ) + { + // Trailing / are not supported, so we need to remove them. + KURL newUrl( url ); + QString newPath( url.path() ); + newPath.truncate( newPath.length()-1 ); + newUrl.setPath( newPath ); + redirection( newUrl ); + finished(); + return; + } + + mimeType("text/html"); + // extract the path and node from url + decodeURL( url ); + + QString path = KGlobal::iconLoader()->iconPath("up", KIcon::Toolbar, true); + int revindex = path.findRev('/'); + path = path.left(revindex); + + QString cmd = KProcess::quote(m_perl); + cmd += " "; + cmd += KProcess::quote(m_infoScript); + cmd += " "; + cmd += KProcess::quote(m_infoConf); + cmd += " "; + cmd += KProcess::quote(path); + cmd += " "; + cmd += KProcess::quote(m_page); + cmd += " "; + cmd += KProcess::quote(m_node); + + kdDebug( 7108 ) << "cmd: " << cmd << endl; + + FILE *file = popen( QFile::encodeName(cmd), "r" ); + if ( !file ) { + kdDebug( 7108 ) << "InfoProtocol::get popen failed" << endl; + error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); + return; + } + + char buffer[ 4096 ]; + QByteArray array; + + bool empty = true; + while ( !feof( file ) ) + { + int n = fread( buffer, 1, sizeof( buffer ), file ); + if ( !n && feof( file ) && empty ) { + error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); + return; + } + if ( n < 0 ) + { + // ERROR + kdDebug( 7108 ) << "InfoProtocol::get ERROR!" << endl; + pclose( file ); + return; + } + + empty = false; + array.setRawData( buffer, n ); + data( array ); + array.resetRawData( buffer, n ); + } + + pclose( file ); + + finished(); + + kdDebug( 7108 ) << "InfoProtocol::get - done" << endl; +} + +void InfoProtocol::mimetype( const KURL& /* url */ ) +{ + kdDebug( 7108 ) << "InfoProtocol::mimetype" << endl; + + // to get rid of those "Open with" dialogs... + mimeType( "text/html" ); + + // finish action + finished(); + + kdDebug( 7108 ) << "InfoProtocol::mimetype - done" << endl; +} + +void InfoProtocol::decodeURL( const KURL &url ) +{ + kdDebug( 7108 ) << "InfoProtocol::decodeURL" << endl; + + /* Notes: + * + * I cleaned up the URL decoding and chose not to support URLs in the + * form "info:/usr/local/share/info/libc.info.gz" or similar which the + * older code attempted (and failed, maybe it had worked once) to do. + * + * The reason is that an obvious use such as viewing a info file off your + * infopath would work for the first page, but then all the links would be + * wrong. Of course, one could change kde-info2html to make it work, but I don't + * think it worthy, others are free to disagree and write the necessary code ;) + * + * luis pedro + */ + + if ( url == KURL( "info:/browse_by_file?special=yes" ) ) { + m_page = "#special#"; + m_node = "browse_by_file"; + kdDebug( 7108 ) << "InfoProtocol::decodeURL - special - browse by file" << endl; + return; + } + + decodePath( url.path() ); + + kdDebug( 7108 ) << "InfoProtocol::decodeURL - done" << endl; +} + +void InfoProtocol::decodePath( QString path ) +{ + kdDebug( 7108 ) << "InfoProtocol::decodePath(-" <<path<<"-)"<< endl; + + m_page = "dir"; //default + m_node = ""; + + // remove leading slash + if ('/' == path[0]) { + path = path.mid( 1 ); + } + //kdDebug( 7108 ) << "Path: " << path << endl; + + int slashPos = path.find( "/" ); + + if( slashPos < 0 ) + { + m_page = path; + m_node = "Top"; + return; + } + + m_page = path.left( slashPos ); + + // remove leading+trailing whitespace + m_node = path.right( path.length() - slashPos - 1).stripWhiteSpace (); + + kdDebug( 7108 ) << "InfoProtocol::decodePath - done" << endl; +} + +// A minimalistic stat with only the file type +// This seems to be enough for konqueror +void InfoProtocol::stat( const KURL & ) +{ + UDSEntry uds_entry; + UDSAtom uds_atom; + + // Regular file with rwx permission for all + uds_atom.m_uds = KIO::UDS_FILE_TYPE; + uds_atom.m_long = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; + + uds_entry.append( uds_atom ); + + statEntry( uds_entry ); + + finished(); +} + +extern "C" { int KDE_EXPORT kdemain( int argc, char **argv ); } + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_info" ); + + kdDebug() << "kio_info starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_info protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + InfoProtocol slave( argv[2], argv[3] ); + slave.dispatchLoop(); + + return 0; +} diff --git a/kioslave/info/info.h b/kioslave/info/info.h new file mode 100644 index 000000000..ccf41fc5e --- /dev/null +++ b/kioslave/info/info.h @@ -0,0 +1,36 @@ +#ifndef __info_h__ +#define __info_h__ + +#include <qobject.h> + +#include <kio/slavebase.h> + +class KProcess; + +class InfoProtocol : public KIO::SlaveBase +{ +public: + + InfoProtocol( const QCString &pool, const QCString &app ); + virtual ~InfoProtocol(); + + virtual void get( const KURL& url ); + virtual void stat( const KURL& url ); + virtual void mimetype( const KURL& url ); + +protected: + + void decodeURL( const KURL &url ); + void decodePath( QString path ); + +private: + + QString m_page; + QString m_node; + + QString m_perl; + QString m_infoScript; + QString m_infoConf; +}; + +#endif // __info_h__ diff --git a/kioslave/info/info.protocol b/kioslave/info/info.protocol new file mode 100644 index 000000000..3bb600aea --- /dev/null +++ b/kioslave/info/info.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_info +protocol=info +input=none +output=filesystem +reading=true +defaultMimetype=text/html +determineMimetypeFromExtension=false +DocPath=kioslave/info.html +Icon=help_index +Class=:local diff --git a/kioslave/info/kde-info2html b/kioslave/info/kde-info2html new file mode 100755 index 000000000..428d65dbb --- /dev/null +++ b/kioslave/info/kde-info2html @@ -0,0 +1,1031 @@ +#!/usr/bin/perl +#--------------------------------------------------------- +# info2html +#--------------------------------------------------------- +# +# PURPOSE +# This perl script converts info nodes to HTML format. +# The node is specified on the command line using the +# syntax +# (<infofile>)<tag> +# If <infofile> and/or <tag> are missing, (dir)Top is assumed. +# +# AUTHOR +# Karl Guggisberg <guggis@iam.unibe.ch> +# +# Changes for the KDE Help Center (c) 1999 Matthias ELter +# (me@kde.org) +# +# LICENSE +# GPL +# +# HISTORY +# 11.10.93 V 1.0 +# 14.10.93 V 1.0a some comments added +# 15.10.93 V 1.0b file for configuration settings +# 16.10.93 V 1.0c multiple info path possible +# some bugs in escaping references removed +# 28.6.94 V 1.0d some minor changes +# 8.4.95 V 1.1 bug fixes by Tim Witham +# <twitham@eng.fm.intel.com> +# March 1999 Changes for use in KDE Help Center +# February 2000 Changes for bzip2 format +# Sept. 4 2002 Updated to the KDE look +# by Hisham Muhammad <hisham@apple2.com> +# January 30 2003 Ported Hisham's work to HEAD +# by David Pashley <david@davidpashley.com> +# March 6 2003 Substitute use of absolute fixed file URLs to images with help:common URLs +# for the images and style sheet. By Luis Pedro Coelho +# March 9 2003 Add support for browsing by file. by Luis Pedro Coelho +# June 11 2003 Update the layout of the sides to the new infopageslayout. +# by Sven Leiber <s.leiber@web.de> +# +#------------------------------------------------------- + +use strict; + +# set here the full path of the info2html.conf +push @INC, $1 if $0 =~ m!(.*/)[^/]+$!; # full path of config file is passed in ARGV[1] by caller but let's clean this anyway +my $IMAGEDIR = "file:$ARGV[1]/"; # TV: broken, broken, not passed +my $config_file = $ARGV[0]; +delete $ENV{CDPATH}; +delete $ENV{ENV}; +require $config_file; #-- configuration settings + +my $STYLESHEET_KDE = "<link rel=\"stylesheet\" href=\"help:common/kde-default.css\" type=\"text/css\"/>"; +my $LOGO_KDE = "<img src=\"help:/common/kde_logo.png\" alt=\"KDE - The K Desktop Environment\" width=\"296\" height=\"79\" border=\"0\">"; + +# the use of a query should make sure it never conflicts with a "real" path +my $BROWSE_BY_FILE_PATH = '/browse_by_file?special=yes'; + + +my $DONTPRINTYET = 'DONTPRINTYET '; + +#-- patterns +my $NODEBORDER = '\037\014?'; #-- delimiter of an info node +my $REDIRSEP = '\177'; #-- delimiter in tag tables +my $WS = '[ \t]+'; #-- white space + +my $WSS = '[ \t]*'; #-- white space * +my $TE = '[\t\,\.]'; #-- end of a tag +my $TAG = '[^\t\,\.]+'; #-- pattern for a tag +my $FTAG = '[^\)]+'; #-- pattern for a file name in + #-- a cross reference + +#--------------------------------------------------------- +# DieFileNotFound +#--------------------------------------------------------- +# Replies and error message if the file '$FileName' is +# not accessible. +#--------------------------------------------------------- +sub DieFileNotFound { + my ($FileName) = @_; + $FileName =~ s/&/&/g; + $FileName =~ s/>/>/g; + $FileName =~ s/</</g; + + #-- TEXT : error message if a file could not be opened + print <<EOF; +<head> +<title>Info: (no page found)</title> +</head> +<body> +<h1>KDE Info Pages Viewer Error</h1> + No info page for topic <code>"$FileName"</code> found.<br> + You may find what you are looking for at the <a href="man:$FileName">$FileName manpage</a>. +</body> +EOF + die "\n"; +} + +#--------------------------------------------------------- +# Redirect +#--------------------------------------------------------- +# Since we can't do a kioslave redirection from here, we resort to an HTML +# redirection. +# +# It could be simpler to just output the correct page, but that would leave the +# the browser URL indication a bit wrong and more importantly we might mess up relative links. +# Therefore, I implemented it like this which is simpler if not as nice on the end user +# who sees a flicker. +#--------------------------------------------------------- + +sub Redirect { + my ($File,$Tag) = @_; + print <<EOF; + <html><head><title>Doing redirection</title> + <meta http-equiv="refresh" content="0; url=info:$File/$Tag"> + <body> + <h1>Redirecting .... </h1> + <p>If you are not automatically taken to a new page, <a href="info:$File/$Tag">click here</a> to continue. + </body> + </html> +EOF + + exit 0; +} + +#--------------------------------------------------------- +# FileNotFound +#--------------------------------------------------------- +# If the file is not found and the node is '', try to go through +# dir entries. +# This deals with cases like info:ls should open "coreutils/ls invocation" +#--------------------------------------------------------- +sub FileNotFound { + my ($FileName,$NodeName) = @_; + DieFileNotFound($FileName) if $NodeName ne 'Top' || $FileName eq 'dir'; + # Try to find it in dir + + my $DirFileName = &FindFile('dir'); + if ($DirFileName =~ m/.info.bz2$/ ) { + open DIR, "-|", "bzcat", $DirFileName; + } + elsif ($DirFileName =~ m/.info.gz$/ ) { + open DIR, "-|", "gzip", "-dc", $DirFileName; + } + else { + open DIR, $DirFileName; + } + my $looking = 1; + while (<DIR>) { + next if $looking && !/\* Menu/; + $looking = 0; + my @item = &ParseMenuItem($_,'dir'); + if (!defined(@item)) { next } + my ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText) = @item; + if ($MenuLinkRef eq $FileName) { + &Redirect($MenuLinkFile, $MenuLinkTag); + exit 0; + } + } + &DieFileNotFound($FileName); +} + +#--------------------------------------------------------- +# Escape +#--------------------------------------------------------- +# This procedures escapes some special characeters. The +# escape sequence follows the WWW guide for escaped +# characters in URLs +#--------------------------------------------------------- +sub Escape { + my ($Tag) = @_; + #-- escaping is not needed anymore KG/28.6.94 + #-- it is, for "?" %3f (info:/cvs/What is CVS?), kaper/23.7.02 + $Tag =~ s/ /%20/g; # space + $Tag =~ s/\?$/%3f/g; # space + $Tag =~ s/\"/%22/g; # space + $Tag =~ s/\#/%23/g; +# $Tag =~ s/\+/%AB/g; # + + $Tag; +} + +#---------------------------------------------------------- +# DirnameCheck +# TV: This is totally broken. +# I don't know what was the original attempt but that code +# cannot work ! we cannot match the info name (which has no full path) +# with the info path ... +# The only thing i can see (guessed from the || part of the caller) +# is that we try to reject files with "/" in their name, guessing +# we pass a man page full path instead of a info file name ... +# In *that* case, the flow logic is inverted and we should have used "&&" +# instead of "||" +# +# Thus the commented out call... +#---------------------------------------------------------- +#sub DirnameCheck { +# my ($Base) = @_; +# my $Dir = $Base; +# +# $Base =~ s!.*/!!g; +# $Dir =~ s!\Q$Base\E!!; +# +# foreach (@info2html::config::INFODIR) { +# return 1 if $Dir =~ /^$_/; +# } +# +# foreach my $i (split(/:/, $ENV{INFOPATH})) { +# return 1 if $Dir =~ /^$i/; +# } +# +# return 0; +#} + +#---------------------------------------------------------- +# DeEscape +#---------------------------------------------------------- +#sub DeEscape { +# my ($Tag) = @_; +# #-- deescaping is not needed anymore. KG/28.6.94 +# $Tag =~ s/%AB/+/g; +# $Tag =~ s/%20/ /g; +# $Tag =~ s/\.\.\///g; +# $Tag =~ s/\.\.//g; +# $Tag =~ s/\.\///g; +# $Tag; +#} + +sub infocat { +# Collect them all into an array that can be sorted + + my %InfoFile; + my %LinkText; + my @dirs; + + foreach my $dir (@info2html::config::INFODIR) { + push @dirs, $dir; + } + if ($ENV{'INFOPATH'}) { + foreach my $dir (split(/:/, $ENV{INFOPATH})) { + push @dirs, $dir; + } + } + + foreach my $dir (@dirs) { + opendir DIR, $dir; + my ($infofile,$filedesc); + while ($infofile = readdir(DIR)) { + if ($infofile =~ m/.info.bz2$/ ) { + open INFOFILE, "-|", "bzcat", "$dir/$infofile"; + } + elsif ($infofile =~ m/.info.gz$/ ) { + open INFOFILE, "-|", "gzip", "-dc", "$dir/$infofile"; + } + elsif ($infofile =~ m/.info$/) { + open INFOFILE, "-|", "$dir/$infofile"; + } + else { + next; + } + $filedesc = ''; + my $collect = 0; + my $empty = 1; + while (<INFOFILE>) { + last if (m/END-INFO-DIR-ENTRY/); + s/^\* //; + chomp; + next if /^\s*$/; + if ($collect) { + $filedesc .= "\n<br>" if ($collect < 16); + $filedesc .= $_; + --$collect; + $empty = 0; + } elsif (!$empty && !$collect) { + $filedesc .= "<br><b>...</b>\n"; + last; + } + $collect=16 if (m/START-INFO-DIR-ENTRY/); + } + if ($empty) { $filedesc .= 'no description available'; } + close INFOFILE; + $filedesc .= $infofile if ($filedesc eq ""); +# Add to the hash + $LinkText{$filedesc} = "$dir/$infofile"; + $InfoFile{$filedesc} = "$infofile"; + } + } + +# Now output the list + my @sorted = sort { lc($a) cmp lc($b) } keys %InfoFile; + + print '<dl>'; + foreach my $description ( @sorted ) { + print <<EOF; + <dt> <a href="info:$InfoFile{$description}/Top">$LinkText{$description}</a> + <dd>$description + +EOF + } + print '</dl>'; +} + +#---------------------------------------------------------- +# ParsHeaderToken +#---------------------------------------------------------- +# Parses the header line of an info node for a specific +# link directive (e.g. Up, Prev) +# +# Returns a link as (InfoFile,Tag). +#---------------------------------------------------------- +sub ParsHeaderToken { + my ($HeaderLine, $Token) = @_; + return ("", "") if $HeaderLine !~ /$Token:/; #-- token not available + my ($InfoFile, $node, $Temp); + if ($HeaderLine =~ m!$Token:$WS(\(($FTAG)\))!) { + $InfoFile = $2; + $Temp = $2 ne "" ? '\(' . $2 . '\)' : ""; + } + $node = $1 if $HeaderLine =~ m!$Token:$WS$Temp$WSS([^\t,\n]+)?([\t,\.\n])!; + $node ||= "Top"; + return $InfoFile, $node; +} + +#--------------------------------------------------------- +# ParsHeaderLine +#-------------------------------------------------------- +# Parses the header line on an info node for all link +# directives allowed in a header line. +# Sometimes the keyword 'Previous' is found in stead of +# 'Prev'. Thats why the redirection line is checked +# against both of these keywords. +#------------------------------------------------------- +sub ParsHeaderLine { + my ($HL) = @_; + my @LinkList; + #-- Node + push(@LinkList, &ParsHeaderToken($HL, "Node")); + #-- Next + push(@LinkList, &ParsHeaderToken($HL, "Next")); + #-- Up + push(@LinkList, &ParsHeaderToken($HL, "Up")); + #-- Prev or Previous + my @LinkInfo = &ParsHeaderToken($HL, "Prev"); + &ParsHeaderToken($HL, "Previous") if $LinkInfo[0] eq "" && $LinkInfo[1] eq ""; + push(@LinkList, @LinkInfo); + return @LinkList; +} + +############################################################ +# turn tabs into correct number of spaces +# +sub Tab2Space { + my ($line) = @_; + $line =~ s/^\t/ /; # 8 leading spaces if initial tab + while ($line =~ s/^([^\t]+)(\t)/$1 . ' ' x (8 - length($1) % 8)/e) { + } # replace each tab with right num of spaces + return $line; +} + +#-------------------------------------------------------- +# ParseMenuItem +#-------------------------------------------------------- +# Takes a line containing a Menu item and returns a list of +# ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText) +# or undef if the parsing fails +#------------------------------------------------------- + +sub ParseMenuItem { + my ($Line,$BaseInfoFile) = @_; + my ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText); + $Line = &Tab2Space($Line); # make sure columns line up well + + if ($Line =~ /\* ([^:]+)::/) { # -- is a simple entry ending with :: ? + $MenuLinkTag = $1; + $MenuLinkRef = $1; + $MenuLinkText = $'; #' --just to help emacs perl-mode + $MenuLinkFile = &Escape($BaseInfoFile); + } elsif ($Line =~ /\* ([^:]+):(\s*\(($FTAG)\)($TAG)?$TE\.?)?(.*)$/) { + $MenuLinkFile = $BaseInfoFile; + $MenuLinkRef = $1; + $MenuLinkText = $5; + if ($2) { + $MenuLinkFile = $3; + $MenuLinkTag = $4 || 'Top'; + $MenuLinkText = ($2 ? ' ' x (length($2)+1) : '') . "$5\n"; + } else { + $Line = "$5\n"; + if ($Line =~ /( *($TAG)?$TE(.*))$/) { + $MenuLinkTag = $2; + $MenuLinkText = $Line; + } + } + } else { + return undef; + } + $MenuLinkTag = &Escape($MenuLinkTag); # -- escape special chars + $MenuLinkText =~ s/^ *//; + return ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText); +} + +#-------------------------------------------------------- +# MenuItem2HTML +#-------------------------------------------------------- +# Transform an info menu item in HTML with references +#------------------------------------------------------- +sub MenuItem2HTML { + my ($Line, $BaseInfoFile) = @_; + my @parse_results = &ParseMenuItem($Line, $BaseInfoFile); + if (!defined (@parse_results)) { return $Line; } + my ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText) = @parse_results; + #-- produce a HTML line + return "<tr class=\"infomenutr\"><td class=\"infomenutd\" width=\"30%\"><ul><li><a href=\"info:/$MenuLinkFile/$MenuLinkTag\">$MenuLinkRef</a></ul></td><td class=\"infomenutd\">$MenuLinkText"; +} + +#------------------------------------------------------------- +# ReadIndirectTable +#------------------------------------------------------------ +# Scans an info file for the occurence of an 'Indirect:' +# table. Scans the entrys and returns two lists with the +# filenames and the global offsets. +#--------------------------------------------------------- +sub ReadIndirectTable { + my ($FileName, $FileNames, $Offsets) = @_; + + local *FH1; + if ($FileName =~ /\.gz$/) { + open FH1, "-|", "gunzip", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } elsif ($FileName =~ /\.bz2$/) { + open FH1, "-|", "bunzip2", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } else { + open(FH1, $FileName) || &DieFileNotFound($FileName); + } + #-- scan for start of Indirect: Table + local $_; + while (<FH1>) { + my $Next = <FH1> if /$NODEBORDER/; + last if $Next =~ /^Indirect:/i; + } + #-- scan the entrys and setup the arrays + local $_; + while (<FH1>) { + last if /$NODEBORDER/; + if (/([^:]+):[ \t]+(\d+)/) { + push(@$FileNames, $1); + push(@$Offsets, $2); + } + } + close(FH1); +} + +#--------------------------------------------------------- +# ReadTagTable +#-------------------------------------------------------- +# Reads in a tag table from an info file. +# Returns an assoziative array with the tags found. +# Tags are transformed to lower case (info is not +# case sensitive for tags). +# The entrys in the assoziative Array are of the +# form +# <file>#<offset> +# <file> may be empty if an indirect table is +# present or if the node is located in the +# main file. +# 'Exists' indicates if a tag table has been found. +# 'IsIndirect' indicates if the tag table is based +# on a indirect table. +#-------------------------------------------------------- +sub ReadTagTable { + my ($FileName, $TagList, $Exists, $IsIndirect) = @_; + + local *FH; + if ($FileName =~ /\.gz$/) { + open FH, "-|", "gunzip", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } elsif ($FileName =~ /\.bz2$/) { + open FH, "-|", "bunzip2", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } else { + open FH, $FileName || &DieFileNotFound($FileName); + } + ($$Exists, $$IsIndirect) = (0, 0); + #-- scan for start of tag table + local $_; + while (<FH>) { + if (/$NODEBORDER/) { + if (<FH> =~ /^Tag table:/i) { + $$Exists = 1; + last; + } + } + } + #-- scan the entrys + local $_; + while (<FH>) { + $$IsIndirect = 1 if /^\(Indirect\)/i; + last if /$NODEBORDER/; + if (/Node:[ \t]+([^$REDIRSEP]+)$REDIRSEP(\d+)/) { + my ($Tag, $Offset) = (lc($1), $2); + my $File = $1 if /File:[ \t]+([^\t,]+)/; + $TagList->{$Tag} = $File."#".$Offset; + } + } + close(FH); +} + +#---------------------------------------------------------- +# ParsCrossRefs +#---------------------------------------------------------- +# scans a line for the existence of cross references and +# transforms them to HTML using a little icon +#---------------------------------------------------------- +sub ParsCrossRefs { + my ($prev, $Line, $BaseInfoFile) = @_; + my ($NewLine, $Token); + my ($CrossRef, $CrossRefFile, $CrossRefTag, $CrossRefRef, $CrossRefText); + $Line = " " . $Line; + if ($prev =~ /\*Note([^\t\,\.]*)$/mi) { + $Line = "$prev-NEWLINE-$Line" if $Line =~ /^$TAG$TE/m; + } + my @Tokens = split(/(\*Note)/i, $Line); # -- split the line + while ($Token = shift @Tokens) { + $CrossRefTag = $CrossRefRef = $CrossRefFile = $CrossRefText = ''; + if ($Token !~ /^\*Note/i) { #-- this part is pure text + $NewLine .= $Token; + next; #-- ... take the next part + } + $CrossRef = shift(@Tokens); + if ($CrossRef !~ /:/) { #-- seems not to be a valid cross ref. + $NewLine .= $Token.$CrossRef; + next; # -- ... take the next one + } + if ($CrossRef =~ /^([^:]+)::/) { # -- a simple cross ref.. + $CrossRefTag = $1; + $CrossRefText = $'; + $CrossRefRef = $CrossRefTag; + $CrossRefTag =~ s/-NEWLINE-/ /g; + $CrossRefTag =~ s/^\s+//; + $CrossRefTag =~ s/\s+/ /g; + $CrossRefRef =~ s/-NEWLINE-/\n/g; + $CrossRefTag = &Escape($CrossRefTag); # -- escape specials + $BaseInfoFile = &Escape($BaseInfoFile); + $NewLine .= "<a href=\"info:/$BaseInfoFile/$CrossRefTag\">"; + $NewLine .= "$CrossRefRef</a>$CrossRefText"; + next; # -- .. take the next one + } + if ($CrossRef !~ /$TE/) { # never mind if tag doesn't end on this line + $NewLine .= $Token.$CrossRef; + next; + } +#print "--- Com. CR : $CrossRef --- \n"; + if ($CrossRef =~ /([^:]+):/) { #-- A more complicated one .. + $CrossRefRef = $1; + $CrossRef = $'; + $CrossRefText = $CrossRef; + } + if ($CrossRef =~ /^(\s|\n|-NEWLINE-)*\(($FTAG)\)/) { #-- .. with another file ? + $CrossRefFile = $2; + $CrossRef = $'; + } + $CrossRefTag = $2 if $CrossRef =~ /^(\s|\n|-NEWLINE-)*($TAG)?($TE)/; #-- ... and a tag ? + if ($CrossRefTag eq "" && $CrossRefFile eq "") { + $NewLine .= "*Note : $CrossRefText$3"; + next; + } + + $CrossRefTag =~ s/-NEWLINE-/ /g; + $CrossRefTag =~ s/^\s+//; + $CrossRefTag =~ s/\s+/ /g; + $CrossRefRef =~ s/-NEWLINE-/\n/g; + $CrossRefText =~ s/-NEWLINE-/\n/g; + $CrossRefFile = $BaseInfoFile if $CrossRefFile eq ""; + $CrossRefTag = "Top" if $CrossRefTag eq ""; + $CrossRefRef = "($CrossRefFile)$CrossRefTag" if $CrossRefRef eq ''; + $CrossRefTag = &Escape($CrossRefTag); #-- escape specials + $CrossRefFile = &Escape($CrossRefFile); + #-- append the HTML text + $NewLine .= "<a href=\"info:/$CrossRefFile/$CrossRefTag\">"; + $NewLine .= "$CrossRefRef</a>$CrossRefText"; + } + if ($NewLine =~ /\*Note([^\t\,\.]*)$/i) { + return "$DONTPRINTYET$NewLine"; + } else { + $NewLine; #-- return the new line + } +} + + +#------------------------------------------------------------- +# PrintLinkInfo +#------------------------------------------------------------- +# prints the HTML text for a link information in the +# header of an info node. Uses some icons URLs of icons +# are specified in 'info2html.conf'. +#------------------------------------------------------------ +sub PrintLinkInfo { + my ($LinkType, $LinkFile, $LinkTag, $BaseInfoFile) = @_; + my ($LinkFileEsc, $LinkTypeText); + return if $LinkFile eq "" && $LinkTag eq ""; + + #-- If no auxiliary file specified use the current info file + $LinkFile ||= $BaseInfoFile; + my $LinkRef = $LinkTag; + $LinkTag = &Escape($LinkTag); + $LinkFileEsc = &Escape($LinkFile); + #-- print the HTML Text + print <<EOF; +<a href="info:/$LinkFileEsc/$LinkTag"> + $LinkTypeText + <strong>$LinkRef</strong> +</a> +EOF +} + +#------------------------------------------------------------- +# PrintHeader +#------------------------------------------------------------- +# Prints the header for an info node in HTML format +#------------------------------------------------------------ +sub PrintHeader { + my ($LinkList, $BaseInfoFile) = @_; + my @LinkList = @{$LinkList}; + + my $UpcaseInfoFile = $BaseInfoFile; + $UpcaseInfoFile =~ tr/a-z/A-Z/; + #-- TEXT for the header of an info node + print <<EOF; +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html> + <head> + <title>Info: ($BaseInfoFile) $LinkList[1]</title> + $STYLESHEET_KDE + </head> + <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> +<!--header start--> +<div style="background-image: url(help:/common/top-middle.png); width: 100%; height: 131px;"> +<div style="position: absolute; right: 0px;"> +<img src="help:/common/top-right-konqueror.png" style="margin: 0px" alt="" /> +</div> +<div style="position: absolute; left: 0px;"> +<img src="help:/common/top-left.png" style="margin: 0px" alt="" /> +</div> +<div style="position: absolute; top: 25px; right: 100px; text-align: right; font-size: xx-large; font-weight: bold; text-shadow: #fff 0px 0px 5px; color: #444"> +$UpcaseInfoFile: $LinkList[1]</div> +</div> +<div class="header" style="border: none"> +EOF + common_headers($LinkList, $BaseInfoFile); +print <<EOF; +</div> + <div id="contents"> + <div class="chapter"> +EOF +} + +sub common_headers { + my ($LinkList, $BaseInfoFile) = @_; + my @LinkList = @{$LinkList}; + print <<EOF; + <tr><td width="33%" align="left" valign="top" class="navLeft"> +EOF + &PrintLinkInfo("Prev", $LinkList[6], $LinkList[7], $BaseInfoFile); + print <<EOF; + </td><td width="34%" align="center" valign="top" class="navCenter"> +EOF + &PrintLinkInfo("Up", $LinkList[4], $LinkList[5], $BaseInfoFile); + print <<EOF; + </td><td width="33%" align="right" valign="top" class="navRight"> +EOF + &PrintLinkInfo("Next", $LinkList[2], $LinkList[3], $BaseInfoFile); +} + +#--------------------------------------------------------- +# PrintFooter +#--------------------------------------------------------- +# prints the footer for an info node in HTML format +#--------------------------------------------------------- +sub PrintFooter { + my ($LinkList, $BaseInfoFile, $LinkFile) = @_; + + $LinkFile ||= $BaseInfoFile; + + #-- TEXT for the footer of an info node + print <<EOF; + </div> + <em>Automatically generated by a version of + <a href="$info2html::config::DOC_URL"> + <b>info2html</b> + </a> modified for <a href="http://www.kde.org/">KDE</a></em>. + <div class="bottom-nav"> +EOF + common_headers($LinkList, $BaseInfoFile); + print <<EOF; + <!--<br />--><em>$LinkFile</em> + </div> + </body> +</html> +EOF +} + +#---------------------------------------------------------- +# ReplyNotFoundMessage +#---------------------------------------------------------- +sub ReplyNotFoundMessage { + my ($FileName, $Tag) = @_; + print <<EOF; +<head> +<title>Info Files - Error Message</title> +</head> +<h1>Error</h1> +<body> +The Info node <em>$Tag</em> in Info file <em>$FileName</em> +does not exist. +</body> +EOF +} + +sub PrintByFileLink { + print <<EOF + + <hr width="80%"/> + <p>If you did not find what you were looking for try <a href="info:$BROWSE_BY_FILE_PATH">browsing by file</a> to + see files from packages which did not update the directory. +EOF +} + +#----------------------------------------------------------- +# BrowseByFile +#----------------------------------------------------------- +# Shows a list of available files in the system with a short +# description of them. +#------------------------------------------------------------ + +sub BrowseByFile { + my @LinkList = ('', '', '', '', + 'dir', 'Top', '','',''); # set LinkList[4] & LinkList[5], of course ;) + my $BaseInfoFile = 'Available Files'; + &PrintHeader(\@LinkList, $BaseInfoFile); + print <<EOF; +<h2>Available Files</h2> +EOF + &infocat; + &PrintFooter(\@LinkList, $BaseInfoFile); +} + +#----------------------------------------------------------- +# InfoNode2HTML +#----------------------------------------------------------- +# scans an info file for the node with the name '$Tag' +# starting at the postion '$Offset'. +# If found the node is tranlated to HTML and printed. +#------------------------------------------------------------ +sub InfoNode2HTML { + my ($FileName, $Offset, $Tag, $BaseInfoFile) = @_; + + local *FH2; + if ($FileName =~ /\.gz$/) { + open FH2, "-|", "gunzip", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } elsif ($FileName =~ /\.bz2$/) { + open FH2, "-|", "bunzip2", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } else { + open FH2, $FileName || &DieFileNotFound($FileName); + } + seek(FH2, $Offset, 0); + $Tag =~ tr/A-Z/a-z/; # -- to lowercase + #-- scan for the node start + my ($Found, @LinkList); + local $_; + while (<FH2>) { + if (/$NODEBORDER/) { + my $line = <FH2>; + @LinkList = &ParsHeaderLine($line); + my $CompareTag = $Tag; + $CompareTag =~ s/([^0-9A-Za-z])/\\$1/g; #-- escape special chars ! + my $Temp = $LinkList[1]; + $Temp =~ tr/A-Z/a-z/; #-- to lower case + if ($Temp =~ /^\s*$CompareTag\s*$/) { #-- node start found ? + $Found = 1; + last; + } + } + } + + return &ReplyNotFoundMessage($FileName, $Tag) unless $Found; # -- break if not found; + + &PrintHeader(\@LinkList, $BaseInfoFile); + my $InMenu = 0; + my $prev; + my $LineCount = 0; + my $Entries = 0; + my $Par = 0; + my @ParLines = (); + my $ParLine=0; + my $MayBeText=0; + my $MayBeTitle=0; + my $Line; + my $PrevMenu; + local $_; + while (<FH2>) { + $LineCount++; + last if /$NODEBORDER/; + #-- replace meta characters + #s/"`"([^"'"]*)"'"/"<span class=\"option\">"$1"</span>"/g; + s/&/&/g; + s/>/>/g; + s/</</g; + + my $Length = length($_); + if ($LineCount == 3 && $InMenu == 0 && length($_) == $Length && $Length > 1){ #-- an underline ? + if (/^\**$/) { + print "<h2>$prev</h2>\n"; + $prev = ""; + next; + } + elsif (/^=*$/) { + print "<h3>$prev</h3>\n"; + $prev = ""; + next; + } + else { + print "<h4>$prev</h4>\n"; + $prev = ""; + next; + } + } + + if (/^\* Menu/ && $InMenu == 0) { # -- start of menu section ? + $InMenu = 1; + print "<h3>Menu</h3>\n"; + } + elsif ($InMenu == 1) { + # This is pretty crappy code. + # A lot of logic (like the ParsCrossRefs and tranforming Variable: etc) is repeated below. + # There have been a few bugs which were fixed in one branch of the code and left in the other. + # This should be refactored. + # LPC (16 March 2003) + if (/^\* /) { #-- a menu entry ? + if ($Entries == 0) { + $Entries = 1; + print "<table class=\"infomenutable\">"; + } + print &MenuItem2HTML($_,$BaseInfoFile); + } + elsif (/^$/) { #-- empty line + if ($Entries == 1) { + print "</td></tr></table>"; + $Entries = 0; + } + print "<br>"; + } + else { + $Line = &ParsCrossRefs($prev,$_,$BaseInfoFile); + if ($Line =~ /^$DONTPRINTYET/) { + $prev = $Line; + $prev =~ s/^$DONTPRINTYET//; + chomp $prev; + } + elsif ($LineCount == 2) { + $prev = $Line; + } else { + $prev = $Line; + $Line =~ s#- (Variable|Function|Macro|Command|Special Form|User Option|Data Type):.*$#<em><strong>$&</strong></em>#; + $Line =~ s/^[ \t]*//; + print $Line; + } + } + } + else { + if (/^ *$/) { + if ($MayBeText == 1) { + print "<p>$Par</p>" + } else { + print "<pre>"; + foreach (@ParLines) { + print $_; + } + print "\n"; + print "</pre>"; + } + @ParLines = (); + $ParLine = 1; + $MayBeText = 1; + $MayBeTitle = 1; + $Par = ""; + } else { + if ($ParLine == 1) { + if (!/^ {1,4}[^ ]/ || /[^ ] [^ ]/) { + $MayBeText = 0; + } + } else { + if (!/^ ?[^ ]/ || /[^ ] [^ ]/) { + $MayBeText = 0; + } + } + $Line = &ParsCrossRefs($prev,$_,$BaseInfoFile); + if ($Line =~ /^$DONTPRINTYET/) { + $prev = $Line; + $prev =~ s/^$DONTPRINTYET//; + chomp $prev; + } elsif ($LineCount == 2) { + $prev = $Line; + } else { + $prev = $Line; + $Line =~ s#- (Variable|Function|Macro|Command|Special Form|User Option):.*$#<strong>$&</strong>#; + $Line =~ s/`([^']*)'/`<span class="option">$1<\/span>'/g; #' + $Line =~ s/((news|ftp|http):\/\/[A-Za-z0-9\.\/\#\-_\~]*)/<a href="$1">$1<\/a>/g; + $Line =~ s/([A-Za-z0-9\.\/\#\-_\~]*\@[A-Za-z0-9\.\/\#\-_\~]*\.[A-Za-z]{2,3})/<a href="mailto:$1">$1<\/a>/g; + $Par = $Par . $Line; + $ParLines[$ParLine] = $Line; + $ParLine++; + } + } + } + } + if ($Entries == 1) { + print "</table>" + } + if ($PrevMenu =~ "") { + print &MenuItem2HTML($PrevMenu,$BaseInfoFile); + } + + close(FH2); + + if ($BaseInfoFile =~ m/dir/i + && $Tag =~ m/Top/i) { + &PrintByFileLink; + } + + &PrintFooter(\@LinkList, $BaseInfoFile); +} + +#------------------------------------------------------------- +# max +#------------------------------------------------------------ +sub max { + my ($a, $b) = @_; + return $a >= $b ? $a : $b; +} + +#----------------------------------------------------------- +# GetFileAndOffset +#------------------------------------------------------------ +# This procedure locates a specific node in a info file +# The location is based on the tag and indirect table in +# basic info file if such tables are available. +# Because the offsets specified in the tag and in the +# indirect table are more or less inacurate the computet +# offset is set back 100 bytes. From this position +# the specified node will looked for sequentially +#------------------------------------------------------------ +sub GetFileAndOffset { + my ($BaseInfoFile, $NodeName) = @_; + my ($Exists, $IsIndirect, $File, $Offset, $FileOffset, %TagList, @FileNames, @Offsets); + $NodeName =~ tr/A-Z/a-z/; + &ReadIndirectTable($BaseInfoFile, \@FileNames, \@Offsets); + + +# This looks wastefull: +# We build a whole TagList hash and then use it to lookup the tag info. +# Why not pass $NodeName to ReadTagTable and let it return just the desired info? +# lpc (16 March 2003) + &ReadTagTable($BaseInfoFile, \%TagList, \$Exists, \$IsIndirect); + return "", 0 unless $Exists; #-- no tag table available + return "", 0 unless defined $TagList{$NodeName}; #-- tag is not in the tag table + ($File, $Offset) = split(/#/, $TagList{$NodeName}); + return $File, &max($Offset - 100, 0) if $File; #-- there is an explicite + #-- not in the tag table + + if ($IsIndirect == 1) { + foreach my $i (0..$#Offsets) { + $FileOffset = $Offsets[$i] if $Offsets[$i] <= $Offset; + $File = $FileNames[$i] if $Offsets[$i] <= $Offset; + } + return $File, &max($Offset - $FileOffset - 100,0); #-- be safe (-100!) + } else { + return "", &max($Offset - 100, 0); + } +} + +# FindFile: find the given file on the infopath, return full name or "". +# Let filenames optionally have .info suffix. Try named version first. +# Handle gzipped file too. +sub FindFile { + my ($File) = @_; + return "" if ($File =~ /\.\./); + my $Alt = $File =~ /^(.+)\.info$/ ? $1 : $File . '.info'; + foreach my $Name ($File, $Alt) { + my $gzName = $Name . '.gz'; + my $bz2Name = $Name . '.bz2'; + + foreach (@info2html::config::INFODIR) { + return "$_/$Name" if -e "$_/$Name"; + return "$_/$gzName" if -e "$_/$gzName"; + return "$_/$bz2Name" if -e "$_/$bz2Name"; + } + next unless $ENV{INFOPATH}; + foreach my $i (split(/:/, $ENV{INFOPATH})) { + return "$i/$Name" if -e "$i/$Name"; + return "$i/$gzName" if -e "$i/$gzName"; + return "$i/$bz2Name" if -e "$i/$bz2Name"; + } + } + return ""; +} + +#------------------------------------------------------- +# +#------------------- MAIN ----------------------------- +# +# called as +# perl /path/kde-info2html config_file image_base_path BaseInfoFile NodeName +# +# BaseInfoFile eq '#special#' to pass special args through NodeName (yes, it is a hack). +# + +my $PROGRAM = $0; # determine our basename and version +$PROGRAM =~ s!.*/!!; +my ($BaseInfoFile, $NodeName) = ($ARGV[2], $ARGV[3]); +#&DirnameCheck($BaseInfoFile) || &DieFileNotFound($BaseInfoFile); + +if ($BaseInfoFile eq '#special#' && $NodeName eq 'browse_by_file') { + &BrowseByFile; + exit 0; +} + +$BaseInfoFile = "dir" if $BaseInfoFile =~ /^dir$/i; +my $FileNameFull = &FindFile($BaseInfoFile) || &FileNotFound($BaseInfoFile,$NodeName); +my ($File, $Offset) = &GetFileAndOffset($FileNameFull, $NodeName); +$File ||= $BaseInfoFile; +$FileNameFull = &FindFile($File); +&InfoNode2HTML($FileNameFull, $Offset, $NodeName, $BaseInfoFile); + +exit 0; diff --git a/kioslave/info/kde-info2html.conf b/kioslave/info/kde-info2html.conf new file mode 100644 index 000000000..260d2b336 --- /dev/null +++ b/kioslave/info/kde-info2html.conf @@ -0,0 +1,43 @@ +# -*- perl -*- +package info2html::config; +#----------------------------------------------------------------- +# info2html.conf +#----------------------------------------------------------------- +# PURPOSE +# configuration settings for the 'info2html' script. +# +# AUTHOR +# Karl Guggisberg <guggis@iam.unibe.ch> +# +# HISTORY +# 15.10.93 V 1.0b +# 16.10.93 V 1.0c multple info files possible +# 28.6.94 V 1.0d some minor changes +# 8.4.95 V 1.1 some changements +#---------------------------------------------------------------- + +use strict; +#use vars qw(@ISA @EXPORT); +# +#@ISA = qw(Exporter); +#@EXPORT = qw(@INFODIR $DOC_URL); + + +#-- location of info files. +our @INFODIR = ( + "/usr/share/info", + "/usr/info", + "/usr/lib/info", +# "/usr/lib/teTeX/info", + "/usr/local/info", + "/usr/local/lib/info", + "/usr/X11R6/info", + "/usr/X11R6/lib/info", + "/usr/X11R6/lib/xemacs/info" + ); + + +#-- URL for documentation of info2html +our $DOC_URL = 'http://info2html.sourceforge.net/'; + +1; diff --git a/kioslave/ldap/LICENSE b/kioslave/ldap/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/kioslave/ldap/LICENSE @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/ldap/Makefile.am b/kioslave/ldap/Makefile.am new file mode 100644 index 000000000..49faa1e98 --- /dev/null +++ b/kioslave/ldap/Makefile.am @@ -0,0 +1,22 @@ +## Makefile.am of kdebase/kioslave/ldap + +INCLUDES = $(all_includes) $(LDAP_INCS) +AM_CXXFLAGS = -DLDAP_DEPRECATED +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LDAP_RPATH) +LDADD = $(LIB_KIO) $(LDAP_LIBS) + +####### Files + +kde_module_LTLIBRARIES = kio_ldap.la + +kio_ldap_la_SOURCES = kio_ldap.cpp +kio_ldap_la_LIBADD = $(LIB_KIO) $(LDAP_LIBS) $(LIB_KABC) +kio_ldap_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LDAP_RPATH) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_ldap.h + +kdelnk_DATA = ldap.protocol ldaps.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_ldap.pot diff --git a/kioslave/ldap/configure.in.in b/kioslave/ldap/configure.in.in new file mode 100644 index 000000000..b23c12387 --- /dev/null +++ b/kioslave/ldap/configure.in.in @@ -0,0 +1,111 @@ +AC_MSG_CHECKING(for LDAP support) +AC_ARG_WITH(ldap, +AC_HELP_STRING([--with-ldap=PATH],[Set path for LDAP files [default=check]]), +[ case "$withval" in + yes) + with_ldap=CHECK + ;; + esac ], +[ with_ldap=CHECK ] +)dnl + +if test "x$with_ldap" = "xCHECK" ; then + with_ldap=NOTFOUND + search_incs="$kde_includes /usr/include /usr/local/include" + AC_FIND_FILE(ldap.h, $search_incs, ldap_incdir) + if test -r $ldap_incdir/ldap.h ; then + test "x$ldap_incdir" != "x/usr/include" && LDAP_INCS="-I$ldap_incdir" + with_ldap=FOUND + fi + if test $with_ldap = FOUND ; then + with_ldap=NOTFOUND + for ext in la so sl a dylib ; do + AC_FIND_FILE(libldap.$ext, $kde_libraries /usr/lib /usr/local/lib /usr/lib64, + ldap_libdir) + if test -r $ldap_libdir/libldap.$ext ; then + if test "x$ldap_libdir" != "x/usr/lib" ; then + LDAP_LIBS="-L$ldap_libdir " + test "$USE_RPATH" = yes && LDAP_RPATH="-R $ldap_libdir" + fi + LDAP_LIBS="${LDAP_LIBS}-lldap" + with_ldap=FOUND + break + fi + done + fi +fi + +case "$with_ldap" in +no) AC_MSG_RESULT(no) ;; +framework) + LDAP_LIBS="-Xlinker -framework -Xlinker LDAP" + AC_DEFINE_UNQUOTED(HAVE_LIBLDAP, 1, [Define if you have LDAP libraries]) + LDAP_SUBDIR="ldap" + AC_MSG_RESULT(Apple framework) + ;; +FOUND) + AC_MSG_RESULT(incs=$ldap_incdir libs=$ldap_libdir) + ;; +NOTFOUND) AC_MSG_RESULT(searched but not found) ;; +*) + AC_MSG_RESULT($with_ldap) + ;; +esac + +LIB_LBER= +KDE_CHECK_LIB(lber, ber_alloc, [LIB_LBER=-llber], [], -L$ldap_libdir) +AC_SUBST(LIB_LBER) + +AC_MSG_CHECKING(whether LDAP support can be compiled) + + if test "x$with_ldap" != "xFOUND" ; then + LDAP_ROOT="$with_ldap" + if test "x$LDAP_ROOT" != "x/usr" ; then + LDAP_INCS="-I${LDAP_ROOT}/include" + LDAP_LIBS="-L${LDAP_ROOT}/lib " + if test "$USE_RPATH" = "yes" ; then + LDAP_RPATH="-R ${LDAP_ROOT}/lib" + fi + fi + LDAP_LIBS="${LDAP_LIBS}-lldap" + fi + LDAP_LIBS="${LDAP_LIBS} ${LIB_LBER} ${LIBRESOLV}" + + kde_safe_LIBS="$LIBS" + kde_safe_CFLAGS="$CFLAGS" + LIBS="$LIBS $all_libraries $LDAP_LIBS $KRB4_LIBS $X_EXTRA_LIBS" + CFLAGS="$CFLAGS $all_includes $LDAP_INCS $KRB4_INCS" + AC_LANG_SAVE + AC_LANG_C + AC_TRY_LINK(dnl + [ + #include <ldap.h> + #if LDAP_API_VERSION < 2004 + #error LDAP version too old, please upgrade to a library supporting API 2004 or higher + #endif + ], + [ + LDAP *ldap; + ], + , with_ldap=no + ) + AC_LANG_RESTORE + CFLAGS=$kde_safe_CFLAGS + LIBS=$kde_safe_LIBS + if test "$with_ldap" = "no" ; then + LDAP_INCS= + LDAP_LIBS= + LDAP_RPATH= + LDAP_SUBDIR= + AC_MSG_RESULT(no (but first try gave $msg)) + else + AC_DEFINE_UNQUOTED(HAVE_LIBLDAP, 1, [Define if you have LDAP libraries]) + LDAP_SUBDIR="ldap" + AC_MSG_RESULT(yes) + fi + +AC_SUBST(LDAP_INCS) +AC_SUBST(LDAP_LIBS) +AC_SUBST(LDAP_RPATH) + +AM_CONDITIONAL(include_kioslave_ldap, test -n "$LDAP_SUBDIR") diff --git a/kioslave/ldap/kio_ldap.cpp b/kioslave/ldap/kio_ldap.cpp new file mode 100644 index 000000000..749ab6121 --- /dev/null +++ b/kioslave/ldap/kio_ldap.cpp @@ -0,0 +1,1154 @@ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <sys/stat.h> + +#include <unistd.h> +#include <stdlib.h> +#include <netdb.h> +#include <netinet/in.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> + +#ifdef HAVE_SASL_SASL_H //prefer libsasl2 +#include <sasl/sasl.h> +#else +#ifdef HAVE_SASL_H +#include <sasl.h> +#endif +#endif +#include <kabc/ldif.h> + +#include "kio_ldap.h" + +using namespace KIO; +using namespace KABC; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +/** + * The main program. + */ +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_ldap" ); + + kdDebug(7125) << "Starting " << getpid() << endl; + + if ( argc != 4 ) { + kdError() << "Usage kio_ldap protocol pool app" << endl; + return -1; + } + + // let the protocol class do its work + LDAPProtocol slave( argv[1], argv[ 2 ], argv[ 3 ] ); + slave.dispatchLoop(); + + kdDebug( 7125 ) << "Done" << endl; + return 0; +} + +/** + * Initialize the ldap slave + */ +LDAPProtocol::LDAPProtocol( const QCString &protocol, const QCString &pool, + const QCString &app ) : SlaveBase( protocol, pool, app ) +{ + mLDAP = 0; mTLS = 0; mVer = 3; mAuthSASL = false; + mRealm = ""; mBindName = ""; + mTimeLimit = mSizeLimit = 0; + kdDebug(7125) << "LDAPProtocol::LDAPProtocol (" << protocol << ")" << endl; +} + +LDAPProtocol::~LDAPProtocol() +{ + closeConnection(); +} + +void LDAPProtocol::LDAPErr( const KURL &url, int err ) +{ + + char *errmsg = 0; + if ( mLDAP ) { + if ( err == LDAP_SUCCESS ) ldap_get_option( mLDAP, LDAP_OPT_ERROR_NUMBER, &err ); + if ( err != LDAP_SUCCESS ) ldap_get_option( mLDAP, LDAP_OPT_ERROR_STRING, &errmsg ); + } + if ( err == LDAP_SUCCESS ) return; + kdDebug(7125) << "error code: " << err << " msg: " << ldap_err2string(err) << + " Additonal error message: '" << errmsg << "'" << endl; + QString msg; + QString extraMsg; + if ( errmsg ) { + if ( errmsg[0] ) + extraMsg = i18n("\nAdditional info: ") + QString::fromUtf8( errmsg ); + free( errmsg ); + } + msg = url.prettyURL(); + if ( !extraMsg.isEmpty() ) msg += extraMsg; + + /* FIXME: No need to close on all errors */ + closeConnection(); + + switch (err) { +/* FIXME: is it worth mapping the following error codes to kio errors? + + LDAP_OPERATIONS_ERROR + LDAP_STRONG_AUTH_REQUIRED + LDAP_PROTOCOL_ERROR + LDAP_TIMELIMIT_EXCEEDED + LDAP_SIZELIMIT_EXCEEDED + LDAP_COMPARE_FALSE + LDAP_COMPARE_TRUE + LDAP_PARTIAL_RESULTS + LDAP_NO_SUCH_ATTRIBUTE + LDAP_UNDEFINED_TYPE + LDAP_INAPPROPRIATE_MATCHING + LDAP_CONSTRAINT_VIOLATION + LDAP_INVALID_SYNTAX + LDAP_NO_SUCH_OBJECT + LDAP_ALIAS_PROBLEM + LDAP_INVALID_DN_SYNTAX + LDAP_IS_LEAF + LDAP_ALIAS_DEREF_PROBLEM + LDAP_INAPPROPRIATE_AUTH + LDAP_BUSY + LDAP_UNAVAILABLE + LDAP_UNWILLING_TO_PERFORM + LDAP_LOOP_DETECT + LDAP_NAMING_VIOLATION + LDAP_OBJECT_CLASS_VIOLATION + LDAP_NOT_ALLOWED_ON_NONLEAF + LDAP_NOT_ALLOWED_ON_RDN + LDAP_NO_OBJECT_CLASS_MODS + LDAP_OTHER + LDAP_LOCAL_ERROR + LDAP_ENCODING_ERROR + LDAP_DECODING_ERROR + LDAP_FILTER_ERROR +*/ + case LDAP_AUTH_UNKNOWN: + case LDAP_INVALID_CREDENTIALS: + case LDAP_STRONG_AUTH_NOT_SUPPORTED: + error(ERR_COULD_NOT_AUTHENTICATE, msg); + break; + case LDAP_ALREADY_EXISTS: + error(ERR_FILE_ALREADY_EXIST, msg); + break; + case LDAP_INSUFFICIENT_ACCESS: + error(ERR_ACCESS_DENIED, msg); + break; + case LDAP_CONNECT_ERROR: + case LDAP_SERVER_DOWN: + error(ERR_COULD_NOT_CONNECT,msg); + break; + case LDAP_TIMEOUT: + error(ERR_SERVER_TIMEOUT,msg); + break; + case LDAP_PARAM_ERROR: + error(ERR_INTERNAL,msg); + break; + case LDAP_NO_MEMORY: + error(ERR_OUT_OF_MEMORY,msg); + break; + + default: + error( ERR_SLAVE_DEFINED, + i18n( "LDAP server returned the error: %1 %2\nThe LDAP URL was: %3" ). + arg( ldap_err2string(err)).arg( extraMsg ).arg( url.prettyURL() ) ); + } +} + +void LDAPProtocol::controlsFromMetaData( LDAPControl ***serverctrls, + LDAPControl ***clientctrls ) +{ + QString oid; bool critical; QByteArray value; + int i = 0; + while ( hasMetaData( QString::fromLatin1("SERVER_CTRL%1").arg(i) ) ) { + QCString val = metaData( QString::fromLatin1("SERVER_CTRL%1").arg(i) ).utf8(); + LDIF::splitControl( val, oid, critical, value ); + kdDebug(7125) << "server ctrl #" << i << " value: " << val << + " oid: " << oid << " critical: " << critical << " value: " << + QString::fromUtf8( value, value.size() ) << endl; + addControlOp( serverctrls, oid, value, critical ); + i++; + } + i = 0; + while ( hasMetaData( QString::fromLatin1("CLIENT_CTRL%1").arg(i) ) ) { + QCString val = metaData( QString::fromLatin1("CLIENT_CTRL%1").arg(i) ).utf8(); + LDIF::splitControl( val, oid, critical, value ); + kdDebug(7125) << "client ctrl #" << i << " value: " << val << + " oid: " << oid << " critical: " << critical << " value: " << + QString::fromUtf8( value, value.size() ) << endl; + addControlOp( clientctrls, oid, value, critical ); + i++; + } +} + +int LDAPProtocol::asyncSearch( LDAPUrl &usrc ) +{ + char **attrs = 0; + int msgid; + LDAPControl **serverctrls = 0, **clientctrls = 0; + + int count = usrc.attributes().count(); + if ( count > 0 ) { + attrs = static_cast<char**>( malloc((count+1) * sizeof(char*)) ); + for (int i=0; i<count; i++) + attrs[i] = strdup( (*usrc.attributes().at(i)).utf8() ); + attrs[count] = 0; + } + + int retval, scope = LDAP_SCOPE_BASE; + switch ( usrc.scope() ) { + case LDAPUrl::Base: + scope = LDAP_SCOPE_BASE; + break; + case LDAPUrl::One: + scope = LDAP_SCOPE_ONELEVEL; + break; + case LDAPUrl::Sub: + scope = LDAP_SCOPE_SUBTREE; + break; + } + + controlsFromMetaData( &serverctrls, &clientctrls ); + + kdDebug(7125) << "asyncSearch() dn=\"" << usrc.dn() << "\" scope=" << + usrc.scope() << " filter=\"" << usrc.filter() << "\" attrs=" << usrc.attributes() << + endl; + retval = ldap_search_ext( mLDAP, usrc.dn().utf8(), scope, + usrc.filter().isEmpty() ? QCString() : usrc.filter().utf8(), attrs, 0, + serverctrls, clientctrls, + 0, mSizeLimit, &msgid ); + + ldap_controls_free( serverctrls ); + ldap_controls_free( clientctrls ); + + // free the attributes list again + if ( count > 0 ) { + for ( int i=0; i<count; i++ ) free( attrs[i] ); + free(attrs); + } + + if ( retval == 0 ) retval = msgid; + return retval; +} + +QCString LDAPProtocol::LDAPEntryAsLDIF( LDAPMessage *message ) +{ + QCString result; + char *name; + struct berval **bvals; + BerElement *entry; + QByteArray tmp; + + char *dn = ldap_get_dn( mLDAP, message ); + if ( dn == NULL ) return QCString( "" ); + tmp.setRawData( dn, strlen( dn ) ); + result += LDIF::assembleLine( "dn", tmp ) + '\n'; + tmp.resetRawData( dn, strlen( dn ) ); + ldap_memfree( dn ); + + // iterate over the attributes + name = ldap_first_attribute(mLDAP, message, &entry); + while ( name != 0 ) + { + // print the values + bvals = ldap_get_values_len(mLDAP, message, name); + if ( bvals ) { + + for ( int i = 0; bvals[i] != 0; i++ ) { + char* val = bvals[i]->bv_val; + unsigned long len = bvals[i]->bv_len; + tmp.setRawData( val, len ); + result += LDIF::assembleLine( QString::fromUtf8( name ), tmp, 76 ) + '\n'; + tmp.resetRawData( val, len ); + } + ldap_value_free_len(bvals); + } + ldap_memfree( name ); + // next attribute + name = ldap_next_attribute(mLDAP, message, entry); + } + return result; +} + +void LDAPProtocol::addControlOp( LDAPControl ***pctrls, const QString &oid, + const QByteArray &value, bool critical ) +{ + LDAPControl **ctrls; + LDAPControl *ctrl = (LDAPControl *) malloc( sizeof( LDAPControl ) ); + + ctrls = *pctrls; + + kdDebug(7125) << "addControlOp: oid:'" << oid << "' val: '" << + QString::fromUtf8(value, value.size()) << "'" << endl; + int vallen = value.size(); + ctrl->ldctl_value.bv_len = vallen; + if ( vallen ) { + ctrl->ldctl_value.bv_val = (char*) malloc( vallen ); + memcpy( ctrl->ldctl_value.bv_val, value.data(), vallen ); + } else { + ctrl->ldctl_value.bv_val = 0; + } + ctrl->ldctl_iscritical = critical; + ctrl->ldctl_oid = strdup( oid.utf8() ); + + uint i = 0; + + if ( ctrls == 0 ) { + ctrls = (LDAPControl **) malloc ( 2 * sizeof( LDAPControl* ) ); + ctrls[ 0 ] = 0; + ctrls[ 1 ] = 0; + } else { + while ( ctrls[ i ] != 0 ) i++; + ctrls[ i + 1 ] = 0; + ctrls = (LDAPControl **) realloc( ctrls, (i + 2) * sizeof( LDAPControl * ) ); + } + ctrls[ i ] = ctrl; + + *pctrls = ctrls; +} + +void LDAPProtocol::addModOp( LDAPMod ***pmods, int mod_type, const QString &attr, + const QByteArray &value ) +{ +// kdDebug(7125) << "type: " << mod_type << " attr: " << attr << +// " value: " << QString::fromUtf8(value,value.size()) << +// " size: " << value.size() << endl; + LDAPMod **mods; + + mods = *pmods; + + uint i = 0; + + if ( mods == 0 ) { + mods = (LDAPMod **) malloc ( 2 * sizeof( LDAPMod* ) ); + mods[ 0 ] = (LDAPMod*) malloc( sizeof( LDAPMod ) ); + mods[ 1 ] = 0; + memset( mods[ 0 ], 0, sizeof( LDAPMod ) ); + } else { + while( mods[ i ] != 0 && + ( strcmp( attr.utf8(),mods[i]->mod_type ) != 0 || + ( mods[ i ]->mod_op & ~LDAP_MOD_BVALUES ) != mod_type ) ) i++; + + if ( mods[ i ] == 0 ) { + mods = ( LDAPMod ** )realloc( mods, (i + 2) * sizeof( LDAPMod * ) ); + if ( mods == 0 ) { + kdError() << "addModOp: realloc" << endl; + return; + } + mods[ i + 1 ] = 0; + mods[ i ] = ( LDAPMod* ) malloc( sizeof( LDAPMod ) ); + memset( mods[ i ], 0, sizeof( LDAPMod ) ); + } + } + + mods[ i ]->mod_op = mod_type | LDAP_MOD_BVALUES; + if ( mods[ i ]->mod_type == 0 ) mods[ i ]->mod_type = strdup( attr.utf8() ); + + *pmods = mods; + + int vallen = value.size(); + if ( vallen == 0 ) return; + BerValue *berval; + berval = ( BerValue* ) malloc( sizeof( BerValue ) ); + berval -> bv_val = (char*) malloc( vallen ); + berval -> bv_len = vallen; + memcpy( berval -> bv_val, value.data(), vallen ); + + if ( mods[ i ] -> mod_vals.modv_bvals == 0 ) { + mods[ i ]->mod_vals.modv_bvals = ( BerValue** ) malloc( sizeof( BerValue* ) * 2 ); + mods[ i ]->mod_vals.modv_bvals[ 0 ] = berval; + mods[ i ]->mod_vals.modv_bvals[ 1 ] = 0; + kdDebug(7125) << "addModOp: new bervalue struct " << endl; + } else { + uint j = 0; + while ( mods[ i ]->mod_vals.modv_bvals[ j ] != 0 ) j++; + mods[ i ]->mod_vals.modv_bvals = ( BerValue ** ) + realloc( mods[ i ]->mod_vals.modv_bvals, (j + 2) * sizeof( BerValue* ) ); + if ( mods[ i ]->mod_vals.modv_bvals == 0 ) { + kdError() << "addModOp: realloc" << endl; + return; + } + mods[ i ]->mod_vals.modv_bvals[ j ] = berval; + mods[ i ]->mod_vals.modv_bvals[ j+1 ] = 0; + kdDebug(7125) << j << ". new bervalue " << endl; + } +} + +void LDAPProtocol::LDAPEntry2UDSEntry( const QString &dn, UDSEntry &entry, + const LDAPUrl &usrc, bool dir ) +{ + UDSAtom atom; + + int pos; + entry.clear(); + atom.m_uds = UDS_NAME; + atom.m_long = 0; + QString name = dn; + if ( (pos = name.find(",")) > 0 ) + name = name.left( pos ); + if ( (pos = name.find("=")) > 0 ) + name.remove( 0, pos+1 ); + name.replace(' ', "_"); + if ( !dir ) name += ".ldif"; + atom.m_str = name; + entry.append( atom ); + + // the file type + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = dir ? S_IFDIR : S_IFREG; + entry.append( atom ); + + // the mimetype + if (!dir) { + atom.m_uds = UDS_MIME_TYPE; + atom.m_long = 0; + atom.m_str = "text/plain"; + entry.append( atom ); + } + + atom.m_uds = UDS_ACCESS; + atom.m_long = dir ? 0500 : 0400; + entry.append( atom ); + + // the url + atom.m_uds = UDS_URL; + atom.m_long = 0; + LDAPUrl url; + url=usrc; + + url.setPath("/"+dn); + url.setScope( dir ? LDAPUrl::One : LDAPUrl::Base ); + atom.m_str = url.prettyURL(); + entry.append( atom ); +} + +void LDAPProtocol::changeCheck( LDAPUrl &url ) +{ + bool critical; + bool tls = ( url.hasExtension( "x-tls" ) ); + int ver = 3; + if ( url.hasExtension( "x-ver" ) ) + ver = url.extension( "x-ver", critical).toInt(); + bool authSASL = url.hasExtension( "x-sasl" ); + QString mech; + if ( url.hasExtension( "x-mech" ) ) + mech = url.extension( "x-mech", critical).upper(); + QString realm; + if ( url.hasExtension( "x-realm" ) ) + mech = url.extension( "x-realm", critical).upper(); + QString bindname; + if ( url.hasExtension( "bindname" ) ) + bindname = url.extension( "bindname", critical).upper(); + int timelimit = 0; + if ( url.hasExtension( "x-timelimit" ) ) + timelimit = url.extension( "x-timelimit", critical).toInt(); + int sizelimit = 0; + if ( url.hasExtension( "x-sizelimit" ) ) + sizelimit = url.extension( "x-sizelimit", critical).toInt(); + + if ( !authSASL && bindname.isEmpty() ) bindname = mUser; + + if ( tls != mTLS || ver != mVer || authSASL != mAuthSASL || mech != mMech || + mRealm != realm || mBindName != bindname || mTimeLimit != timelimit || + mSizeLimit != sizelimit ) { + closeConnection(); + mTLS = tls; + mVer = ver; + mAuthSASL = authSASL; + mMech = mech; + mRealm = realm; + mBindName = bindname; + mTimeLimit = timelimit; + mSizeLimit = sizelimit; + kdDebug(7125) << "parameters changed: tls = " << mTLS << + " version: " << mVer << "SASLauth: " << mAuthSASL << endl; + openConnection(); + if ( mAuthSASL ) { + url.setUser( mUser ); + } else { + url.setUser( mBindName ); + } + } else { + if ( !mLDAP ) openConnection(); + } +} + +void LDAPProtocol::setHost( const QString& host, int port, + const QString& user, const QString& password ) +{ + + if( mHost != host || mPort != port || mUser != user || mPassword != password ) + closeConnection(); + + mHost = host; + if( port > 0 ) + mPort = port; + else { + struct servent *pse; + if ( (pse = getservbyname(mProtocol, "tcp")) == NULL ) + if ( mProtocol == "ldaps" ) + mPort = 636; + else + mPort = 389; + else + mPort = ntohs( pse->s_port ); + } + mUser = user; + mPassword = password; + + kdDebug(7125) << "setHost: " << host << " port: " << port << " user: " << + mUser << " pass: [protected]" << endl; +} + +static int kldap_sasl_interact( LDAP *, unsigned, void *slave, void *in ) +{ + return ((LDAPProtocol*) slave)->saslInteract( in ); +} + +void LDAPProtocol::fillAuthInfo( AuthInfo &info ) +{ + info.url.setProtocol( mProtocol ); + info.url.setHost( mHost ); + info.url.setPort( mPort ); + info.url.setUser( mUser ); + info.caption = i18n("LDAP Login"); + info.comment = QString::fromLatin1( mProtocol ) + "://" + mHost + ":" + + QString::number( mPort ); + info.commentLabel = i18n("site:"); + info.username = mAuthSASL ? mUser : mBindName; + info.password = mPassword; + info.keepPassword = true; +} + +int LDAPProtocol::saslInteract( void *in ) +{ +#if defined HAVE_SASL_H || defined HAVE_SASL_SASL_H + AuthInfo info; + fillAuthInfo( info ); + + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so it doesn't need a popup + //window for getting this info + for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { + if ( interact->id == SASL_CB_AUTHNAME || + interact->id == SASL_CB_PASS ) { + + if ( info.username.isEmpty() || info.password.isEmpty() ) { + + const bool cached = checkCachedAuthentication( info ); + + if ( ! ( ( mFirstAuth && cached ) || + ( mFirstAuth ? + openPassDlg( info ) : + openPassDlg( info, i18n("Invalid authorization information.") ) ) ) ) { + kdDebug(7125) << "Dialog cancelled!" << endl; + mCancel = true; + return LDAP_USER_CANCELLED; + } + mUser = info.username; + mPassword = info.password; + } + break; + } + } + + interact = ( sasl_interact_t * ) in; + QString value; + + while( interact->id != SASL_CB_LIST_END ) { + value = ""; + switch( interact->id ) { + case SASL_CB_GETREALM: + value = mRealm; + kdDebug(7125) << "SASL_REALM=" << mRealm << endl; + break; + case SASL_CB_AUTHNAME: + value = mUser; + kdDebug(7125) << "SASL_AUTHNAME=" << mUser << endl; + break; + case SASL_CB_PASS: + value = mPassword; + kdDebug(7125) << "SASL_PASSWD=[hidden]" << endl; + break; + case SASL_CB_USER: + value = mBindName; + kdDebug(7125) << "SASL_AUTHZID=" << mBindName << endl; + break; + } + if ( value.isEmpty() ) { + interact->result = NULL; + interact->len = 0; + } else { + interact->result = strdup( value.utf8() ); + interact->len = strlen( (const char *) interact->result ); + } + interact++; + } + +#endif + return LDAP_SUCCESS; +} + +void LDAPProtocol::openConnection() +{ + if ( mLDAP ) return; + + int version,ret; + + version = ( mVer == 2 ) ? LDAP_VERSION2 : LDAP_VERSION3; + + KURL Url; + Url.setProtocol( mProtocol ); + Url.setHost( mHost ); + Url.setPort( mPort ); + + AuthInfo info; + fillAuthInfo( info ); +/////////////////////////////////////////////////////////////////////////// + kdDebug(7125) << "OpenConnection to " << mHost << ":" << mPort << endl; + + ret = ldap_initialize( &mLDAP, Url.htmlURL().utf8() ); + if ( ret != LDAP_SUCCESS ) { + LDAPErr( Url, ret ); + return; + } + + if ( (ldap_set_option( mLDAP, LDAP_OPT_PROTOCOL_VERSION, &version )) != + LDAP_OPT_SUCCESS ) { + + closeConnection(); + error( ERR_UNSUPPORTED_ACTION, + i18n("Cannot set LDAP protocol version %1").arg(version) ); + return; + } + + if ( mTLS ) { + kdDebug(7125) << "start TLS" << endl; + if ( ( ret = ldap_start_tls_s( mLDAP, NULL, NULL ) ) != LDAP_SUCCESS ) { + LDAPErr( Url ); + return; + } + } + + if ( mSizeLimit ) { + kdDebug(7125) << "sizelimit: " << mSizeLimit << endl; + if ( ldap_set_option( mLDAP, LDAP_OPT_SIZELIMIT, &mSizeLimit ) != LDAP_SUCCESS ) { + closeConnection(); + error( ERR_UNSUPPORTED_ACTION, + i18n("Cannot set size limit.")); + return; + } + } + + if ( mTimeLimit ) { + kdDebug(7125) << "timelimit: " << mTimeLimit << endl; + if ( ldap_set_option( mLDAP, LDAP_OPT_TIMELIMIT, &mTimeLimit ) != LDAP_SUCCESS ) { + closeConnection(); + error( ERR_UNSUPPORTED_ACTION, + i18n("Cannot set time limit.")); + return; + } + } + +#if !defined HAVE_SASL_H && !defined HAVE_SASL_SASL_H + if ( mAuthSASL ) { + closeConnection(); + error( ERR_SLAVE_DEFINED, + i18n("SASL authentication not compiled into the ldap ioslave.") ); + return; + } +#endif + + bool auth = false; + QString mechanism = mMech.isEmpty() ? "DIGEST-MD5" : mMech; + mFirstAuth = true; mCancel = false; + + const bool cached = checkCachedAuthentication( info ); + + ret = LDAP_SUCCESS; + while (!auth) { + if ( !mAuthSASL && ( + ( mFirstAuth && + !( mBindName.isEmpty() && mPassword.isEmpty() ) && //For anonymous bind + ( mBindName.isEmpty() || mPassword.isEmpty() ) ) || !mFirstAuth ) ) + { + if ( ( mFirstAuth && cached ) || + ( mFirstAuth ? + openPassDlg( info ) : + openPassDlg( info, i18n("Invalid authorization information.") ) ) ) { + + mBindName = info.username; + mPassword = info.password; + } else { + kdDebug(7125) << "Dialog cancelled!" << endl; + error( ERR_USER_CANCELED, QString::null ); + closeConnection(); + return; + } + } + kdDebug(7125) << "user: " << mUser << " bindname: " << mBindName << endl; + ret = +#if defined HAVE_SASL_H || defined HAVE_SASL_SASL_H + mAuthSASL ? + ldap_sasl_interactive_bind_s( mLDAP, NULL, mechanism.utf8(), + NULL, NULL, LDAP_SASL_INTERACTIVE, &kldap_sasl_interact, this ) : +#endif + ldap_simple_bind_s( mLDAP, mBindName.utf8(), mPassword.utf8() ); + + mFirstAuth = false; + if ( ret != LDAP_INVALID_CREDENTIALS && + ret != LDAP_INSUFFICIENT_ACCESS && + ret != LDAP_INAPPROPRIATE_AUTH ) { + kdDebug(7125) << "ldap_bind retval: " << ret << endl; + auth = true; + if ( ret != LDAP_SUCCESS ) { + if ( mCancel ) + error( ERR_USER_CANCELED, QString::null ); + else + LDAPErr( Url ); + closeConnection(); + return; + } + } + } + + kdDebug(7125) << "connected!" << endl; + connected(); +} + +void LDAPProtocol::closeConnection() +{ + if (mLDAP) ldap_unbind(mLDAP); + mLDAP = 0; + kdDebug(7125) << "connection closed!" << endl; +} + +/** + * Get the information contained in the URL. + */ +void LDAPProtocol::get( const KURL &_url ) +{ + kdDebug(7125) << "get(" << _url << ")" << endl; + + LDAPUrl usrc(_url); + int ret, id; + LDAPMessage *msg,*entry; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + if ( (id = asyncSearch( usrc )) == -1 ) { + LDAPErr( _url ); + return; + } + + // tell the mimetype + mimeType("text/plain"); + // collect the result + QCString result; + filesize_t processed_size = 0; + QByteArray array; + + while( true ) { + ret = ldap_result( mLDAP, id, 0, NULL, &msg ); + if ( ret == -1 ) { + LDAPErr( _url ); + return; + } + kdDebug(7125) << " ldap_result: " << ret << endl; + if ( ret == LDAP_RES_SEARCH_RESULT ) break; + if ( ret != LDAP_RES_SEARCH_ENTRY ) continue; + + entry = ldap_first_entry( mLDAP, msg ); + while ( entry ) { + result = LDAPEntryAsLDIF(entry); + result += '\n'; + uint len = result.length(); + processed_size += len; + array.setRawData( result.data(), len ); + data(array); + processedSize( processed_size ); + array.resetRawData( result.data(), len ); + + entry = ldap_next_entry( mLDAP, entry ); + } + LDAPErr( _url ); + + ldap_msgfree(msg); + // tell the length + } + + totalSize(processed_size); + + array.resize(0); + // tell we are finished + data(array); + + // tell we are finished + finished(); +} + +/** + * Test if the url contains a directory or a file. + */ +void LDAPProtocol::stat( const KURL &_url ) +{ + kdDebug(7125) << "stat(" << _url << ")" << endl; + + QStringList att,saveatt; + LDAPUrl usrc(_url); + LDAPMessage *msg; + int ret, id; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + // look how many entries match + saveatt = usrc.attributes(); + att.append( "dn" ); + usrc.setAttributes( att ); + if ( _url.query().isEmpty() ) usrc.setScope( LDAPUrl::One ); + + if ( (id = asyncSearch( usrc )) == -1 ) { + LDAPErr( _url ); + return; + } + + kdDebug(7125) << "stat() getting result" << endl; + do { + ret = ldap_result( mLDAP, id, 0, NULL, &msg ); + if ( ret == -1 ) { + LDAPErr( _url ); + return; + } + if ( ret == LDAP_RES_SEARCH_RESULT ) { + ldap_msgfree( msg ); + error( ERR_DOES_NOT_EXIST, _url.prettyURL() ); + return; + } + } while ( ret != LDAP_RES_SEARCH_ENTRY ); + + ldap_msgfree( msg ); + ldap_abandon( mLDAP, id ); + + usrc.setAttributes( saveatt ); + + UDSEntry uds; + bool critical; + LDAPEntry2UDSEntry( usrc.dn(), uds, usrc, usrc.extension("x-dir", critical) != "base" ); + + statEntry( uds ); + // we are done + finished(); +} + +/** + * Deletes one entry; + */ +void LDAPProtocol::del( const KURL &_url, bool ) +{ + kdDebug(7125) << "del(" << _url << ")" << endl; + + LDAPUrl usrc(_url); + int ret; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + kdDebug(7125) << " del: " << usrc.dn().utf8() << endl ; + + if ( (ret = ldap_delete_s( mLDAP,usrc.dn().utf8() )) != LDAP_SUCCESS ) { + LDAPErr( _url ); + return; + } + finished(); +} + +#define FREELDAPMEM { \ + ldap_mods_free( lmod, 1 ); \ + ldap_controls_free( serverctrls ); \ + ldap_controls_free( clientctrls ); \ + lmod = 0; serverctrls = 0; clientctrls = 0; \ + } + +void LDAPProtocol::put( const KURL &_url, int, bool overwrite, bool ) +{ + kdDebug(7125) << "put(" << _url << ")" << endl; + + LDAPUrl usrc(_url); + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + LDAPMod **lmod = 0; + LDAPControl **serverctrls = 0, **clientctrls = 0; + QByteArray buffer; + int result = 0; + LDIF::ParseVal ret; + LDIF ldif; + ret = LDIF::MoreData; + int ldaperr; + + + do { + if ( ret == LDIF::MoreData ) { + dataReq(); // Request for data + result = readData( buffer ); + ldif.setLDIF( buffer ); + } + if ( result < 0 ) { + //error + FREELDAPMEM; + return; + } + if ( result == 0 ) { + kdDebug(7125) << "EOF!" << endl; + ldif.endLDIF(); + } + do { + + ret = ldif.nextItem(); + kdDebug(7125) << "nextitem: " << ret << endl; + + switch ( ret ) { + case LDIF::None: + case LDIF::NewEntry: + case LDIF::MoreData: + break; + case LDIF::EndEntry: + ldaperr = LDAP_SUCCESS; + switch ( ldif.entryType() ) { + case LDIF::Entry_None: + error( ERR_INTERNAL, i18n("The LDIF parser failed.") ); + FREELDAPMEM; + return; + case LDIF::Entry_Del: + kdDebug(7125) << "kio_ldap_del" << endl; + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_delete_ext_s( mLDAP, ldif.dn().utf8(), + serverctrls, clientctrls ); + FREELDAPMEM; + break; + case LDIF::Entry_Modrdn: + kdDebug(7125) << "kio_ldap_modrdn olddn:" << ldif.dn() << + " newRdn: " << ldif.newRdn() << + " newSuperior: " << ldif.newSuperior() << + " deloldrdn: " << ldif.delOldRdn() << endl; + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_rename_s( mLDAP, ldif.dn().utf8(), ldif.newRdn().utf8(), + ldif.newSuperior().isEmpty() ? QCString() : ldif.newSuperior().utf8(), + ldif.delOldRdn(), serverctrls, clientctrls ); + + FREELDAPMEM; + break; + case LDIF::Entry_Mod: + kdDebug(7125) << "kio_ldap_mod" << endl; + if ( lmod ) { + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_modify_ext_s( mLDAP, ldif.dn().utf8(), lmod, + serverctrls, clientctrls ); + FREELDAPMEM; + } + break; + case LDIF::Entry_Add: + kdDebug(7125) << "kio_ldap_add " << ldif.dn() << endl; + if ( lmod ) { + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_add_ext_s( mLDAP, ldif.dn().utf8(), lmod, + serverctrls, clientctrls ); + if ( ldaperr == LDAP_ALREADY_EXISTS && overwrite ) { + kdDebug(7125) << ldif.dn() << " already exists, delete first" << endl; + ldaperr = ldap_delete_s( mLDAP, ldif.dn().utf8() ); + if ( ldaperr == LDAP_SUCCESS ) + ldaperr = ldap_add_ext_s( mLDAP, ldif.dn().utf8(), lmod, + serverctrls, clientctrls ); + } + FREELDAPMEM; + } + break; + } + if ( ldaperr != LDAP_SUCCESS ) { + kdDebug(7125) << "put ldap error: " << ldap_err2string(ldaperr) << endl; + LDAPErr( _url ); + FREELDAPMEM; + return; + } + break; + case LDIF::Item: + switch ( ldif.entryType() ) { + case LDIF::Entry_Mod: { + int modtype = 0; + switch ( ldif.modType() ) { + case LDIF::Mod_None: + modtype = 0; + break; + case LDIF::Mod_Add: + modtype = LDAP_MOD_ADD; + break; + case LDIF::Mod_Replace: + modtype = LDAP_MOD_REPLACE; + break; + case LDIF::Mod_Del: + modtype = LDAP_MOD_DELETE; + break; + } + addModOp( &lmod, modtype, ldif.attr(), ldif.val() ); + break; + } + case LDIF::Entry_Add: + if ( ldif.val().size() > 0 ) + addModOp( &lmod, 0, ldif.attr(), ldif.val() ); + break; + default: + error( ERR_INTERNAL, i18n("The LDIF parser failed.") ); + FREELDAPMEM; + return; + } + break; + case LDIF::Control: + addControlOp( &serverctrls, ldif.oid(), ldif.val(), ldif.critical() ); + break; + case LDIF::Err: + error( ERR_SLAVE_DEFINED, + i18n( "Invalid LDIF file in line %1." ).arg( ldif.lineNo() ) ); + FREELDAPMEM; + return; + } + } while ( ret != LDIF::MoreData ); + } while ( result > 0 ); + + FREELDAPMEM; + finished(); +} + +/** + * List the contents of a directory. + */ +void LDAPProtocol::listDir( const KURL &_url ) +{ + int ret, ret2, id, id2; + unsigned long total=0; + char *dn; + QStringList att,saveatt; + LDAPMessage *entry,*msg,*entry2,*msg2; + LDAPUrl usrc(_url),usrc2; + bool critical; + bool isSub = ( usrc.extension( "x-dir", critical ) == "sub" ); + + kdDebug(7125) << "listDir(" << _url << ")" << endl; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + usrc2 = usrc; + + saveatt = usrc.attributes(); + // look up the entries + if ( isSub ) { + att.append("dn"); + usrc.setAttributes(att); + } + if ( _url.query().isEmpty() ) usrc.setScope( LDAPUrl::One ); + + if ( (id = asyncSearch( usrc )) == -1 ) { + LDAPErr( _url ); + return; + } + + usrc.setAttributes( "" ); + usrc.setExtension( "x-dir", "base" ); + // publish the results + UDSEntry uds; + + while( true ) { + ret = ldap_result( mLDAP, id, 0, NULL, &msg ); + if ( ret == -1 ) { + LDAPErr( _url ); + return; + } + if ( ret == LDAP_RES_SEARCH_RESULT ) break; + if ( ret != LDAP_RES_SEARCH_ENTRY ) continue; + kdDebug(7125) << " ldap_result: " << ret << endl; + + entry = ldap_first_entry( mLDAP, msg ); + while( entry ) { + + total++; + uds.clear(); + + dn = ldap_get_dn( mLDAP, entry ); + kdDebug(7125) << "dn: " << dn << endl; + LDAPEntry2UDSEntry( QString::fromUtf8(dn), uds, usrc ); + listEntry( uds, false ); +// processedSize( total ); + kdDebug(7125) << " total: " << total << " " << usrc.prettyURL() << endl; + + // publish the sub-directories (if dirmode==sub) + if ( isSub ) { + usrc2.setDn( QString::fromUtf8( dn ) ); + usrc2.setScope( LDAPUrl::One ); + usrc2.setAttributes( att ); + usrc2.setFilter( QString::null ); + kdDebug(7125) << "search2 " << dn << endl; + if ( (id2 = asyncSearch( usrc2 )) != -1 ) { + while ( true ) { + kdDebug(7125) << " next result " << endl; + ret2 = ldap_result( mLDAP, id2, 0, NULL, &msg2 ); + if ( ret2 == -1 ) break; + if ( ret2 == LDAP_RES_SEARCH_RESULT ) { + ldap_msgfree( msg2 ); + break; + } + if ( ret2 == LDAP_RES_SEARCH_ENTRY ) { + entry2=ldap_first_entry( mLDAP, msg2 ); + if ( entry2 ) { + usrc2.setAttributes( saveatt ); + usrc2.setFilter( usrc.filter() ); + LDAPEntry2UDSEntry( QString::fromUtf8( dn ), uds, usrc2, true ); + listEntry( uds, false ); + total++; + } + ldap_msgfree( msg2 ); + ldap_abandon( mLDAP, id2 ); + break; + } + } + } + } + free( dn ); + + entry = ldap_next_entry( mLDAP, entry ); + } + LDAPErr( _url ); + ldap_msgfree( msg ); + } + +// totalSize( total ); + + uds.clear(); + listEntry( uds, true ); + // we are done + finished(); +} diff --git a/kioslave/ldap/kio_ldap.h b/kioslave/ldap/kio_ldap.h new file mode 100644 index 000000000..ff722d345 --- /dev/null +++ b/kioslave/ldap/kio_ldap.h @@ -0,0 +1,65 @@ +#ifndef __LDAP_H__ +#define __LDAP_H__ + +#include <qstring.h> +#include <qvaluelist.h> + +#include <kio/slavebase.h> +#include <kio/authinfo.h> + +#define LDAP_DEPRECATED 1 /* Needed for ldap_simple_bind_s with openldap >= 2.3.x */ +#include <lber.h> +#include <ldap.h> +#include <kabc/ldapurl.h> + +class LDAPProtocol : public KIO::SlaveBase +{ + public: + LDAPProtocol( const QCString &protocol, const QCString &pool, const QCString &app ); + virtual ~LDAPProtocol(); + + virtual void setHost( const QString& host, int port, + const QString& user, const QString& pass ); + + virtual void openConnection(); + virtual void closeConnection(); + + virtual void get( const KURL& url ); + virtual void stat( const KURL& url ); + virtual void listDir( const KURL& url ); + virtual void del( const KURL& url, bool isfile ); + virtual void put( const KURL& url, int permissions, bool overwrite, bool resume ); + + int saslInteract( void *in ); + + private: + + QString mHost; + int mPort; + QString mUser; + QString mPassword; + LDAP *mLDAP; + int mVer, mSizeLimit, mTimeLimit; + bool mTLS; + bool mAuthSASL; + QString mMech,mRealm,mBindName; + bool mCancel, mFirstAuth; + + void controlsFromMetaData( LDAPControl ***serverctrls, + LDAPControl ***clientctrls ); + void addControlOp( LDAPControl ***pctrls, const QString &oid, + const QByteArray &value, bool critical ); + void addModOp( LDAPMod ***pmods, int mod_type, + const QString &attr, const QByteArray &value ); + void LDAPEntry2UDSEntry( const QString &dn, KIO::UDSEntry &entry, + const KABC::LDAPUrl &usrc, bool dir=false ); + int asyncSearch( KABC::LDAPUrl &usrc ); + + QCString LDAPEntryAsLDIF( LDAPMessage *msg ); + void LDAPErr( const KURL &url, int err = LDAP_SUCCESS ); + void changeCheck( KABC::LDAPUrl &url ); + + void fillAuthInfo( KIO::AuthInfo &info ); +}; + +#endif diff --git a/kioslave/ldap/ldap.protocol b/kioslave/ldap/ldap.protocol new file mode 100644 index 000000000..3ab0b7eb5 --- /dev/null +++ b/kioslave/ldap/ldap.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_ldap +protocol=ldap +input=none +output=filesystem +listing=Name, +reading=true +source=true +writing=true +#makedir=true +deleting=true +#linking=true +#moving=true +mimetype=text/plain +determineMimetypeFromExtension=false +DocPath=kioslave/ldap.html +Icon=kaddressbook diff --git a/kioslave/ldap/ldaps.protocol b/kioslave/ldap/ldaps.protocol new file mode 100644 index 000000000..542faa597 --- /dev/null +++ b/kioslave/ldap/ldaps.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_ldap +protocol=ldaps +input=none +output=filesystem +listing=Name, +reading=true +source=true +writing=true +#makedir=true +deleting=true +#linking=true +#moving=true +mimetype=text/plain +determineMimetypeFromExtension=false +DocPath=kioslave/ldap.html +Icon=kaddressbook diff --git a/kioslave/mac/AUTHORS b/kioslave/mac/AUTHORS new file mode 100644 index 000000000..78b940be4 --- /dev/null +++ b/kioslave/mac/AUTHORS @@ -0,0 +1 @@ +Jonathan Riddell, jr@jriddell.org diff --git a/kioslave/mac/ChangeLog b/kioslave/mac/ChangeLog new file mode 100644 index 000000000..6c8f647ce --- /dev/null +++ b/kioslave/mac/ChangeLog @@ -0,0 +1,39 @@ +10 Feb 2002 - Jonathan Riddell <jr@jriddell.org> + - v1.0 + - Nicer icon (thanks to ikons project) + - moved into KDE CVS kdenonbeta + - Everything seems to be stable, lets up the version number to prove me wrong + +1 Feb 2002 - Jonathan Riddell <jr@jriddell.org> + - v0.8 + - Now displays hidden files + - Locked files are copied as read only + - Nice icon + - sources now use autoconf/automake + - Fixed regular expression which matches some files as directories + - Aliases now display as links + +26 Jan 2002 - Jonathan Riddell <jr@jriddell.org> + - v0.7 + - Converts some HFS+ file types and application labels into mimetypes + - Added a SuSE Makefile + - Hopefully managed to get the SuSE RPMs working + - When copying files kio-mac now reports the amount progressed so + you can see how much has been copied + - Text files are now copies over in text mode by default + +24 Jan2002 - Jonathan Riddell <jr@jriddell.org> + - v0.6 + - It can now read empty directories without complaining + - Fixed the way data was being passed back to KDE which corrupted some files + - Fixed Makefile a bit + - Now reports the modified date to as good an acuracy as hpls -l gives it + - Found a truly bizarre bug while doing the above which broke + things a lot less than it should have + +21 Jan 2002 - Jonathan Riddell <jr@jriddell.org> + - v0.5 + - Initial release + - talks to hfs+ partitions using hptools + - surprisingly successful + diff --git a/kioslave/mac/Makefile.am b/kioslave/mac/Makefile.am new file mode 100644 index 000000000..f2f0d97fb --- /dev/null +++ b/kioslave/mac/Makefile.am @@ -0,0 +1,23 @@ +## Makfile.am for kio_mac + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +####### Files + +kde_module_LTLIBRARIES = kio_mac.la + +kio_mac_la_SOURCES = kio_mac.cpp +kio_mac_la_LIBADD = -lkio +kio_mac_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_mac.h + +kdelnk_DATA = mac.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO +KDE_ICON = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_mac.pot diff --git a/kioslave/mac/README b/kioslave/mac/README new file mode 100644 index 000000000..bb907dd9c --- /dev/null +++ b/kioslave/mac/README @@ -0,0 +1,65 @@ +From the hfsplus man page: + + "HFS+, also known as the Macintosh Extended Format, was + introduced by Apple Computer in 1998 with the release of + MacOS 8.1. It contains many improvements over the old HFS + file system, most notably the ability to allocate up to + 2^64 blocks, resulting in much more efficient storage of + many small files on large disks." + +This kio slave lets you read an HFS+ partition from konqueror +or any other KDE file dialogue. It uses hfsplus tools so you will +need these installed for it to work. + +TO INSTALL + +Read the INSTALL file. + + +NOTES + +Just enter mac:/ into Konqueror and you should see the contents of +your MacOS partition. Actually you'll probably get an error message +saying you havn't specified the right partition. Enter something +like mac:/?dev=/dev/hda2 to specify the partition (if you don't know +which partition MacOS is on you can probably guess by changing hda2 to +hda3 and so on or use the print command from mac-fdisk. The partition +will be used the next time so you don't have to specify it each time. + +Hfsplus tools let you see the file and copy data from the HFS+ +partition but not to copy data to it or change the filenames or such like. + +HFS+ actually keeps two files for every one you see (called forks), a +resource fork and a data fork. The default copy mode when you're +copying files across to you native drive is raw data which means it +just copies the data. Text files are copied in text mode (same as raw +format but changes the line endings to be Unix friendly and gets rid +of some funny extra characters - strongly advised for text files) +unless you specify otherwise. You can also copy the files across in +Mac Binary II format or specify text or raw format with another query: +mac:/myfile?mode=b or mac:/myfile?mode=t See man hpcopy for more. + +Note that you need permissions to read your HFS+ partition. How you +get this depends on your distribution, do a ls -l /dev/hdaX on it to +see. Under Debian you have to be in the disk group (just add your +username to the end of the entry in /etc/group). + +File types are done with matching the HFS+ type and application label +and then by extentions. See the source for the exact matching that +happens and feel free to suggest improvements. + +For some reason some directories in MacOS end in a funny tall f +character. This seems to confuse hfstools. + +You can't easiily use the command line tools while you are browsing +using kio-mac in Konqueror. Konqueror continuously refreshes it's +view which mean hpmount is being called every few seconds. Click on +Konqueror's home button before using the tools yourself on the command +line. + +Hidden files are now shown all the time. Apparantly Konqueror only +considers files with a dot at the front of the name to be hidden which +is a bit system dependant. + +Please e-mail me with any comments, problems and success stories: +Jonathan Riddell, jr@jriddell.org diff --git a/kioslave/mac/TODO b/kioslave/mac/TODO new file mode 100644 index 000000000..e94d88254 --- /dev/null +++ b/kioslave/mac/TODO @@ -0,0 +1,14 @@ +FIXMEs: + Amazingly, none that I can think of + +grep TODO kio_mac.cpp + //TODO this means dev=foo must be the last argument in the query + //TODO this error interrupts the user when typing ?dev=foo on each letter of foo + //TODO are there any more characters to escape? + QString theSize(fileRE.group(4)); //TODO: this is data size, what about resource size? + +Future things: + - maybe make it work with plain old hfs partitions + - possibly use libhfsp directly + - A Friend suggested reading the resource data for the icon to display - advanced I think. + - Follow symlinks/aliases (requires reading the resource fork as well) diff --git a/kioslave/mac/cr16-app-mac.png b/kioslave/mac/cr16-app-mac.png Binary files differnew file mode 100644 index 000000000..930694eab --- /dev/null +++ b/kioslave/mac/cr16-app-mac.png diff --git a/kioslave/mac/cr32-app-mac.png b/kioslave/mac/cr32-app-mac.png Binary files differnew file mode 100644 index 000000000..87df6fb85 --- /dev/null +++ b/kioslave/mac/cr32-app-mac.png diff --git a/kioslave/mac/kio_mac.cpp b/kioslave/mac/kio_mac.cpp new file mode 100644 index 000000000..56989487a --- /dev/null +++ b/kioslave/mac/kio_mac.cpp @@ -0,0 +1,561 @@ +/*************************************************************************** + kio_mac.cpp + ------------------- + copyright : (C) 2002 Jonathan Riddell + email : jr@jriddell.org + version : 1.0.1 + release date : 19 July 2002 + ***************************************************************************/ + +/*************************************************************************** + * * + * This program 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. * + * * + ***************************************************************************/ + +#define PARTITION "/dev/hda11" + +#include <kinstance.h> +#include <kdebug.h> +#include <klocale.h> +#include <kconfig.h> +#include <qstring.h> +#include <qregexp.h> + +#include <sys/stat.h> +#include <stdlib.h> +#include <iostream> +#include <time.h> + +#include "kio_mac.moc" + +using namespace KIO; + +extern "C" { + int KDE_EXPORT kdemain(int, char **argv) { + KInstance instance("kio_mac"); + MacProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; + } +} + +MacProtocol::MacProtocol(const QCString &pool, const QCString &app) + : QObject(), SlaveBase("mac", pool, app) { +/* logFile = new QFile("/home/jr/logfile"); + logFile->open(IO_ReadWrite | IO_Append); + logStream = new QTextStream(logFile); + *logStream << "Start Macprotocol()" << endl; + */ +} + +MacProtocol::~MacProtocol() { +/* *logStream << "destructor ~MacProtocol()" << endl; + logFile->close(); + delete logFile; + logFile = 0; + delete logStream; + logStream = 0; +*/ + delete myKProcess; + myKProcess = 0L; +} + +//get() called when a file is to be read +void MacProtocol::get(const KURL& url) { + QString path = prepareHP(url); //mount and change to correct directory - return the filename + QString query = url.query(); + QString mode("-"); + QString mime; + processedBytes = 0; + + //Find out the size and if it's a text file + UDSEntry entry = doStat(url); + UDSEntry::Iterator it; + for(it = entry.begin(); it != entry.end(); ++it) { + if ((*it).m_uds == KIO::UDS_MIME_TYPE) { + mime = (*it).m_str; + } + if ((*it).m_uds == KIO::UDS_SIZE) { + totalSize((*it).m_long); + } + } + + //find out if a mode has been specified in the query e.g. ?mode=t + //or if it's a text file then set the mode to text + int modepos = query.find("mode="); + int textpos = mime.find("text"); + if (modepos != -1) { + mode += query.mid(modepos + 5, 1); + if (mode != "-r" && mode != "-b" && mode != "-m" && mode != "-t" && mode != "-a") { + error(ERR_SLAVE_DEFINED, i18n("Unknown mode")); + } + } else if (textpos != -1) { + mode += "t"; + } else { + mode += "r"; + } + + //now we can read the file + myKProcess = new KProcess(); + + *myKProcess << "hpcopy" << mode << path << "-"; + + //data is now sent directly from the slot + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotSetDataStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + if (!myKProcess->normalExit() || !(myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("There was an error with hpcopy - please ensure it is installed")); + return; + } + + //clean up + delete myKProcess; myKProcess = 0; + //finish + data(QByteArray()); + finished(); +} + +//listDir() called when the user is looking at a directory +void MacProtocol::listDir(const KURL& url) { + QString filename = prepareHP(url); + + if (filename.isNull()) { + error(ERR_CANNOT_LAUNCH_PROCESS, i18n("No filename was found")); + } else { + myKProcess = new KProcess(); + *myKProcess << "hpls" << "-la" << filename; + + standardOutputStream = QString::null; + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("There was an error with hpls - please ensure it is installed")); + } + + //clean up + delete myKProcess; myKProcess = 0; + disconnect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + UDSEntry entry; + if (!standardOutputStream.isEmpty()) { + QTextStream in(&standardOutputStream, IO_ReadOnly); + QString line = in.readLine(); //throw away top file which shows current directory + line = in.readLine(); + + while (line != NULL) { + //1.0.4 puts this funny line in sometimes, we don't want it + if (line.contains("Thread ") == 0) { + entry = makeUDS(line); + listEntry(entry, false); + } + line = in.readLine(); + } + }//if standardOutputStream != null + + listEntry(entry, true); + finished(); + + }//if filename == null +} + +//stat() called to see if it's a file or directory, called before listDir() or get() +void MacProtocol::stat(const KURL& url) { + statEntry(doStat(url)); + finished(); +} + +//doStat(), does all the work that stat() needs +//it's been separated out so it can be called from get() which +//also need information +QValueList<KIO::UDSAtom> MacProtocol::doStat(const KURL& url) { + QString filename = prepareHP(url); + + if (filename.isNull()) { + error(ERR_SLAVE_DEFINED, i18n("No filename was found in the URL")); + } else if (! filename.isEmpty()) { + myKProcess = new KShellProcess(); + + *myKProcess << "hpls" << "-ld" << filename; + + standardOutputStream = QString::null; + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("hpls did not exit normally - please ensure you have installed the hfsplus tools")); + } + + //clean up + delete myKProcess; myKProcess = 0; + disconnect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + if (standardOutputStream.isEmpty()) { + filename.replace("\\ ", " "); //get rid of escapes + filename.replace("\\&", "&"); //mm, slashes... + filename.replace("\\!", "!"); + filename.replace("\\(", "("); + filename.replace("\\)", ")"); + error(ERR_DOES_NOT_EXIST, filename); + } else { + //remove trailing \n + QString line = standardOutputStream.left(standardOutputStream.length()-1); + UDSEntry entry = makeUDS(line); + return entry; + } + } else { //filename is empty means we're looking at root dir + //we don't have a listing for the root directory so here's a dummy one + UDSEntry entry = makeUDS("d 0 item Jan 01 2000 /"); + return entry; + }//if filename == null + + return QValueList<KIO::UDSAtom>(); +} + +//prepareHP() called from get() listDir() and stat() +//(re)mounts the partition and changes to the appropriate directory +QString MacProtocol::prepareHP(const KURL& url) { + QString path = url.path(-1); + if (path.left(1) == "/") { + path = path.mid(1); // strip leading slash + } + + //find out if a device has been specified in the query e.g. ?dev=/dev/fd0 + //or in the config file (query device entries are saved to config file) + QString device; + KConfig* config = new KConfig("macrc"); + + QString query = url.query(); + int modepos = query.find("dev="); + if (modepos == -1) { + //no device specified, read from config or go with #define PARTITION + device = config->readEntry("device",PARTITION); + } else { + //TODO this means dev=foo must be the last argument in the query + device = query.mid(modepos + 4); + config->writeEntry("device",device); + } + delete config; config = 0; + + //first we run just hpmount and check the output to see if it's version 1.0.2 or 1.0.4 + myKProcess = new KProcess(); + *myKProcess << "hpmount"; + standardOutputStream = QString::null; + connect(myKProcess, SIGNAL(receivedStderr(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + bool version102 = true; + + if (standardOutputStream.contains("options") != 0) { + version102 = false; + } + + delete myKProcess; myKProcess = 0; + disconnect(myKProcess, SIGNAL(receivedStderr(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + //now mount the drive + myKProcess = new KProcess(); + if (version102) { + *myKProcess << "hpmount" << device; + } else { + *myKProcess << "hpmount" << "-r" << device; + } + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + //TODO this error interrupts the user when typing ?dev=foo on each letter of foo + error(ERR_SLAVE_DEFINED, + i18n("hpmount did not exit normally - please ensure that hfsplus utils are installed,\n" + "that you have permission to read the partition (ls -l /dev/hdaX)\n" + "and that you have specified the correct partition.\n" + "You can specify partitions by adding ?dev=/dev/hda2 to the URL.")); + return NULL; + } + + //clean up + delete myKProcess; myKProcess = 0; + + //escape any funny characters + //TODO are there any more characters to escape? + path.replace(" ", "\\ "); + path.replace("&", "\\&"); + path.replace("!", "\\!"); + path.replace("(", "\\("); + path.replace(")", "\\)"); + + //then change to the right directory + int s; QString dir; + s = path.find('/'); + while (s != -1) { + dir = path.left(s); + path = path.mid(s+1); + + myKProcess = new KProcess(); + *myKProcess << "hpcd" << dir; + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("hpcd did not exit normally - please ensure it is installed")); + return NULL; + } + + //clean up + delete myKProcess; myKProcess = 0; + + s = path.find('/'); + } + + return path; +} + +//makeUDS() takes a line of output from hpls -l and converts it into +// one of these UDSEntrys to return +//called from listDir() and stat() +QValueList<KIO::UDSAtom> MacProtocol::makeUDS(const QString& _line) { + QString line(_line); + UDSEntry entry; + + //is it a file or a directory + QRegExp dirRE("^d. +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +(.*)"); + QRegExp fileRE("^([f|F]). +(....)/(....) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +(.*)"); + if (dirRE.exactMatch(line)) { + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = dirRE.cap(6); + entry.append(atom); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = makeTime(dirRE.cap(4), dirRE.cap(3), dirRE.cap(5)); + 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 = 0755; + entry.append(atom); + + } else if (fileRE.exactMatch(line)) { + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = fileRE.cap(9); + entry.append(atom); + + atom.m_uds = KIO::UDS_SIZE; + QString theSize(fileRE.cap(4)); //TODO: this is data size, what about resource size? + atom.m_long = theSize.toLong(); + entry.append(atom); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = makeTime(fileRE.cap(7), fileRE.cap(6), fileRE.cap(8)); + entry.append(atom); + + atom.m_uds = KIO::UDS_ACCESS; + if (QString(fileRE.cap(1)) == QString("F")) { //if locked then read only + atom.m_long = 0444; + } else { + atom.m_long = 0644; + } + entry.append(atom); + + atom.m_uds = KIO::UDS_MIME_TYPE; + QString mimetype = getMimetype(fileRE.cap(2),fileRE.cap(3)); + atom.m_str = mimetype.local8Bit(); + entry.append(atom); + + // Is it a file or a link/alias, just make aliases link to themselves + if (QString(fileRE.cap(2)) == QString("adrp") || + QString(fileRE.cap(2)) == QString("fdrp")) { + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = KIO::UDS_LINK_DEST; + atom.m_str = fileRE.cap(9); //I have a file called "Mozilla alias" the name + // of which displays funny because of this. + // No idea why. Same for other kioslaves. A font thing? + entry.append(atom); + } else { + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + } + } else { + error(ERR_INTERNAL, i18n("hpls output was not matched")); + } //if match dirRE or fileRE + + return entry; +} + +//slotGetStdOutput() grabs output from the hp commands +// and adds it to the buffer +void MacProtocol::slotGetStdOutput(KProcess*, char *s, int len) { + standardOutputStream += QString::fromLocal8Bit(s, len); +} + +//slotSetDataStdOutput() is used during hpcopy to give +//standard output to KDE +void MacProtocol::slotSetDataStdOutput(KProcess*, char *s, int len) { + processedBytes += len; + processedSize(processedBytes); + QByteArray array; + array.setRawData(s, len); + data(array); + array.resetRawData(s, len); +} + +//makeTime() takes in the date output from hpls -l +//and returns as good a timestamp as we're going to get +int MacProtocol::makeTime(QString mday, QString mon, QString third) { + int year; int month; int day; + int hour; int minute; + + //find the month + if (mon == "Jan") { month = 1; } + else if (mon == "Feb") { month = 2; } + else if (mon == "Mar") { month = 3; } + else if (mon == "Apr") { month = 4; } + else if (mon == "May") { month = 5; } + else if (mon == "Jun") { month = 6; } + else if (mon == "Jul") { month = 7; } + else if (mon == "Aug") { month = 8; } + else if (mon == "Sep") { month = 9; } + else if (mon == "Oct") { month = 10; } + else if (mon == "Nov") { month = 11; } + else if (mon == "Dec") { month = 12; } + else { + error(ERR_INTERNAL, i18n("Month output from hpls -l not matched")); + month = 13; + } + + //if the file is recent (last 12 months) hpls gives us the time, + // otherwise it only prints the year + QRegExp hourMin("(..):(..)"); + if (hourMin.exactMatch(third)) { + QDate currentDate(QDate::currentDate()); + + if (month > currentDate.month()) { + year = currentDate.year() - 1; + } else { + year = currentDate.year(); + } + QString h(hourMin.cap(1)); + QString m(hourMin.cap(2)); + hour = h.toInt(); + minute = m.toInt(); + } else { + year = third.toInt(); + hour = 0; + minute = 0; + }// if hour:min or year + + day = mday.toInt(); + + //check it's valid + if ( (!QDate::isValid(year, month, day)) || (!QTime::isValid(hour, minute, 0) ) ) { + error(ERR_INTERNAL, i18n("Could not parse a valid date from hpls")); + } + + //put it together and work it out + QDate fileDate(year, month, day); + QTime fileTime(hour, minute); + QDateTime fileDateTime(fileDate, fileTime); + + return fileDateTime.toTime_t(); +} + +QString MacProtocol::getMimetype(QString type, QString app) { + if (type == QString("TEXT") && app == QString("ttxt")) { + return QString("text/plain"); + } else if (type == QString("TEXT") && app == QString("udog")) { + return QString("text/html"); + } else if (type == QString("svgs")) { + return QString("text/xml"); + } else if (type == QString("ZIP ")) { + return QString("application/zip"); + } else if (type == QString("pZip")) { + return QString("application/zip"); + } else if (type == QString("APPL")) { + return QString("application/x-executable"); + } else if (type == QString("MooV")) { + return QString("video/quicktime"); + } else if (type == QString("TEXT") && app == QString("MSWD")) { + return QString("application/vnd.ms-word"); + } else if (type == QString("PDF ")) { + return QString("application/pdf"); + } else if (app == QString("CARO")) { + return QString("application/pdf"); + } else if (type == QString("SIT5")) { + return QString("application/x-stuffit"); + } else if (type == QString("SITD")) { + return QString("application/x-stuffit"); + } else if (type == QString("SIT!")) { + return QString("application/x-stuffit"); + } else if (app == QString("SIT!")) { + return QString("application/x-stuffit"); + } else if (type == QString("RTFf")) { + return QString("text/rtf"); + } else if (type == QString("GIFf")) { + return QString("image/gif"); + } else if (type == QString("JPEG")) { + return QString("image/jpeg"); + } else if (type == QString("PNGf")) { + return QString("image/png"); + } else if (type == QString("XBMm")) { + return QString("image/x-xbm"); + } else if (type == QString("EPSF")) { + return QString("image/x-epsf"); + } else if (type == QString("TIFF")) { + return QString("image/tiff"); + } else if (type == QString("PICT")) { + return QString("image/pict"); + } else if (type == QString("TPIC")) { + return QString("image/x-targa"); + } else if (type == QString("ULAW")) { + return QString("audio/basic"); + } else if (type == QString("AIFF")) { + return QString("audio/x-aiff"); + } else if (type == QString("WAVE")) { + return QString("audio/x-wav"); + } else if (type == QString("FFIL") && app == QString("DMOV")) { + return QString("application/x-font"); + } else if (type == QString("XLS3")) { + return QString("application/vnd.ms-excel"); + } else if (type == QString("XLS4")) { + return QString("application/vnd.ms-excel"); + } else if (type == QString("XLS5")) { + return QString("application/vnd.ms-excel"); + } else if (app == QString("MSWD")) { + return QString("application/vnd.ms-word"); + } else if (type == QString("TEXT")) { + return QString("text/plain"); + } else if (app == QString("ttxt")) { + return QString("text/plain"); + } + return QString("application/octet-stream"); +} + + diff --git a/kioslave/mac/kio_mac.h b/kioslave/mac/kio_mac.h new file mode 100644 index 000000000..c87217e08 --- /dev/null +++ b/kioslave/mac/kio_mac.h @@ -0,0 +1,55 @@ +/*************************************************************************** + mac.cpp + ------------------- + copyright : (C) 2002 Jonathan Riddell + email : jr@jriddell.org + version : 1.0 + release date : 10 Feburary 2002 + ***************************************************************************/ + +/*************************************************************************** + * * + * This program 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. * + * * + ***************************************************************************/ + +#include <kio/slavebase.h> +#include <kio/global.h> +#include <kurl.h> +#include <kprocess.h> + +#include <qstring.h> +#include <qcstring.h> +#include <qfile.h> +#include <qtextstream.h> + +class MacProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT +public: + MacProtocol(const QCString &pool, const QCString &app); + ~MacProtocol(); + virtual void get(const KURL& url ); + virtual void listDir(const KURL& url); + virtual void stat(const KURL& url); +protected slots: + void slotGetStdOutput(KProcess*, char*, int); + void slotSetDataStdOutput(KProcess*, char *s, int len); +protected: + QString prepareHP(const KURL& _url); + QValueList<KIO::UDSAtom> makeUDS(const QString& _line); + int makeTime(QString mday, QString mon, QString third); + QString getMimetype(QString type, QString app); + QValueList<KIO::UDSAtom> doStat(const KURL& url); + + KIO::filesize_t processedBytes; + QString standardOutputStream; + KProcess* myKProcess; + + //for debugging + //QFile* logFile; + //QTextStream* logStream; +}; diff --git a/kioslave/mac/mac.protocol b/kioslave/mac/mac.protocol new file mode 100644 index 000000000..cef77621d --- /dev/null +++ b/kioslave/mac/mac.protocol @@ -0,0 +1,73 @@ +[Protocol] +exec=kio_mac +protocol=mac +input=none +output=filesystem +reading=true +listing=Name,Type,Size,Date +defaultMimetype=application/octet-stream +Description=A kioslave for MacOS HFS+ partitions +Description[af]='n Kioslave vir MacOS HFS+ partisies +Description[be]=Kioslave Ð´Ð»Ñ Ñ€Ð°Ð·Ð´Ð·ÐµÐ»Ð°Ñž MacOS HFS+ +Description[bn]=মà§à¦¯à¦¾à¦•-ও-à¦à¦¸ HFS+ পারà§à¦Ÿà¦¿à¦¶à¦¨-à¦à¦° জনà§à¦¯ à¦à¦•টি kioslave +Description[br]=Ur c'hioslave evit ar parzhadurioù MacOS HFS+ +Description[bs]=kioslave za MacOS HFS+ particije +Description[ca]=Un kioslave per a particions MacOS HFS+ +Description[cs]=Pomocný protokol pro diskové oddÃly MacOS HFS+ +Description[csb]=Plugins protokòłu dlô particëji HFS+ systemë MacOS +Description[da]=En kioslave for MacOS HFS+ partitioner +Description[de]=Ein-/Ausgabemodul für MacOS HFS+ Partitionen +Description[el]=Ένας kioslave για κατατμήσεις MacOS HFS+ +Description[eo]=K-enel-sklavo por MacOS HFS+ subdiskoj +Description[es]=Un kioslave para particiones MacOS HFS+ +Description[et]=MacOS-i HFS+-partitsioonide IO-moodul +Description[eu]=MacOS HFS+ zatiketetarako kioslavea +Description[fa]=یک kioslave برای Ø§ÙØ±Ø§Ø²Ù‡Ø§ÛŒ HFS+ سیستم عامل مکینتاش +Description[fi]=Liitäntä MacOS HFS+ osioinneille +Description[fr]=Un module d'entrées / sorties pour les partitions MacOS HFS+ +Description[fy]=In kioslave foar MacOS HFS+-partities +Description[ga]=kioslave le haghaidh deighiltà MacOS HFS+ +Description[gl]=Un kioslave para particións MacOS HFS+ +Description[he]=ממשק kioslave עבור מחיצות MacOS HFS+ +Description[hi]=मॅक-ओà¤à¤¸ à¤à¤šà¤à¤«à¤¼à¤à¤¸+ पारà¥à¤Ÿà¥€à¤¶à¤¨à¥‹à¤‚ के लिठके-आई-ओ-सà¥à¤²à¥‡à¤µ +Description[hr]=Kioslave za MacOS HFS+ particije +Description[hu]=KDE-protokoll MacOS HFS+ partÃciók kezeléséhez +Description[is]=kioslave fyrir MacOS HFS+ disksneiðar +Description[it]=Un kioslave per partizioni MacOS HFS+ +Description[ja]=MacOS HFS+ パーティションã®ãŸã‚ã® kioslave +Description[ka]=kioslave MacOS HFS+ პáƒáƒ ტიციებისთვის +Description[kk]=MacOS HFS+ файл жүйеÑінің енгізу-шығару модулі +Description[km]=kioslave សម្រាប់​ភាគ MacOS HFS+ +Description[lt]=Kiovergas MacOS HFS+ dalmenims +Description[lv]=KIO vergs MacOS HFS+ partÄ«cijÄm +Description[mk]=kio-Ñлужител за HFS+ партиции од MacOS +Description[ms]=Hamba kio untuk MacOS HFS+ petak +Description[nb]=En kioskslave for MacOS HFS+-partisjoner +Description[nds]=En In-/Utgaavdeenst för MacOS-HFS+-Partitschonen +Description[ne]=MacOS HFS+ विà¤à¤¾à¤œà¤¨à¤•ा लागि किओसà¥à¤²à¥‡à¤ +Description[nl]=Een kioslave voor MacOS HFS+-partities +Description[nn]=Ein IU-slave for MacOS HFS+-partisjonar +Description[pa]=MacOS HFS+ à¨à¨¾à¨—ਾਂ ਲਈ kioslave +Description[pl]=Wtyczka protokoÅ‚u dla partycji HFS+ systemu MacOS +Description[pt]=Um 'kioslave' para partições MacOS HFS+ +Description[pt_BR]=Um protocolo para as partições HFS+ do MacOS +Description[ro]=Un dispozitiv de I/E pentru partiÈ›ii HFS+ MacOS +Description[ru]=Модуль ввода-вывода Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑиÑтемы MacOS HFS+ +Description[rw]=Kio-umugaragu ya MacOS HFS+ibicedisiki +Description[se]=SO-Å¡láva MacOS HFS+-partiÅ¡uvnnaid várás +Description[sk]=kioslave pre MacOS HFS+ +Description[sl]=kioslave za razdelke MacOS HFS+ +Description[sr]=Kioslave за MacOS-ове HFS+ партиције +Description[sr@Latn]=Kioslave za MacOS-ove HFS+ particije +Description[sv]=En I/O-slav för MacOS HFS+ partitioner +Description[ta]=MacOS HFS+ partitionsகà¯à®•௠ஒர௠கà¯à®¯à¯‹à®¸à¯à®²à¯‡à®µà¯ +Description[th]=ตัวนำข้à¸à¸¡à¸¹à¸¥à¹€à¸‚้า-à¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸šà¸žà¸²à¸£à¹Œà¸•ิชั่นที่ใช้ระบบไฟล์ HFS+ ขà¸à¸‡ MacOS +Description[tr]=MacOS HFS+ bölümleri için kioslave +Description[tt]=MacOS HFS+ bülemnäre öçen birem sistemeneñ modulı +Description[uk]=Підлеглий B/Ð’ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð´Ñ–Ð»Ñ–Ð² MacOS HFS+ +Description[vi]=A kioslave (đà y tá»› và o ra KDE) cho MacOS HFS và các phân vùng +Description[wa]=On kioslave po MacOS HFS + pÃ¥rticions +Description[zh_CN]=MacOS HFS+ 分区的 KIO 仆人 +Description[zh_TW]=用於 MacOS HFS+ 分割å€çš„ kioslave +Icon=mac +DocPath=kioslave/mac.html diff --git a/kioslave/man/LICENSE b/kioslave/man/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/kioslave/man/LICENSE @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/man/Makefile.am b/kioslave/man/Makefile.am new file mode 100644 index 000000000..365d2f774 --- /dev/null +++ b/kioslave/man/Makefile.am @@ -0,0 +1,51 @@ +## Makefile.am of kdebase/kioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +EXTRA_PROGRAMS = kio_man_test man2html + +####### just for testing (j.habenicht@europemail.com, 15.02.2001) + +kio_man_test_SOURCES = kio_man_test.cpp +kio_man_test_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kio_man_test_LDADD = man2html.lo kio_man.lo $(LIB_KIO) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_QT) + +####### Files + +kde_module_LTLIBRARIES = kio_man.la libkmanpart.la + +kio_man_la_SOURCES = man2html.cpp kio_man.cpp +kio_man_la_LIBADD = $(LIB_KSYCOCA) +kio_man_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = kio_man.h +### TODO Why is man2htmk.h distributed? + +libkmanpart_la_SOURCES = kmanpart.cpp +libkmanpart_la_LIBADD = -lkhtml $(LIB_KPARTS) +libkmanpart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) + +kdelnk_DATA = man.protocol kmanpart.desktop +kdelnkdir = $(kde_servicesdir) + +kio_man_data_DATA = kio_man.css +kio_man_datadir = $(kde_datadir)/kio_man +EXTRA_DIST=$(kio_man_data_DATA) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/kio_man.pot + +man2html_SOURCES = dummy.cpp +man2html_LDADD = man2html_simple.o $(LIB_QT) +man2html_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +dummy.cpp: + echo > $@ + +man2html_simple.o: $(srcdir)/man2html.cpp + -rm -f man2html_simple.cpp + $(LN_S) $(srcdir)/man2html.cpp man2html_simple.cpp + $(CXX) $(DEFS) $(DEFAULT_INCLUDES) -DSIMPLE_MAN2HTML $(INCLUDES) $(CPPFLAGS) $(CXXFLAGS) -c man2html_simple.cpp + diff --git a/kioslave/man/kio_man.cpp b/kioslave/man/kio_man.cpp new file mode 100644 index 000000000..0511a165d --- /dev/null +++ b/kioslave/man/kio_man.cpp @@ -0,0 +1,1532 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <string.h> +#include <dirent.h> + +#include <qdir.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qdatastream.h> +#include <qcstring.h> +#include <qptrlist.h> +#include <qmap.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kprocess.h> +#include <klocale.h> +#include <kmimetype.h> + +#include "kio_man.h" +#include "kio_man.moc" +#include "man2html.h" +#include <assert.h> +#include <kfilterbase.h> +#include <kfilterdev.h> + +using namespace KIO; + +MANProtocol *MANProtocol::_self = 0; + +#define SGML2ROFF_DIRS "/usr/lib/sgml" + +/* + * Drop trailing ".section[.gz]" from name + */ +static +void stripExtension( QString *name ) +{ + int pos = name->length(); + + if ( name->find(".gz", -3) != -1 ) + pos -= 3; + else if ( name->find(".z", -2, false) != -1 ) + pos -= 2; + else if ( name->find(".bz2", -4) != -1 ) + pos -= 4; + else if ( name->find(".bz", -3) != -1 ) + pos -= 3; + + if ( pos > 0 ) + pos = name->findRev('.', pos-1); + + if ( pos > 0 ) + name->truncate( pos ); +} + +static +bool parseUrl(const QString& _url, QString &title, QString §ion) +{ + section = QString::null; + + QString url = _url; + if (url.at(0) == '/') { + if (KStandardDirs::exists(url)) { + title = url; + return true; + } else + { + // If the directory does not exist, then it is perhaps a normal man page + kdDebug(7107) << url << " does not exist" << endl; + } + } + + while (url.at(0) == '/') + url.remove(0,1); + + title = url; + + int pos = url.find('('); + if (pos < 0) + return true; + + title = title.left(pos); + + section = url.mid(pos+1); + section = section.left(section.length()-1); + + return true; +} + + +MANProtocol::MANProtocol(const QCString &pool_socket, const QCString &app_socket) + : QObject(), SlaveBase("man", pool_socket, app_socket) +{ + assert(!_self); + _self = this; + const QString common_dir = KGlobal::dirs()->findResourceDir( "html", "en/common/kde-common.css" ); + const QString strPath=QString( "file:%1/en/common" ).arg( common_dir ); + m_htmlPath=strPath.local8Bit(); // ### TODO encode for HTML + m_cssPath=strPath.local8Bit(); // ### TODO encode for CSS + section_names << "1" << "2" << "3" << "3n" << "3p" << "4" << "5" << "6" << "7" + << "8" << "9" << "l" << "n"; + m_manCSSFile = locate( "data", "kio_man/kio_man.css" ); +} + +MANProtocol *MANProtocol::self() { return _self; } + +MANProtocol::~MANProtocol() +{ + _self = 0; +} + +void MANProtocol::parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark ) +{ + QRegExp re( mark ); + QString l; + while ( !t.atEnd() ) + { + l = t.readLine(); + int pos = re.search( l ); + if (pos != -1) + { + QString names = l.left(pos); + QString descr = l.mid(pos + re.matchedLength()); + while ((pos = names.find(",")) != -1) + { + i[names.left(pos++)] = descr; + while (names[pos] == ' ') + pos++; + names = names.mid(pos); + } + i[names] = descr; + } + } +} + +bool MANProtocol::addWhatIs(QMap<QString, QString> &i, const QString &name, const QString &mark) +{ + QFile f(name); + if (!f.open(IO_ReadOnly)) + return false; + QTextStream t(&f); + parseWhatIs( i, t, mark ); + return true; +} + +QMap<QString, QString> MANProtocol::buildIndexMap(const QString §ion) +{ + QMap<QString, QString> i; + QStringList man_dirs = manDirectories(); + // Supplementary places for whatis databases + man_dirs += m_mandbpath; + if (man_dirs.find("/var/cache/man")==man_dirs.end()) + man_dirs << "/var/cache/man"; + if (man_dirs.find("/var/catman")==man_dirs.end()) + man_dirs << "/var/catman"; + + QStringList names; + names << "whatis.db" << "whatis"; + QString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+"; + + for ( QStringList::ConstIterator it_dir = man_dirs.begin(); + it_dir != man_dirs.end(); + ++it_dir ) + { + if ( QFile::exists( *it_dir ) ) { + QStringList::ConstIterator it_name; + for ( it_name = names.begin(); + it_name != names.end(); + it_name++ ) + { + if (addWhatIs(i, (*it_dir) + "/" + (*it_name), mark)) + break; + } + if ( it_name == names.end() ) { + KProcess proc; + proc << "whatis" << "-M" << (*it_dir) << "-w" << "*"; + myStdStream = QString::null; + connect( &proc, SIGNAL( receivedStdout(KProcess *, char *, int ) ), + SLOT( slotGetStdOutput( KProcess *, char *, int ) ) ); + proc.start( KProcess::Block, KProcess::Stdout ); + QTextStream t( &myStdStream, IO_ReadOnly ); + parseWhatIs( i, t, mark ); + } + } + } + return i; +} + +QStringList MANProtocol::manDirectories() +{ + checkManPaths(); + // + // Build a list of man directories including translations + // + QStringList man_dirs; + + for ( QStringList::ConstIterator it_dir = m_manpath.begin(); + it_dir != m_manpath.end(); + it_dir++ ) + { + // Translated pages in "<mandir>/<lang>" if the directory + // exists + QStringList languages = KGlobal::locale()->languageList(); + + for (QStringList::ConstIterator it_lang = languages.begin(); + it_lang != languages.end(); + it_lang++ ) + { + if ( !(*it_lang).isEmpty() && (*it_lang) != QString("C") ) { + QString dir = (*it_dir) + '/' + (*it_lang); + + struct stat sbuf; + + if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + const QString p = QDir(dir).canonicalPath(); + if (!man_dirs.contains(p)) man_dirs += p; + } + } + } + + // Untranslated pages in "<mandir>" + const QString p = QDir(*it_dir).canonicalPath(); + if (!man_dirs.contains(p)) man_dirs += p; + } + return man_dirs; +} + +QStringList MANProtocol::findPages(const QString &_section, + const QString &title, + bool full_path) +{ + QString section = _section; + + QStringList list; + + // kdDebug() << "findPages '" << section << "' '" << title << "'\n"; + if (title.at(0) == '/') { + list.append(title); + return list; + } + + const QString star( "*" ); + + // + // Find man sections in this directory + // + QStringList sect_list; + if ( section.isEmpty() ) + section = star; + + if ( section != star ) + { + // + // Section given as argument + // + sect_list += section; + while (section.at(section.length() - 1).isLetter()) { + section.truncate(section.length() - 1); + sect_list += section; + } + } else { + sect_list += section; + } + + QStringList man_dirs = manDirectories(); + + // + // Find man pages in the sections listed above + // + for ( QStringList::ConstIterator it_sect = sect_list.begin(); + it_sect != sect_list.end(); + it_sect++ ) + { + QString it_real = (*it_sect).lower(); + // + // Find pages + // + for ( QStringList::ConstIterator it_dir = man_dirs.begin(); + it_dir != man_dirs.end(); + it_dir++ ) + { + QString man_dir = (*it_dir); + + // + // Sections = all sub directories "man*" and "sman*" + // + DIR *dp = ::opendir( QFile::encodeName( man_dir ) ); + + if ( !dp ) + continue; + + struct dirent *ep; + + const QString man = QString("man"); + const QString sman = QString("sman"); + + while ( (ep = ::readdir( dp )) != 0L ) { + const QString file = QFile::decodeName( ep->d_name ); + QString sect = QString::null; + + if ( file.startsWith( man ) ) + sect = file.mid(3); + else if (file.startsWith(sman)) + sect = file.mid(4); + + if (sect.lower()==it_real) it_real = sect; + + // Only add sect if not already contained, avoid duplicates + if (!sect_list.contains(sect) && _section.isEmpty()) { + kdDebug() << "another section " << sect << endl; + sect_list += sect; + } + } + + ::closedir( dp ); + + if ( *it_sect != star ) { // in that case we only look around for sections + const QString dir = man_dir + QString("/man") + (it_real) + '/'; + const QString sdir = man_dir + QString("/sman") + (it_real) + '/'; + + findManPagesInSection(dir, title, full_path, list); + findManPagesInSection(sdir, title, full_path, list); + } + } + } + +// kdDebug(7107) << "finished " << list << " " << sect_list << endl; + + return list; +} + +void MANProtocol::findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list) +{ + kdDebug() << "findManPagesInSection " << dir << " " << title << endl; + bool title_given = !title.isEmpty(); + + DIR *dp = ::opendir( QFile::encodeName( dir ) ); + + if ( !dp ) + return; + + struct dirent *ep; + + while ( (ep = ::readdir( dp )) != 0L ) { + if ( ep->d_name[0] != '.' ) { + + QString name = QFile::decodeName( ep->d_name ); + + // check title if we're looking for a specific page + if ( title_given ) { + if ( !name.startsWith( title ) ) { + continue; + } + else { + // beginning matches, do a more thorough check... + QString tmp_name = name; + stripExtension( &tmp_name ); + if ( tmp_name != title ) + continue; + } + } + + if ( full_path ) + name.prepend( dir ); + + list += name ; + } + } + ::closedir( dp ); +} + +void MANProtocol::output(const char *insert) +{ + if (insert) + { + m_outputBuffer.writeBlock(insert,strlen(insert)); + } + if (!insert || m_outputBuffer.at() >= 2048) + { + m_outputBuffer.close(); + data(m_outputBuffer.buffer()); + m_outputBuffer.setBuffer(QByteArray()); + m_outputBuffer.open(IO_WriteOnly); + } +} + +// called by man2html +char *read_man_page(const char *filename) +{ + return MANProtocol::self()->readManPage(filename); +} + +// called by man2html +void output_real(const char *insert) +{ + MANProtocol::self()->output(insert); +} + +static QString text2html(const QString& txt) +{ + QString reply = txt; + + reply = reply.replace('&', "&"); + reply = reply.replace('<', "<"); + reply = reply.replace('>', ">"); + reply = reply.replace('"', "&dquot;"); + reply = reply.replace('\'', """); + return reply; +} + +void MANProtocol::get(const KURL& url ) +{ + kdDebug(7107) << "GET " << url.url() << endl; + + QString title, section; + + if (!parseUrl(url.path(), title, section)) + { + showMainIndex(); + return; + } + + // see if an index was requested + if (url.query().isEmpty() && (title.isEmpty() || title == "/" || title == ".")) + { + if (section == "index" || section.isEmpty()) + showMainIndex(); + else + showIndex(section); + return; + } + + // tell the mimetype + mimeType("text/html"); + + const QStringList foundPages=findPages(section, title); + bool pageFound=true; + if (foundPages.isEmpty()) + { + outputError(i18n("No man page matching to %1 found.<br><br>" + "Check that you have not mistyped the name of the page that you want.\n" + "Be careful that you must take care about upper case and lower case characters!<br>" + "If everything looks correct, then perhaps you need to set a better search path " + "for man pages, be it by the environment variable MANPATH or a matching file " + "in the directory /etc .").arg(text2html(title))); + pageFound=false; + } + else if (foundPages.count()>1) + { + pageFound=false; + //check for the case that there is foo.1 and foo.1.gz found: + // ### TODO make it more generic (other extensions) + if ((foundPages.count()==2) && + (((foundPages[0]+".gz") == foundPages[1]) || + (foundPages[0] == (foundPages[1]+".gz")))) + pageFound=true; + else + outputMatchingPages(foundPages); + } + //yes, we found exactly one man page + + if (pageFound) + { + setResourcePath(m_htmlPath,m_cssPath); + m_outputBuffer.open(IO_WriteOnly); + const QCString filename=QFile::encodeName(foundPages[0]); + char *buf = readManPage(filename); + + if (!buf) + { + outputError(i18n("Open of %1 failed.").arg(title)); + finished(); + return; + } + // will call output_real + scan_man_page(buf); + delete [] buf; + + output(0); // flush + + m_outputBuffer.close(); + data(m_outputBuffer.buffer()); + m_outputBuffer.setBuffer(QByteArray()); + // tell we are done + data(QByteArray()); + } + finished(); +} + +void MANProtocol::slotGetStdOutput(KProcess* /* p */, char *s, int len) +{ + myStdStream += QString::fromLocal8Bit(s, len); +} + +char *MANProtocol::readManPage(const char *_filename) +{ + QCString filename = _filename; + + char *buf = NULL; + + /* Determine type of man page file by checking its path. Determination by + * MIME type with KMimeType doesn't work reliablely. E.g., Solaris 7: + * /usr/man/sman7fs/pcfs.7fs -> text/x-csrc : WRONG + * If the path name constains the string sman, assume that it's SGML and + * convert it to roff format (used on Solaris). */ + //QString file_mimetype = KMimeType::findByPath(QString(filename), 0, false)->name(); + if (filename.contains("sman", false)) //file_mimetype == "text/html" || ) + { + myStdStream =QString::null; + KProcess proc; + + /* Determine path to sgml2roff, if not already done. */ + getProgramPath(); + proc << mySgml2RoffPath << filename; + + QApplication::connect(&proc, SIGNAL(receivedStdout (KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + proc.start(KProcess::Block, KProcess::All); + + const QCString cstr=myStdStream.latin1(); + const int len = cstr.size()-1; + buf = new char[len + 4]; + qmemmove(buf + 1, cstr.data(), len); + buf[0]=buf[len]='\n'; // Start and end with a end of line + buf[len+1]=buf[len+2]='\0'; // Two additional NUL characters at end + } + else + { + if (QDir::isRelativePath(filename)) { + kdDebug(7107) << "relative " << filename << endl; + filename = QDir::cleanDirPath(lastdir + "/" + filename).utf8(); + if (!KStandardDirs::exists(filename)) { // exists perhaps with suffix + lastdir = filename.left(filename.findRev('/')); + QDir mandir(lastdir); + mandir.setNameFilter(filename.mid(filename.findRev('/') + 1) + ".*"); + filename = lastdir + "/" + QFile::encodeName(mandir.entryList().first()); + } + kdDebug(7107) << "resolved to " << filename << endl; + } + lastdir = filename.left(filename.findRev('/')); + + QIODevice *fd= KFilterDev::deviceForFile(filename); + + if ( !fd || !fd->open(IO_ReadOnly)) + { + delete fd; + return 0; + } + QByteArray array(fd->readAll()); + kdDebug(7107) << "read " << array.size() << endl; + fd->close(); + delete fd; + + if (array.isEmpty()) + return 0; + + const int len = array.size(); + buf = new char[len + 4]; + qmemmove(buf + 1, array.data(), len); + buf[0]=buf[len]='\n'; // Start and end with a end of line + buf[len+1]=buf[len+2]='\0'; // Two NUL characters at end + } + return buf; +} + + +void MANProtocol::outputError(const QString& errmsg) +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl; + os << "<title>" << i18n("Man output") << "</title>\n" << endl; + if ( !m_manCSSFile.isEmpty() ) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" << endl; + os << i18n("<body><h1>KDE Man Viewer Error</h1>") << errmsg << "</body>" << endl; + os << "</html>" << endl; + + data(array); +} + +void MANProtocol::outputMatchingPages(const QStringList &matchingPages) +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html>\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"<<endl; + os << "<title>" << i18n("Man output") <<"</title>" << endl; + if ( !m_manCSSFile.isEmpty() ) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" <<endl; + os << "<body><h1>" << i18n("There is more than one matching man page."); + os << "</h1>\n<ul>\n"; + + int acckey=1; + for (QStringList::ConstIterator it = matchingPages.begin(); it != matchingPages.end(); ++it) + { + os<<"<li><a href='man:"<<(*it)<<"' accesskey='"<< acckey <<"'>"<< *it <<"</a><br>\n<br>\n"; + acckey++; + } + os << "</ul>\n"; + os << "<hr>\n"; + os << "<p>" << i18n("Note: if you read a man page in your language," + " be aware it can contain some mistakes or be obsolete." + " In case of doubt, you should have a look at the English version.") << "</p>"; + + os << "</body>\n</html>"<<endl; + + data(array); + finished(); +} + +void MANProtocol::stat( const KURL& url) +{ + kdDebug(7107) << "ENTERING STAT " << url.url() << endl; + + QString title, section; + + if (!parseUrl(url.path(), title, section)) + { + error(KIO::ERR_MALFORMED_URL, url.url()); + return; + } + + kdDebug(7107) << "URL " << url.url() << " parsed to title='" << title << "' section=" << section << endl; + + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = UDS_NAME; + atom.m_long = 0; + atom.m_str = title; + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = UDS_URL; + atom.m_long = 0; + QString newUrl = "man:"+title; + if (!section.isEmpty()) + newUrl += QString("(%1)").arg(section); + atom.m_str = newUrl; + entry.append(atom); + + atom.m_uds = UDS_MIME_TYPE; + atom.m_long = 0; + atom.m_str = "text/html"; + entry.append(atom); + + statEntry(entry); + + finished(); +} + + +extern "C" +{ + + int KDE_EXPORT kdemain( int argc, char **argv ) { + + KInstance instance("kio_man"); + + kdDebug(7107) << "STARTING " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_man protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + MANProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7107) << "Done" << endl; + + return 0; + } + +} + +void MANProtocol::mimetype(const KURL & /*url*/) +{ + mimeType("text/html"); + finished(); +} + +static QString sectionName(const QString& section) +{ + if (section == "1") + return i18n("User Commands"); + else if (section == "2") + return i18n("System Calls"); + else if (section == "3") + return i18n("Subroutines"); + else if (section == "3p") + return i18n("Perl Modules"); + else if (section == "3n") + return i18n("Network Functions"); + else if (section == "4") + return i18n("Devices"); + else if (section == "5") + return i18n("File Formats"); + else if (section == "6") + return i18n("Games"); + else if (section == "7") + return i18n("Miscellaneous"); + else if (section == "8") + return i18n("System Administration"); + else if (section == "9") + return i18n("Kernel"); + else if (section == "l") + return i18n("Local Documentation"); + else if (section == "n") + return i18n("New"); + + return QString::null; +} + +QStringList MANProtocol::buildSectionList(const QStringList& dirs) const +{ + QStringList l; + + for (QStringList::ConstIterator it = section_names.begin(); + it != section_names.end(); ++it) + { + for (QStringList::ConstIterator dir = dirs.begin(); + dir != dirs.end(); ++dir) + { + QDir d((*dir)+"/man"+(*it)); + if (d.exists()) + { + l << *it; + break; + } + } + } + return l; +} + +void MANProtocol::showMainIndex() +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + // print header + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl; + os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl; + if (!m_manCSSFile.isEmpty()) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" << endl; + os << "<body><h1>" << i18n("UNIX Manual Index") << "</h1>" << endl; + + // ### TODO: why still the environment variable + const QString sectList = getenv("MANSECT"); + QStringList sections; + if (sectList.isEmpty()) + sections = buildSectionList(manDirectories()); + else + sections = QStringList::split(':', sectList); + + os << "<table>" << endl; + + QStringList::ConstIterator it; + for (it = sections.begin(); it != sections.end(); ++it) + os << "<tr><td><a href=\"man:(" << *it << ")\" accesskey=\"" << + (((*it).length()==1)?(*it):(*it).right(1))<<"\">" << i18n("Section ") + << *it << "</a></td><td> </td><td> " << sectionName(*it) << "</td></tr>" << endl; + + os << "</table>" << endl; + + // print footer + os << "</body></html>" << endl; + + data(array); + finished(); +} + +void MANProtocol::constructPath(QStringList& constr_path, QStringList constr_catmanpath) +{ + QMap<QString, QString> manpath_map; + QMap<QString, QString> mandb_map; + + // Add paths from /etc/man.conf + // + // Explicit manpaths may be given by lines starting with "MANPATH" or + // "MANDATORY_MANPATH" (depending on system ?). + // Mappings from $PATH to manpath are given by lines starting with + // "MANPATH_MAP" + + QRegExp manpath_regex( "^MANPATH\\s" ); + QRegExp mandatory_regex( "^MANDATORY_MANPATH\\s" ); + QRegExp manpath_map_regex( "^MANPATH_MAP\\s" ); + QRegExp mandb_map_regex( "^MANDB_MAP\\s" ); + //QRegExp section_regex( "^SECTION\\s" ); + QRegExp space_regex( "\\s+" ); // for parsing manpath map + + QFile mc("/etc/man.conf"); // Caldera + if (!mc.exists()) + mc.setName("/etc/manpath.config"); // SuSE, Debian + if (!mc.exists()) + mc.setName("/etc/man.config"); // Mandrake + + if (mc.open(IO_ReadOnly)) + { + QTextStream is(&mc); + is.setEncoding(QTextStream::Locale); + + while (!is.eof()) + { + const QString line = is.readLine(); + if ( manpath_regex.search(line, 0) == 0 ) + { + const QString path = line.mid(8).stripWhiteSpace(); + constr_path += path; + } + else if ( mandatory_regex.search(line, 0) == 0 ) + { + const QString path = line.mid(18).stripWhiteSpace(); + constr_path += path; + } + else if ( manpath_map_regex.search(line, 0) == 0 ) + { + // The entry is "MANPATH_MAP <path> <manpath>" + const QStringList mapping = + QStringList::split(space_regex, line); + + if ( mapping.count() == 3 ) + { + const QString dir = QDir::cleanDirPath( mapping[1] ); + const QString mandir = QDir::cleanDirPath( mapping[2] ); + + manpath_map[ dir ] = mandir; + } + } + else if ( mandb_map_regex.search(line, 0) == 0 ) + { + // The entry is "MANDB_MAP <manpath> <catmanpath>" + const QStringList mapping = + QStringList::split(space_regex, line); + + if ( mapping.count() == 3 ) + { + const QString mandir = QDir::cleanDirPath( mapping[1] ); + const QString catmandir = QDir::cleanDirPath( mapping[2] ); + + mandb_map[ mandir ] = catmandir; + } + } + /* sections are not used + else if ( section_regex.find(line, 0) == 0 ) + { + if ( !conf_section.isEmpty() ) + conf_section += ':'; + conf_section += line.mid(8).stripWhiteSpace(); + } + */ + } + mc.close(); + } + + // Default paths + static const char *manpaths[] = { + "/usr/X11/man", + "/usr/X11R6/man", + "/usr/man", + "/usr/local/man", + "/usr/exp/man", + "/usr/openwin/man", + "/usr/dt/man", + "/opt/freetool/man", + "/opt/local/man", + "/usr/tex/man", + "/usr/www/man", + "/usr/lang/man", + "/usr/gnu/man", + "/usr/share/man", + "/usr/motif/man", + "/usr/titools/man", + "/usr/sunpc/man", + "/usr/ncd/man", + "/usr/newsprint/man", + NULL }; + + + int i = 0; + while (manpaths[i]) { + if ( constr_path.findIndex( QString( manpaths[i] ) ) == -1 ) + constr_path += QString( manpaths[i] ); + i++; + } + + // Directories in $PATH + // - if a manpath mapping exists, use that mapping + // - if a directory "<path>/man" or "<path>/../man" exists, add it + // to the man path (the actual existence check is done further down) + + if ( ::getenv("PATH") ) { + const QStringList path = + QStringList::split( ":", + QString::fromLocal8Bit( ::getenv("PATH") ) ); + + for ( QStringList::const_iterator it = path.begin(); + it != path.end(); + ++it ) + { + const QString dir = QDir::cleanDirPath( *it ); + QString mandir = manpath_map[ dir ]; + + if ( !mandir.isEmpty() ) { + // a path mapping exists + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + } + else { + // no manpath mapping, use "<path>/man" and "<path>/../man" + + mandir = dir + QString( "/man" ); + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + + int pos = dir.findRev( '/' ); + if ( pos > 0 ) { + mandir = dir.left( pos ) + QString("/man"); + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + } + } + QString catmandir = mandb_map[ mandir ]; + if ( !mandir.isEmpty() ) + { + if ( constr_catmanpath.findIndex( catmandir ) == -1 ) + constr_catmanpath += catmandir; + } + else + { + // What is the default mapping? + catmandir = mandir; + catmandir.replace("/usr/share/","/var/cache/"); + if ( constr_catmanpath.findIndex( catmandir ) == -1 ) + constr_catmanpath += catmandir; + } + } + } +} + +void MANProtocol::checkManPaths() +{ + static bool inited = false; + + if (inited) + return; + + inited = true; + + const QString manpath_env = QString::fromLocal8Bit( ::getenv("MANPATH") ); + //QString mansect_env = QString::fromLocal8Bit( ::getenv("MANSECT") ); + + // Decide if $MANPATH is enough on its own or if it should be merged + // with the constructed path. + // A $MANPATH starting or ending with ":", or containing "::", + // should be merged with the constructed path. + + bool construct_path = false; + + if ( manpath_env.isEmpty() + || manpath_env[0] == ':' + || manpath_env[manpath_env.length()-1] == ':' + || manpath_env.contains( "::" ) ) + { + construct_path = true; // need to read config file + } + + // Constucted man path -- consists of paths from + // /etc/man.conf + // default dirs + // $PATH + QStringList constr_path; + QStringList constr_catmanpath; // catmanpath + + QString conf_section; + + if ( construct_path ) + { + constructPath(constr_path, constr_catmanpath); + } + + m_mandbpath=constr_catmanpath; + + // Merge $MANPATH with the constructed path to form the + // actual manpath. + // + // The merging syntax with ":" and "::" in $MANPATH will be + // satisfied if any empty string in path_list_env (there + // should be 1 or 0) is replaced by the constructed path. + + const QStringList path_list_env = QStringList::split( ':', manpath_env , true ); + + for ( QStringList::const_iterator it = path_list_env.begin(); + it != path_list_env.end(); + ++it ) + { + struct stat sbuf; + + QString dir = (*it); + + if ( !dir.isEmpty() ) { + // Add dir to the man path if it exists + if ( m_manpath.findIndex( dir ) == -1 ) { + if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + m_manpath += dir; + } + } + } + else { + // Insert constructed path ($MANPATH was empty, or + // there was a ":" at an end or "::") + + for ( QStringList::Iterator it2 = constr_path.begin(); + it2 != constr_path.end(); + it2++ ) + { + dir = (*it2); + + if ( !dir.isEmpty() ) { + if ( m_manpath.findIndex( dir ) == -1 ) { + if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + m_manpath += dir; + } + } + } + } + } + } + +/* sections are not used + // Sections + QStringList m_mansect = QStringList::split( ':', mansect_env, true ); + + const char* default_sect[] = + { "1", "2", "3", "4", "5", "6", "7", "8", "9", "n", 0L }; + + for ( int i = 0; default_sect[i] != 0L; i++ ) + if ( m_mansect.findIndex( QString( default_sect[i] ) ) == -1 ) + m_mansect += QString( default_sect[i] ); +*/ + +} + + +//#define _USE_OLD_CODE + +#ifdef _USE_OLD_CODE +#warning "using old code" +#else + +// Define this, if you want to compile with qsort from stdlib.h +// else the Qt Heapsort will be used. +// Note, qsort seems to be a bit faster (~10%) on a large man section +// eg. man section 3 +#define _USE_QSORT + +// Setup my own structure, with char pointers. +// from now on only pointers are copied, no strings +// +// containing the whole path string, +// the beginning of the man page name +// and the length of the name +struct man_index_t { + char *manpath; // the full path including man file + const char *manpage_begin; // pointer to the begin of the man file name in the path + int manpage_len; // len of the man file name +}; +typedef man_index_t *man_index_ptr; + +#ifdef _USE_QSORT +int compare_man_index(const void *s1, const void *s2) +{ + struct man_index_t *m1 = *(struct man_index_t **)s1; + struct man_index_t *m2 = *(struct man_index_t **)s2; + int i; + // Compare the names of the pages + // with the shorter length. + // Man page names are not '\0' terminated, so + // this is a bit tricky + if ( m1->manpage_len > m2->manpage_len) + { + i = qstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m2->manpage_len); + if (!i) + return 1; + return i; + } + + if ( m1->manpage_len < m2->manpage_len) + { + i = qstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + if (!i) + return -1; + return i; + } + + return qstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); +} + +#else /* !_USE_QSORT */ +#warning using heapsort +// Set up my own man page list, +// with a special compare function to sort itself +typedef QPtrList<struct man_index_t> QManIndexListBase; +typedef QPtrListIterator<struct man_index_t> QManIndexListIterator; + +class QManIndexList : public QManIndexListBase +{ +public: +private: + int compareItems( QPtrCollection::Item s1, QPtrCollection::Item s2 ) + { + struct man_index_t *m1 = (struct man_index_t *)s1; + struct man_index_t *m2 = (struct man_index_t *)s2; + int i; + // compare the names of the pages + // with the shorter length + if (m1->manpage_len > m2->manpage_len) + { + i = qstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m2->manpage_len); + if (!i) + return 1; + return i; + } + + if (m1->manpage_len > m2->manpage_len) + { + + i = qstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + if (!i) + return -1; + return i; + } + + return qstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + } +}; + +#endif /* !_USE_QSORT */ +#endif /* !_USE_OLD_CODE */ + + + + +void MANProtocol::showIndex(const QString& section) +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + // print header + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl; + os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl; + if ( !m_manCSSFile.isEmpty() ) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" << endl; + os << "<body><div class=\"secidxmain\">" << endl; + os << "<h1>" << i18n( "Index for Section %1: %2").arg(section).arg(sectionName(section)) << "</h1>" << endl; + + // compose list of search paths ------------------------------------------------------------- + + checkManPaths(); + infoMessage(i18n("Generating Index")); + + // search for the man pages + QStringList pages = findPages( section, QString::null ); + + QMap<QString, QString> indexmap = buildIndexMap(section); + + // print out the list + os << "<table>" << endl; + +#ifdef _USE_OLD_CODE + pages.sort(); + + QMap<QString, QString> pagemap; + + QStringList::ConstIterator page; + for (page = pages.begin(); page != pages.end(); ++page) + { + QString fileName = *page; + + stripExtension( &fileName ); + + pos = fileName.findRev('/'); + if (pos > 0) + fileName = fileName.mid(pos+1); + + if (!fileName.isEmpty()) + pagemap[fileName] = *page; + + } + + for (QMap<QString,QString>::ConstIterator it = pagemap.begin(); + it != pagemap.end(); ++it) + { + os << "<tr><td><a href=\"man:" << it.data() << "\">\n" + << it.key() << "</a></td><td> </td><td> " + << (indexmap.contains(it.key()) ? indexmap[it.key()] : "" ) + << "</td></tr>" << endl; + } + +#else /* ! _USE_OLD_CODE */ + +#ifdef _USE_QSORT + + int listlen = pages.count(); + man_index_ptr *indexlist = new man_index_ptr[listlen]; + listlen = 0; + +#else /* !_USE_QSORT */ + + QManIndexList manpages; + manpages.setAutoDelete(TRUE); + +#endif /* _USE_QSORT */ + + QStringList::const_iterator page; + for (page = pages.begin(); page != pages.end(); ++page) + { + // I look for the beginning of the man page name + // i.e. "bla/pagename.3.gz" by looking for the last "/" + // Then look for the end of the name by searching backwards + // for the last ".", not counting zip extensions. + // If the len of the name is >0, + // store it in the list structure, to be sorted later + + char *manpage_end; + struct man_index_t *manindex = new man_index_t; + manindex->manpath = strdup((*page).utf8()); + + manindex->manpage_begin = strrchr(manindex->manpath, '/'); + if (manindex->manpage_begin) + { + manindex->manpage_begin++; + assert(manindex->manpage_begin >= manindex->manpath); + } + else + { + manindex->manpage_begin = manindex->manpath; + assert(manindex->manpage_begin >= manindex->manpath); + } + + // Skip extension ".section[.gz]" + + char *begin = (char*)(manindex->manpage_begin); + int len = strlen( begin ); + char *end = begin+(len-1); + + if ( len >= 3 && strcmp( end-2, ".gz" ) == 0 ) + end -= 3; + else if ( len >= 2 && strcmp( end-1, ".Z" ) == 0 ) + end -= 2; + else if ( len >= 2 && strcmp( end-1, ".z" ) == 0 ) + end -= 2; + else if ( len >= 4 && strcmp( end-3, ".bz2" ) == 0 ) + end -= 4; + + while ( end >= begin && *end != '.' ) + end--; + + if ( end < begin ) + manpage_end = 0; + else + manpage_end = end; + + if (NULL == manpage_end) + { + // no '.' ending ??? + // set the pointer past the end of the filename + manindex->manpage_len = (*page).length(); + manindex->manpage_len -= (manindex->manpage_begin - manindex->manpath); + assert(manindex->manpage_len >= 0); + } + else + { + manindex->manpage_len = (manpage_end - manindex->manpage_begin); + assert(manindex->manpage_len >= 0); + } + + if (0 < manindex->manpage_len) + { + +#ifdef _USE_QSORT + + indexlist[listlen] = manindex; + listlen++; + +#else /* !_USE_QSORT */ + + manpages.append(manindex); + +#endif /* _USE_QSORT */ + + } + } + + // + // Now do the sorting on the page names + // and the printout afterwards + // While printing avoid duplicate man page names + // + + struct man_index_t dummy_index = {0l,0l,0}; + struct man_index_t *last_index = &dummy_index; + +#ifdef _USE_QSORT + + // sort and print + qsort(indexlist, listlen, sizeof(struct man_index_t *), compare_man_index); + + QChar firstchar, tmp; + QString indexLine="<div class=\"secidxshort\">\n"; + if (indexlist[0]->manpage_len>0) + { + firstchar=QChar((indexlist[0]->manpage_begin)[0]).lower(); + + const QString appendixstr = QString( + " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n" + ).arg(firstchar).arg(firstchar).arg(firstchar); + indexLine.append(appendixstr); + } + os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\"" + << firstchar << "\">" << firstchar <<"</a>\n</td></tr>" << endl; + + for (int i=0; i<listlen; i++) + { + struct man_index_t *manindex = indexlist[i]; + + // qstrncmp(): + // "last_man" has already a \0 string ending, but + // "manindex->manpage_begin" has not, + // so do compare at most "manindex->manpage_len" of the strings. + if (last_index->manpage_len == manindex->manpage_len && + !qstrncmp(last_index->manpage_begin, + manindex->manpage_begin, + manindex->manpage_len) + ) + { + continue; + } + + tmp=QChar((manindex->manpage_begin)[0]).lower(); + if (firstchar != tmp) + { + firstchar = tmp; + os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\"" + << firstchar << "\">" << firstchar << "</a>\n</td></tr>" << endl; + + const QString appendixstr = QString( + " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n" + ).arg(firstchar).arg(firstchar).arg(firstchar); + indexLine.append(appendixstr); + } + os << "<tr><td><a href=\"man:" + << manindex->manpath << "\">\n"; + + ((char *)manindex->manpage_begin)[manindex->manpage_len] = '\0'; + os << manindex->manpage_begin + << "</a></td><td> </td><td> " + << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" ) + << "</td></tr>" << endl; + last_index = manindex; + } + indexLine.append("</div>"); + + for (int i=0; i<listlen; i++) { + ::free(indexlist[i]->manpath); // allocated by strdup + delete indexlist[i]; + } + + delete [] indexlist; + +#else /* !_USE_QSORT */ + + manpages.sort(); // using + + for (QManIndexListIterator mit(manpages); + mit.current(); + ++mit ) + { + struct man_index_t *manindex = mit.current(); + + // qstrncmp(): + // "last_man" has already a \0 string ending, but + // "manindex->manpage_begin" has not, + // so do compare at most "manindex->manpage_len" of the strings. + if (last_index->manpage_len == manindex->manpage_len && + !qstrncmp(last_index->manpage_begin, + manindex->manpage_begin, + manindex->manpage_len) + ) + { + continue; + } + + os << "<tr><td><a href=\"man:" + << manindex->manpath << "\">\n"; + + manindex->manpage_begin[manindex->manpage_len] = '\0'; + os << manindex->manpage_begin + << "</a></td><td> </td><td> " + << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" ) + << "</td></tr>" << endl; + last_index = manindex; + } +#endif /* _USE_QSORT */ +#endif /* _USE_OLD_CODE */ + + os << "</table></div>" << endl; + + os << indexLine << endl; + + // print footer + os << "</body></html>" << endl; + + infoMessage(QString::null); + mimeType("text/html"); + data(array); + finished(); +} + +void MANProtocol::listDir(const KURL &url) +{ + kdDebug( 7107 ) << "ENTER listDir: " << url.prettyURL() << endl; + + QString title; + QString section; + + if ( !parseUrl(url.path(), title, section) ) { + error( KIO::ERR_MALFORMED_URL, url.url() ); + return; + } + + QStringList list = findPages( section, QString::null, false ); + + UDSEntryList uds_entry_list; + UDSEntry uds_entry; + UDSAtom uds_atom; + + uds_atom.m_uds = KIO::UDS_NAME; // we only do names... + uds_entry.append( uds_atom ); + + QStringList::Iterator it = list.begin(); + QStringList::Iterator end = list.end(); + + for ( ; it != end; ++it ) { + stripExtension( &(*it) ); + + uds_entry[0].m_str = *it; + uds_entry_list.append( uds_entry ); + } + + listEntries( uds_entry_list ); + finished(); +} + +void MANProtocol::getProgramPath() +{ + if (!mySgml2RoffPath.isEmpty()) + return; + + mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff"); + if (!mySgml2RoffPath.isEmpty()) + return; + + /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */ + mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff", QString(SGML2ROFF_DIRS)); + if (!mySgml2RoffPath.isEmpty()) + return; + + /* Cannot find sgml2roff programm: */ + outputError(i18n("Could not find the sgml2roff program on your system. Please install it, if necessary, and extend the search path by adjusting the environment variable PATH before starting KDE.")); + finished(); + exit(); +} diff --git a/kioslave/man/kio_man.css b/kioslave/man/kio_man.css new file mode 100644 index 000000000..8a9f378bc --- /dev/null +++ b/kioslave/man/kio_man.css @@ -0,0 +1,21 @@ +body {background-color:#fffff} + +/*for the list of one manpage section*/ +.secidxshort { + display:block; + position:absolute;overflow:auto; + top:0px;bottom:95%;left:0;right:0; +} +.secidxmain { +/*misfortunately accessing anchors in a scrollview + doesn't seem to work yet with konqi, so:*/ +/* + position:absolute;overflow:auto; + top:5%;bottom:0;left:0;right:0; +*/ +} +.secidxnextletter { + font-size:larger; + border-bottom:1px solid black; + text-align:center +} diff --git a/kioslave/man/kio_man.h b/kioslave/man/kio_man.h new file mode 100644 index 000000000..f571082db --- /dev/null +++ b/kioslave/man/kio_man.h @@ -0,0 +1,99 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de> + + + 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 __kio_man_h__ +#define __kio_man_h__ + + +#include <qstring.h> +#include <qcstring.h> +#include <qstringlist.h> +#include <qdict.h> +#include <qbuffer.h> + + +#include <kio/global.h> +#include <kio/slavebase.h> + + +class MANProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT + +public: + + MANProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~MANProtocol(); + + virtual void get(const KURL& url); + virtual void stat(const KURL& url); + + virtual void mimetype(const KURL &url); + virtual void listDir(const KURL &url); + + void outputError(const QString& errmsg); + void outputMatchingPages(const QStringList &matchingPages); + + void showMainIndex(); + void showIndex(const QString& section); + + // the following two functions are the interface to man2html + void output(const char *insert); + char *readManPage(const char *filename); + + static MANProtocol *self(); + +private slots: + void slotGetStdOutput(KProcess*, char*, int); + +private: + void checkManPaths(); + QStringList manDirectories(); + QMap<QString, QString> buildIndexMap(const QString& section); + bool addWhatIs(QMap<QString, QString>& i, const QString& f, const QString& mark); + void parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark ); + QStringList findPages(const QString& section, + const QString &title, + bool full_path = true); + + void addToBuffer(const char *buffer, int buflen); + QString pageName(const QString& page) const; + QStringList buildSectionList(const QStringList& dirs) const; + void constructPath(QStringList& constr_path, QStringList constr_catmanpath); +private: + static MANProtocol *_self; + QCString lastdir; + + void findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list); + QStringList m_manpath; ///< Path of man directories + QStringList m_mandbpath; ///< Path of catman directories + QStringList section_names; + + QString myStdStream; + QString mySgml2RoffPath; + void getProgramPath(); + + QCString m_htmlPath; ///< Path to KDE resources, encoded for HTML + QCString m_cssPath; ///< Path to KDE resources, encoded for CSS + QBuffer m_outputBuffer; ///< Buffer for the output + QString m_manCSSFile; ///< Path to kio_man.css +}; + + +#endif diff --git a/kioslave/man/kio_man_test.cpp b/kioslave/man/kio_man_test.cpp new file mode 100644 index 000000000..bfb78a652 --- /dev/null +++ b/kioslave/man/kio_man_test.cpp @@ -0,0 +1,38 @@ + + +#include <qobject.h> + +#include "kio_man.h" + + +#include <kapplication.h> +#include <klocale.h> + + +class kio_man_test : public MANProtocol +{ + Q_OBJECT + +public: + kio_man_test(const QCString &pool_socket, const QCString &app_socket); + +protected: + virtual void data(int); + +}; + + + + + +int main(int argc, char **argv) +{ + KApplication a( argc, argv , "p2"); + + MANProtocol testproto("/tmp/kiotest.in", "/tmp/kiotest.out"); + testproto.showIndex("3"); + + return 0; +} + + diff --git a/kioslave/man/kmanpart.cpp b/kioslave/man/kmanpart.cpp new file mode 100644 index 000000000..307a8c6b2 --- /dev/null +++ b/kioslave/man/kmanpart.cpp @@ -0,0 +1,115 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Alexander Neundorf <neundorf@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. +*/ + +#include "kmanpart.h" +#include <qstring.h> + +#include <kinstance.h> +#include <kglobal.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kaboutdata.h> +#include <kdeversion.h> + +extern "C" +{ + KDE_EXPORT void* init_libkmanpart() + { + return new KManPartFactory; + } +} + +KInstance* KManPartFactory::s_instance = 0L; +KAboutData* KManPartFactory::s_about = 0L; + +KManPartFactory::KManPartFactory( QObject* parent, const char* name ) + : KParts::Factory( parent, name ) +{} + +KManPartFactory::~KManPartFactory() +{ + delete s_instance; + s_instance = 0L; + delete s_about; +} + +KParts::Part* KManPartFactory::createPartObject( QWidget * parentWidget, const char* /*widgetName*/, QObject *, + const char* name, const char* /*className*/,const QStringList & ) +{ + KManPart* part = new KManPart(parentWidget, name ); + return part; +} + +KInstance* KManPartFactory::instance() +{ + if( !s_instance ) + { + s_about = new KAboutData( "kmanpart", + I18N_NOOP( "KMan" ), KDE_VERSION_STRING ); + s_instance = new KInstance( s_about ); + } + return s_instance; +} + + +KManPart::KManPart( QWidget * parent, const char * name ) +: KHTMLPart( parent, name ) +,m_job(0) +{ + KInstance * instance = new KInstance( "kmanpart" ); + setInstance( instance ); + m_extension=new KParts::BrowserExtension(this); +} + +bool KManPart::openURL( const KURL &url ) +{ + return KParts::ReadOnlyPart::openURL(url); +} + +bool KManPart::openFile() +{ + if (m_job!=0) + m_job->kill(); + + begin(); + + KURL url; + url.setProtocol( "man" ); + url.setPath( m_file ); + + m_job = KIO::get( url, true, false ); + connect( m_job, SIGNAL( data( KIO::Job *, const QByteArray &) ), SLOT( readData( KIO::Job *, const QByteArray &) ) ); + connect( m_job, SIGNAL( result( KIO::Job * ) ), SLOT( jobDone( KIO::Job * ) ) ); + return true; +} + +void KManPart::readData(KIO::Job * , const QByteArray & data) +{ + write(data,data.size()); +} + +void KManPart::jobDone( KIO::Job *) +{ + m_job=0; + end(); +} + +#include "kmanpart.moc" + diff --git a/kioslave/man/kmanpart.desktop b/kioslave/man/kmanpart.desktop new file mode 100644 index 000000000..1d9bb06bf --- /dev/null +++ b/kioslave/man/kmanpart.desktop @@ -0,0 +1,91 @@ +[Desktop Entry] +Type=Service +Comment=Embeddable Troff Viewer +Comment[af]=Inlegbare Troff Kyker +Comment[ar]=مستعرض Troff المدمج +Comment[be]=Убудаваны праглÑдальнік Troff +Comment[bg]=Визуализатор за вграждане на Troff +Comment[bn]=সনà§à¦¨à¦¿à¦¬à§‡à¦¶à¦¯à§‹à¦—à§à¦¯ টà§à¦°à¦«à§ পà§à¦°à¦¦à¦°à§à¦¶à¦• +Comment[bs]=Ugradivi preglednik Troff datoteka +Comment[ca]=Visor Troff encastable +Comment[cs]=Komponenta pro zobrazovánà manuálových stránek +Comment[csb]=Przezérnik lopków troff +Comment[cy]=Gwelydd Troff Mewnadeiladwy +Comment[da]=Indlejrbar Troff-fremviser +Comment[de]=Eingebetteter Troff-Betrachter +Comment[el]=Ενσωματώσιμος Ï€ÏοβολÎας Troff +Comment[eo]=Enkonstruebla bildrigardilo +Comment[es]=Visor empotrable de Troff +Comment[et]=Põimitav Troff komponent +Comment[eu]=Troff ikustailu txertagarria +Comment[fa]=مشاهده‌گر Troff Ù†Ù‡ÙØªÙ†ÛŒ +Comment[fi]=Upotettava Troff-näytin +Comment[fr]=Afficheur troff incorporé +Comment[fy]=Ynsletten Troff-werjefteprogramma +Comment[ga]=Amharcán Inleabaithe troff +Comment[gl]=Visor de Troff Incrustábel +Comment[he]=מציג Troff בר־הטבעה +Comment[hi]=à¤à¤®à¥à¤¬à¥‡à¤¡à¥‡à¤¬à¤² टà¥à¤°à¤¾à¤« पà¥à¤°à¤¦à¤°à¥à¤¶à¤• +Comment[hr]=Ugradivi preglednik slika +Comment[hu]=Beágyazható Troff-komponens +Comment[is]=Ãvefjanlegur troff skoðari +Comment[it]=Visualizzatore integrabile di file Troff +Comment[ja]=埋ã‚込㿠Troff ビューア +Comment[ka]=ჩáƒáƒ¨áƒ”ნებული დáƒáƒ›áƒ—ვáƒáƒšáƒ˜áƒ”რებელი პრáƒáƒ’რáƒáƒ›áƒ Troff +Comment[kk]=Құрамына енгізілетін Troff қарау құралы +Comment[km]=កម្មវិធី​មើល Troff ដែល​អាច​បង្កប់​បាន +Comment[ko]=í¬í•¨ 가능한 Troff ë·°ì–´ +Comment[lo]=ຄàºàº¡à»‚ປເນັນຕົວສະà»àº”ງພາບທີ່àºàº±à»ˆàº‡à»„ດ້ +Comment[lt]=Ä®dedamas Troff žiÅ«riklis +Comment[lv]=Iegults Troff skatÄ«tÄjs +Comment[mk]=Вгнездлив Troff прегледувач +Comment[mn]=Холбох боломжит Troff-Харагч +Comment[ms]=Pemapar Troff Boleh Benam +Comment[mt]=Werrej integrat Troff +Comment[nb]=Innebyggbar Troff-viser +Comment[nds]=Kieker för Troff, de inbett warrn kann +Comment[ne]=समà¥à¤®à¤¿à¤²à¤¿à¤¤ गरà¥à¤¨ सकिने टà¥à¤°à¤« दरà¥à¤¶à¤• +Comment[nl]=Ingebed Troff-weergaveprogramma +Comment[nn]=Innebyggbar Troff-visar +Comment[nso]=Selebeledi seo se Robaditswego sa Troff +Comment[pa]=ਸ਼ਾਮਿਲ ਹੋਣਯੋਗ Troff ਦਰਸ਼ਕ +Comment[pl]=PrzeglÄ…dania plików troff +Comment[pt]=Visualizador de Troff incorporado +Comment[pt_BR]=Visualizar Troff integrado +Comment[ro]=Componentă de vizualizare Troff înglobată +Comment[ru]=Ð’ÑÑ‚Ñ€Ð°Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° проÑмотра Troff +Comment[rw]=Mugaragaza Troff Ishyirwamo +Comment[se]=Vuojohahtti Troff-cájeheaddji +Comment[sk]=Vložiteľný prehliadaÄ Troff +Comment[sl]=VkljuÄen pregledovalnik za Troff +Comment[sr]=Уградиви Troff приказивач +Comment[sr@Latn]=Ugradivi Troff prikazivaÄ +Comment[sv]=Inbäddningsbar Troff-visare +Comment[ta]=உடà¯à®ªà¯Šà®¤à®¿à®¨à¯à®¤ டà¯à®°à®¾à®ƒà®ªà¯ காடà¯à®šà®¿ +Comment[te]=పొదగదగà±à°— à°Ÿà±à°°à°¾à°«à± వీకà±à°·à°¿à°£à°¿ +Comment[tg]=Биношавандаи барномаи намоишгари Troff +Comment[th]=โปรà¹à¸à¸£à¸¡à¸”ู Troff ที่สามารถà¸à¸±à¸‡à¸•ัวได้ +Comment[tr]=Gömülebilir Troff Görüntüleyici +Comment[tt]=Quşılulı Troff-Kürsätkeç +Comment[uk]=Вмонтований переглÑдач Troff +Comment[uz]=Ichiga oÊ»rnatib boÊ»ladigan Troff-faylini koÊ»ruvchi +Comment[uz@cyrillic]=Ичига ўрнатиб бўладиган Troff-файлини кўрувчи +Comment[ven]=Tshivhoni tsha Troff tsho dzheniswaho +Comment[vi]=Trình xem Troff nhúng được +Comment[wa]=RavalÃ¥ve hÃ¥yneu di fitchîs Troff +Comment[xh]=Umboniseli we Troff Elungiselekayo +Comment[zh_CN]=嵌入的 Troff 查看器 +Comment[zh_TW]=å¯åµŒå…¥çš„ Troff 檢視元件 +Comment[zu]=Umbukisi we-Troff Oshuthekiwe +MimeType=application/x-troff;application/x-troff-man; +Name=KManPart +Name[eo]=KMan-parto +Name[hi]=के-मेन-पारà¥à¤Ÿ +Name[lo]=ຕົວຮງàºàºžàº·à»‰àº™àº—ີ່ທຳງານ - K +Name[ne]=K मà¥à¤¯à¤¾à¤¨ à¤à¤¾à¤— +Name[pt_BR]=Componente KMan +Name[ro]=Componentă KMan +Name[sv]=Kman-del +Name[te]=కెమేనౠà°à°¾à°—à°‚ +ServiceTypes=KParts/ReadOnlyPart,Browser/View +X-KDE-Library=libkmanpart diff --git a/kioslave/man/kmanpart.h b/kioslave/man/kmanpart.h new file mode 100644 index 000000000..f68b68784 --- /dev/null +++ b/kioslave/man/kmanpart.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Alexander Neundorf <neundorf@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 KMANPART_H +#define KMANPART_H + +#include <kparts/factory.h> +#include <kparts/part.h> +#include <kparts/browserextension.h> +#include <khtml_part.h> +#include <kio/job.h> +#include <kio/jobclasses.h> + +#include <qcstring.h> + +class KInstance; +class KAboutData; + +/** + * Man Page Viewer + * \todo: Why is it needed? Why is KHTML alone not possible? + */ +class KManPartFactory: public KParts::Factory +{ + Q_OBJECT + public: + KManPartFactory( QObject * parent = 0, const char * name = 0 ); + virtual ~KManPartFactory(); + + virtual KParts::Part* createPartObject( QWidget * parentWidget, const char * widgetName , + QObject* parent, const char* name, const char * classname, + const QStringList &args); + + static KInstance * instance(); + + private: + static KInstance * s_instance; + static KAboutData * s_about; + +}; + +class KManPart : public KHTMLPart +{ + Q_OBJECT + public: + KManPart( QWidget * parent, const char * name = 0L ); + KParts::BrowserExtension * extension() {return m_extension;} + + public slots: + virtual bool openURL( const KURL &url ); + protected slots: + void readData(KIO::Job * , const QByteArray & data); + void jobDone( KIO::Job *); + protected: + virtual bool openFile(); + KInstance *m_instance; + KParts::BrowserExtension * m_extension; + KIO::TransferJob *m_job; +}; + +#endif + diff --git a/kioslave/man/man.protocol b/kioslave/man/man.protocol new file mode 100644 index 000000000..5df21f19d --- /dev/null +++ b/kioslave/man/man.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=kio_man +protocol=man +input=none +output=filesystem +reading=true +listing=Name +defaultMimetype=text/html +determineMimetypeFromExtension=false +DocPath=kioslave/man.html +Icon=man +Class=:local diff --git a/kioslave/man/man2html.cpp b/kioslave/man/man2html.cpp new file mode 100644 index 000000000..725fba36a --- /dev/null +++ b/kioslave/man/man2html.cpp @@ -0,0 +1,5683 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2005 Nicolas GOUTTE <goutte@kde.org> + + ### TODO: who else? +*/ + +// Start of verbatim comment + +/* +** This program was written by Richard Verhoeven (NL:5482ZX35) +** at the Eindhoven University of Technology. Email: rcb5@win.tue.nl +** +** Permission is granted to distribute, modify and use this program as long +** as this comment is not removed or changed. +*/ + +// End of verbatim comment + +// kate: space-indent on; indent-width 4; replace-tabs on; + +/* + * man2html-linux-1.0/1.1 + * This version modified for Redhat/Caldera linux - March 1996. + * Michael Hamilton <michael@actrix.gen.nz>. + * + * man2html-linux-1.2 + * Added support for BSD mandoc pages - I didn't have any documentation + * on the mandoc macros, so I may have missed some. + * Michael Hamilton <michael@actrix.gen.nz>. + * + * vh-man2html-1.3 + * Renamed to avoid confusion (V for Verhoeven, H for Hamilton). + * + * vh-man2html-1.4 + * Now uses /etc/man.config + * Added support for compressed pages. + * Added "length-safe" string operations for client input parameters. + * More secure, -M secured, and client input string lengths checked. + * + */ + +/* +** If you want to use this program for your WWW server, adjust the line +** which defines the CGIBASE or compile it with the -DCGIBASE='"..."' option. +** +** You have to adjust the built-in manpath to your local system. Note that +** every directory should start and end with the '/' and that the first +** directory should be "/" to allow a full path as an argument. +** +** The program first check if PATH_INFO contains some information. +** If it does (t.i. man2html/some/thing is used), the program will look +** for a manpage called PATH_INFO in the manpath. +** +** Otherwise the manpath is searched for the specified command line argument, +** where the following options can be used: +** +** name name of manpage (csh, printf, xv, troff) +** section the section (1 2 3 4 5 6 7 8 9 n l 1v ...) +** -M path an extra directory to look for manpages (replaces "/") +** +** If man2html finds multiple manpages that satisfy the options, an index +** is displayed and the user can make a choice. If only one page is +** found, that page will be displayed. +** +** man2html will add links to the converted manpages. The function add_links +** is used for that. At the moment it will add links as follows, where +** indicates what should match to start with: +** ^^^ +** Recognition Item Link +** ---------------------------------------------------------- +** name(*) Manpage ../man?/name.* +** ^ +** name@hostname Email address mailto:name@hostname +** ^ +** method://string URL method://string +** ^^^ +** www.host.name WWW server http://www.host.name +** ^^^^ +** ftp.host.name FTP server ftp://ftp.host.name +** ^^^^ +** <file.h> Include file file:/usr/include/file.h +** ^^^ +** +** Since man2html does not check if manpages, hosts or email addresses exist, +** some links might not work. For manpages, some extra checks are performed +** to make sure not every () pair creates a link. Also out of date pages +** might point to incorrect places. +** +** The program will not allow users to get system specific files, such as +** /etc/passwd. It will check that "man" is part of the specified file and +** that "/../" isn't. Even if someone manages to get such file, man2html will +** handle it like a manpage and will usually not produce any output (or crash). +** +** If you find any bugs when normal manpages are converted, please report +** them to me (rcb5@win.tue.nl) after you have checked that man(1) can handle +** the manpage correct. +** +** Known bugs and missing features: +** +** * Equations are not converted at all. +** * Tables are converted but some features are not possible in html. +** * The tabbing environment is converted by counting characters and adding +** spaces. This might go wrong (outside <PRE>) +** * Some manpages rely on the fact that troff/nroff is used to convert +** them and use features which are not descripted in the man manpages. +** (definitions, calculations, conditionals, requests). I can't guarantee +** that all these features work on all manpages. (I didn't have the +** time to look through all the available manpages.) +*/ + +#ifdef SIMPLE_MAN2HTML + // We suppose that we run on a standard Linux +# define HAVE_STRING_H 1 +# define HAVE_UNISTD_H 1 +#else +# include <config.h> +#endif + +#include <ctype.h> + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifdef HAVE_STRING_H +# include <string.h> +#endif + +#include <stdio.h> + +#include <qvaluestack.h> +#include <qstring.h> +#include <qptrlist.h> +#include <qmap.h> +#include <qdatetime.h> + +#ifdef SIMPLE_MAN2HTML +# include <stdlib.h> +# include <iostream> +# include <dirent.h> +# include <sys/stat.h> +# define kdDebug(x) cerr +# define kdWarning(x) cerr << "WARNING " +#else +# include <qtextcodec.h> +# include <kdebug.h> +# include <kdeversion.h> +#endif + + + +#include "man2html.h" + +using namespace std; + +#define NULL_TERMINATED(n) ((n) + 1) + +#define HUGE_STR_MAX 10000 +#define LARGE_STR_MAX 2000 +#define MED_STR_MAX 500 +#define SMALL_STR_MAX 100 +#define TINY_STR_MAX 10 + + +#if 1 +// The output is current too horrible to be called HTML 4.01 +#define DOCTYPE "<!DOCTYPE HTML>" +#else +#define DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" +#endif + +/* mdoc(7) Bl/El lists to HTML list types */ +#define BL_DESC_LIST 1 +#define BL_BULLET_LIST 2 +#define BL_ENUM_LIST 4 + +/* mdoc(7) Bd/Ed example(?) blocks */ +#define BD_LITERAL 1 +#define BD_INDENT 2 + +static int s_nroff = 1; // NROFF mode by default + +static int mandoc_name_count = 0; /* Don't break on the first Nm */ + +static char *stralloc(int len) +{ + /* allocate enough for len + NULL */ + char *news = new char [len+1]; +#ifdef SIMPLE_MAN2HTML + if (!news) + { + cerr << "man2html: out of memory" << endl; + exit(EXIT_FAILURE); + } +#else +// modern compilers do not return a NULL pointer for a new +#endif + return news; +} + +static char *strlimitcpy(char *to, char *from, int n, int limit) +{ /* Assumes space for limit plus a null */ + const int len = n > limit ? limit : n; + qstrncpy(to, from, len + 1); + to[len] = '\0'; + return to; +} + +/* below this you should not change anything unless you know a lot +** about this program or about troff. +*/ + + +/// Structure for character definitions +struct CSTRDEF { + int nr, slen; + const char *st; +}; + + + +const char NEWLINE[2]="\n"; + +/** + * Class for defining strings and macros + */ +class StringDefinition +{ +public: + StringDefinition( void ) : m_length(0) {} + StringDefinition( int len, const char* cstr ) : m_length( len ), m_output( cstr ) {} +public: + int m_length; ///< Length of output text + QCString m_output; ///< Defined string +}; + +/** + * Class for defining number registers + * \note Not for internal read-only registers + */ +class NumberDefinition +{ + public: + NumberDefinition( void ) : m_value(0), m_increment(0) {} + NumberDefinition( int value ) : m_value( value ), m_increment(0) {} + NumberDefinition( int value, int incr) : m_value( value ), m_increment( incr ) {} + public: + int m_value; ///< value of number register + int m_increment; ///< Increment of number register + // ### TODO: display form (.af) +}; + +/** + * Map of character definitions + */ +static QMap<QCString,StringDefinition> s_characterDefinitionMap; + +/** + * Map of string variable and macro definitions + * \note String variables and macros are the same thing! + */ +static QMap<QCString,StringDefinition> s_stringDefinitionMap; + +/** + * Map of number registers + * \note Intern number registers (starting with a dot are not handled here) + */ +static QMap<QCString,NumberDefinition> s_numberDefinitionMap; + +static void fill_old_character_definitions( void ); + +/** + * Initialize character variables + */ +static void InitCharacterDefinitions( void ) +{ + fill_old_character_definitions(); + // ### HACK: as we are converting to HTML too early, define characters with HTML references + s_characterDefinitionMap.insert( "<-", StringDefinition( 1, "←" ) ); // <- + s_characterDefinitionMap.insert( "->", StringDefinition( 1, "→" ) ); // -> + s_characterDefinitionMap.insert( "<>", StringDefinition( 1, "↔" ) ); // <> + s_characterDefinitionMap.insert( "<=", StringDefinition( 1, "≤" ) ); // <= + s_characterDefinitionMap.insert( ">=", StringDefinition( 1, "≥" ) ); // >= + // End HACK +} + +/** + * Initialize string variables + */ +static void InitStringDefinitions( void ) +{ + // mdoc-only, see mdoc.samples(7) + s_stringDefinitionMap.insert( "<=", StringDefinition( 1, "≤" ) ); + s_stringDefinitionMap.insert( ">=", StringDefinition( 1, "≥" ) ); + s_stringDefinitionMap.insert( "Rq", StringDefinition( 1, "”" ) ); + s_stringDefinitionMap.insert( "Lq", StringDefinition( 1, "“" ) ); + s_stringDefinitionMap.insert( "ua", StringDefinition( 1, "&circ" ) ); // Note this is different from \(ua + s_stringDefinitionMap.insert( "aa", StringDefinition( 1, "´" ) ); + s_stringDefinitionMap.insert( "ga", StringDefinition( 1, "`" ) ); + s_stringDefinitionMap.insert( "q", StringDefinition( 1, """ ) ); + s_stringDefinitionMap.insert( "Pi", StringDefinition( 1, "π" ) ); + s_stringDefinitionMap.insert( "Ne", StringDefinition( 1, "≠" ) ); + s_stringDefinitionMap.insert( "Le", StringDefinition( 1, "≤" ) ); + s_stringDefinitionMap.insert( "Ge", StringDefinition( 1, "≥" ) ); + s_stringDefinitionMap.insert( "Lt", StringDefinition( 1, "<" ) ); + s_stringDefinitionMap.insert( "Gt", StringDefinition( 1, ">" ) ); + s_stringDefinitionMap.insert( "Pm", StringDefinition( 1, "±" ) ); + s_stringDefinitionMap.insert( "If", StringDefinition( 1, "∞" ) ); + s_stringDefinitionMap.insert( "Na", StringDefinition( 3, "NaN" ) ); + s_stringDefinitionMap.insert( "Ba", StringDefinition( 1, "|" ) ); + // end mdoc-only + // man(7) + s_stringDefinitionMap.insert( "Tm", StringDefinition( 1, "™" ) ); // \*(TM + s_stringDefinitionMap.insert( "R", StringDefinition( 1, "®" ) ); // \*R + // end man(7) + // Missing characters from man(7): + // \*S "Change to default font size" +#ifndef SIMPLE_MAN2HTML + // Special KDE KIO man: + const QCString kdeversion(KDE_VERSION_STRING); + s_stringDefinitionMap.insert( ".KDE_VERSION_STRING", StringDefinition( kdeversion.length(), kdeversion ) ); +#endif +} + +/** + * Initialize number registers + * \note Internal read-only registers are not handled here + */ +static void InitNumberDefinitions( void ) +{ + // As the date number registers are more for end-users, better choose local time. + // Groff seems to support Gregorian dates only + QDate today( QDate::currentDate( Qt::LocalTime ) ); + s_numberDefinitionMap.insert( "year", today.year() ); // Y2K-correct year + s_numberDefinitionMap.insert( "yr", today.year() - 1900 ); // Y2K-incorrect year + s_numberDefinitionMap.insert( "mo", today.month() ); + s_numberDefinitionMap.insert( "dy", today.day() ); + s_numberDefinitionMap.insert( "dw", today.dayOfWeek() ); +} + + +#define V(A,B) ((A)*256+(B)) + +//used in expand_char, e.g. for "\(bu" +// see groff_char(7) for list +static CSTRDEF standardchar[] = { + { V('*','*'), 1, "*" }, + { V('*','A'), 1, "Α" }, + { V('*','B'), 1, "Β" }, + { V('*','C'), 1, "Ξ" }, + { V('*','D'), 1, "Δ" }, + { V('*','E'), 1, "Ε" }, + { V('*','F'), 1, "Φ" }, + { V('*','G'), 1, "Γ" }, + { V('*','H'), 1, "Θ" }, + { V('*','I'), 1, "Ι" }, + { V('*','K'), 1, "Κ" }, + { V('*','L'), 1, "Λ" }, + { V('*','M'), 1, "&Mu:" }, + { V('*','N'), 1, "Ν" }, + { V('*','O'), 1, "Ο" }, + { V('*','P'), 1, "Π" }, + { V('*','Q'), 1, "Ψ" }, + { V('*','R'), 1, "Ρ" }, + { V('*','S'), 1, "Σ" }, + { V('*','T'), 1, "Τ" }, + { V('*','U'), 1, "Υ" }, + { V('*','W'), 1, "Ω" }, + { V('*','X'), 1, "Χ" }, + { V('*','Y'), 1, "Η" }, + { V('*','Z'), 1, "Ζ" }, + { V('*','a'), 1, "α"}, + { V('*','b'), 1, "β"}, + { V('*','c'), 1, "ξ"}, + { V('*','d'), 1, "δ"}, + { V('*','e'), 1, "ε"}, + { V('*','f'), 1, "φ"}, + { V('*','g'), 1, "γ"}, + { V('*','h'), 1, "θ"}, + { V('*','i'), 1, "ι"}, + { V('*','k'), 1, "κ"}, + { V('*','l'), 1, "λ"}, + { V('*','m'), 1, "μ" }, + { V('*','n'), 1, "ν"}, + { V('*','o'), 1, "ο"}, + { V('*','p'), 1, "π"}, + { V('*','q'), 1, "ψ"}, + { V('*','r'), 1, "ρ"}, + { V('*','s'), 1, "σ"}, + { V('*','t'), 1, "τ"}, + { V('*','u'), 1, "υ"}, + { V('*','w'), 1, "ω"}, + { V('*','x'), 1, "χ"}, + { V('*','y'), 1, "η"}, + { V('*','z'), 1, "ζ"}, + { V('+','-'), 1, "±" }, // not in groff_char(7) + { V('+','f'), 1, "φ"}, // phi1, we use the standard phi + { V('+','h'), 1, "θ"}, // theta1, we use the standard theta + { V('+','p'), 1, "ω"}, // omega1, we use the standard omega + { V('1','2'), 1, "½" }, + { V('1','4'), 1, "¼" }, + { V('3','4'), 1, "¾" }, + { V('F','i'), 1, "ffi" }, // ffi ligature + { V('F','l'), 1, "ffl" }, // ffl ligature + { V('a','p'), 1, "~" }, + { V('b','r'), 1, "|" }, + { V('b','u'), 1, "•" }, + { V('b','v'), 1, "|" }, + { V('c','i'), 1, "○" }, // circle ### TODO verify + { V('c','o'), 1, "©" }, + { V('c','t'), 1, "¢" }, + { V('d','e'), 1, "°" }, + { V('d','g'), 1, "†" }, + { V('d','i'), 1, "÷" }, + { V('e','m'), 1, "&emdash;" }, + { V('e','n'), 1, "&endash;"}, + { V('e','q'), 1, "=" }, + { V('e','s'), 1, "∅" }, + { V('f','f'), 1, "�xFB00;" }, // ff ligature + { V('f','i'), 1, "�xFB01;" }, // fi ligature + { V('f','l'), 1, "�xFB02;" }, // fl ligature + { V('f','m'), 1, "′" }, + { V('g','a'), 1, "`" }, + { V('h','y'), 1, "-" }, + { V('l','c'), 2, "|¯" }, // ### TODO: not in groff_char(7) + { V('l','f'), 2, "|_" }, // ### TODO: not in groff_char(7) + { V('l','k'), 1, "<FONT SIZE=+2>{</FONT>" }, // ### TODO: not in groff_char(7) + { V('m','i'), 1, "-" }, // ### TODO: not in groff_char(7) + { V('m','u'), 1, "×" }, + { V('n','o'), 1, "¬" }, + { V('o','r'), 1, "|" }, + { V('p','l'), 1, "+" }, + { V('r','c'), 2, "¯|" }, // ### TODO: not in groff_char(7) + { V('r','f'), 2, "_|" }, // ### TODO: not in groff_char(7) + { V('r','g'), 1, "®" }, + { V('r','k'), 1, "<FONT SIZE=+2>}</FONT>" }, // ### TODO: not in groff_char(7) + { V('r','n'), 1, "‾" }, + { V('r','u'), 1, "_" }, + { V('s','c'), 1, "§" }, + { V('s','l'), 1, "/" }, + { V('s','q'), 2, "□" }, // WHITE SQUARE + { V('t','s'), 1, "ς" }, // FINAL SIGMA + { V('u','l'), 1, "_" }, + { V('-','D'), 1, "Ð" }, + { V('S','d'), 1, "ð" }, + { V('T','P'), 1, "Þ" }, + { V('T','p'), 1, "þ" }, + { V('A','E'), 1, "Æ" }, + { V('a','e'), 1, "æ" }, + { V('O','E'), 1, "Œ" }, + { V('o','e'), 1, "œ" }, + { V('s','s'), 1, "ß" }, + { V('\'','A'), 1, "Á" }, + { V('\'','E'), 1, "É" }, + { V('\'','I'), 1, "Í" }, + { V('\'','O'), 1, "Ó" }, + { V('\'','U'), 1, "Ú" }, + { V('\'','Y'), 1, "Ý" }, + { V('\'','a'), 1, "á" }, + { V('\'','e'), 1, "é" }, + { V('\'','i'), 1, "í" }, + { V('\'','o'), 1, "ó" }, + { V('\'','u'), 1, "ú" }, + { V('\'','y'), 1, "ý" }, + { V(':','A'), 1, "Ä" }, + { V(':','E'), 1, "Ë" }, + { V(':','I'), 1, "Ï" }, + { V(':','O'), 1, "Ö" }, + { V(':','U'), 1, "Ü" }, + { V(':','a'), 1, "ä" }, + { V(':','e'), 1, "ë" }, + { V(':','i'), 1, "ï" }, + { V(':','o'), 1, "ö" }, + { V(':','u'), 1, "ü" }, + { V(':','y'), 1, "ÿ" }, + { V('^','A'), 1, "Â" }, + { V('^','E'), 1, "Ê" }, + { V('^','I'), 1, "Î" }, + { V('^','O'), 1, "Ô" }, + { V('^','U'), 1, "Û" }, + { V('^','a'), 1, "â" }, + { V('^','e'), 1, "ê" }, + { V('^','i'), 1, "î" }, + { V('^','o'), 1, "ô" }, + { V('^','u'), 1, "û" }, + { V('`','A'), 1, "À" }, + { V('`','E'), 1, "È" }, + { V('`','I'), 1, "Ì" }, + { V('`','O'), 1, "Ò" }, + { V('`','U'), 1, "Ù" }, + { V('`','a'), 1, "à" }, + { V('`','e'), 1, "è" }, + { V('`','i'), 1, "ì" }, + { V('`','o'), 1, "ò" }, + { V('`','u'), 1, "ù" }, + { V('~','A'), 1, "Ã" }, + { V('~','N'), 1, "Ñ" }, + { V('~','O'), 1, "Õ" }, + { V('~','a'), 1, "ã" }, + { V('~','n'), 1, "&ntidle;" }, + { V('~','o'), 1, "&otidle;" }, + { V(',','C'), 1, "Ç" }, + { V(',','c'), 1, "ç" }, + { V('/','L'), 1, "Ł" }, + { V('/','l'), 1, "ł" }, + { V('/','O'), 1, "Ø" }, + { V('/','o'), 1, "ø" }, + { V('o','A'), 1, "Å" }, + { V('o','a'), 1, "å" }, + { V('a','"'), 1, "\"" }, + { V('a','-'), 1, "¯" }, + { V('a','.'), 1, "." }, + { V('a','^'), 1, "ˆ" }, + { V('a','a'), 1, "´" }, + { V('a','b'), 1, "`" }, + { V('a','c'), 1, "¸" }, + { V('a','d'), 1, "¨" }, + { V('a','h'), 1, "˂" }, // caron + { V('a','o'), 1, "˚" }, // ring + { V('a','~'), 1, "˜" }, + { V('h','o'), 1, "˛" }, // ogonek + { V('.','i'), 1, "ı" }, // dot less i + { V('C','s'), 1, "¤" }, + { V('D','o'), 1, "$" }, + { V('P','o'), 1, "£" }, + { V('Y','e'), 1, "¥" }, + { V('F','n'), 1, "ƒ" }, + { V('F','o'), 1, "«" }, + { V('F','c'), 1, "»" }, + { V('f','o'), 1, "‹" }, // single left guillemet + { V('f','c'), 1, "›" }, // single right guillemet + { V('r','!'), 1, "&iecl;" }, + { V('r','?'), 1, "¿" }, + { V('O','f'), 1, "ª" }, + { V('O','m'), 1, "º" }, + { V('p','c'), 1, "·" }, + { V('S','1'), 1, "¹" }, + { V('S','2'), 1, "²" }, + { V('S','3'), 1, "³" }, + { V('<','-'), 1, "←" }, + { V('-','>'), 1, "→" }, + { V('<','>'), 1, "↔" }, + { V('d','a'), 1, "↓" }, + { V('u','a'), 1, "↑" }, + { V('l','A'), 1, "⇐" }, + { V('r','A'), 1, "⇒" }, + { V('h','A'), 1, "⇔" }, + { V('d','A'), 1, "⇓" }, + { V('u','A'), 1, "⇑" }, + { V('b','a'), 1, "|" }, + { V('b','b'), 1, "¦" }, + { V('t','m'), 1, "™" }, + { V('d','d'), 1, "‡" }, + { V('p','s'), 1, "¶" }, + { V('%','0'), 1, "‰" }, + { V('f','/'), 1, "⁄" }, // Fraction slash + { V('s','d'), 1, "″" }, + { V('h','a'), 1, "^" }, + { V('t','i'), 1, "&tidle;" }, + { V('l','B'), 1, "[" }, + { V('r','B'), 1, "]" }, + { V('l','C'), 1, "{" }, + { V('r','C'), 1, "}" }, + { V('l','a'), 1, "<" }, + { V('r','a'), 1, ">" }, + { V('l','h'), 1, "≤" }, + { V('r','h'), 1, "≥" }, + { V('B','q'), 1, "„" }, + { V('b','q'), 1, "‚" }, + { V('l','q'), 1, "“" }, + { V('r','q'), 1, "”" }, + { V('o','q'), 1, "‘" }, + { V('c','q'), 1, "’" }, + { V('a','q'), 1, "'" }, + { V('d','q'), 1, "\"" }, + { V('a','t'), 1, "@" }, + { V('s','h'), 1, "#" }, + { V('r','s'), 1, "\\" }, + { V('t','f'), 1, "∴" }, + { V('~','~'), 1, "≅" }, + { V('~','='), 1, "≈" }, + { V('!','='), 1, "≠" }, + { V('<','='), 1, "≤" }, + { V('=','='), 1, "≡" }, + { V('=','~'), 1, "≅" }, // ### TODO: verify + { V('>','='), 1, "≥" }, + { V('A','N'), 1, "∧" }, + { V('O','R'), 1, "∨" }, + { V('t','e'), 1, "∃" }, + { V('f','a'), 1, "∀" }, + { V('A','h'), 1, "ℵ" }, + { V('I','m'), 1, "ℑ" }, + { V('R','e'), 1, "ℜ" }, + { V('i','f'), 1, "∞" }, + { V('m','d'), 1, "⋅" }, + { V('m','o'), 1, "∆" }, // element ### TODO verify + { V('n','m'), 1, "∉" }, + { V('p','t'), 1, "∝" }, + { V('p','p'), 1, "⊥" }, + { V('s','b'), 1, "⊂" }, + { V('s','p'), 1, "⊃" }, + { V('i','b'), 1, "⊆" }, + { V('i','p'), 1, "⊇" }, + { V('i','s'), 1, "∫" }, + { V('s','r'), 1, "√" }, + { V('p','d'), 1, "∂" }, + { V('c','*'), 1, "⊗" }, + { V('c','+'), 1, "⊕" }, + { V('c','a'), 1, "∩" }, + { V('c','u'), 1, "∪" }, + { V('g','r'), 1, "V" }, // gradient ### TODO Where in Unicode? + { V('C','R'), 1, "↵" }, + { V('s','t'), 2, "-)" }, // "such that" ### TODO Where in Unicode? + { V('/','_'), 1, "∠" }, + { V('w','p'), 1, "℘" }, + { V('l','z'), 1, "◊" }, + { V('a','n'), 1, "-" }, // "horizontal arrow extension" ### TODO Where in Unicode? +}; + +/* default: print code */ + + +/* static char eqndelimopen=0, eqndelimclose=0; */ +static char escapesym='\\', nobreaksym='\'', controlsym='.', fieldsym=0, padsym=0; + +static char *buffer=NULL; +static int buffpos=0, buffmax=0; +static bool scaninbuff=false; +static int itemdepth=0; +static int section=0; +static int dl_set[20]= { 0 }; +static bool still_dd=0; +static int tabstops[20] = { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96 }; +static int maxtstop=12; +static int curpos=0; + +static char *scan_troff(char *c, bool san, char **result); +static char *scan_troff_mandoc(char *c, bool san, char **result); + +static QValueList<char*> s_argumentList; + +static QCString htmlPath, cssPath; + +static QCString s_dollarZero; // Value of $0 + +void setResourcePath(const QCString& _htmlPath, const QCString& _cssPath) +{ + htmlPath=_htmlPath; + cssPath=_cssPath; +} + +static void fill_old_character_definitions( void ) +{ + for (size_t i = 0; i < sizeof(standardchar)/sizeof(CSTRDEF); i++) + { + const int nr = standardchar[i].nr; + const char temp[3] = { nr / 256, nr % 256, 0 }; + QCString name( temp ); + s_characterDefinitionMap.insert( name, StringDefinition( standardchar[i].slen, standardchar[i].st ) ); + } +} + +static char outbuffer[NULL_TERMINATED(HUGE_STR_MAX)]; +static int no_newline_output=0; +static int newline_for_fun=0; +static bool output_possible=false; + +static const char *includedirs[] = { + "/usr/include", + "/usr/include/sys", + "/usr/local/include", + "/opt/local/include", + "/usr/ccs", + "/usr/X11R6/include", + "/usr/openwin/include", + "/usr/include/g++", + 0 +}; + +static bool ignore_links=false; + +static void add_links(char *c) +{ + /* + ** Add the links to the output. + ** At the moment the following are recognized: + ** + ** name(*) -> ../man?/name.* + ** method://string -> method://string + ** www.host.name -> http://www.host.name + ** ftp.host.name -> ftp://ftp.host.name + ** name@host -> mailto:name@host + ** <name.h> -> file:/usr/include/name.h (guess) + ** + ** Other possible links to add in the future: + ** + ** /dir/dir/file -> file:/dir/dir/file + */ + if (ignore_links) + { + output_real(c); + return; + } + + int i,j,nr; + char *f, *g,*h; + const int numtests=6; // Nmber of tests + char *idtest[numtests]; // url, mailto, www, ftp, manpage, C header file + bool ok; + /* search for (section) */ + nr=0; + idtest[0]=strstr(c+1,"://"); + idtest[1]=strchr(c+1,'@'); + idtest[2]=strstr(c,"www."); + idtest[3]=strstr(c,"ftp."); + idtest[4]=strchr(c+1,'('); + idtest[5]=strstr(c+1,".h>"); + for (i=0; i<numtests; ++i) nr += (idtest[i]!=NULL); + while (nr) { + j=-1; + for (i=0; i<numtests; i++) + if (idtest[i] && (j<0 || idtest[i]<idtest[j])) j=i; + switch (j) { + case 5: { /* <name.h> */ + f=idtest[5]; + h=f+2; + g=f; + while (g>c && g[-1]!=';') g--; + bool wrote_include = false; + + if (g!=c) { + + QCString dir; + QCString file(g, h - g + 1); + file = file.stripWhiteSpace(); + for (int index = 0; includedirs[index]; index++) { + QCString str = QCString(includedirs[index]) + "/" + file; + if (!access(str, R_OK)) { + dir = includedirs[index]; + break; + } + } + if (!dir.isEmpty()) { + + char t; + t=*g; + *g=0; + output_real(c); + *g=t;*h=0; + + QCString str; + str.sprintf("<A HREF=\"file:%s/%s\">%s</A>>", dir.data(), file.data(), file.data()); + output_real(str.data()); + c=f+6; + wrote_include = true; + } + + } + + if (!wrote_include) { + f[5]=0; + output_real(c); + f[5]=';'; + c=f+5; + } + } + break; + case 4: /* manpage */ + f=idtest[j]; + /* check section */ + g=strchr(f,')'); + // The character before f must alphanumeric, the end of a HTML tag or the end of a + if (g!=NULL && f>c && (g-f)<12 && (isalnum(f[-1]) || f[-1]=='>' || ( f[-1] == ';' ) ) && + isdigit(f[1]) && f[1]!='0' && ((g-f)<=2 || isalpha(f[2]))) + { + ok = TRUE; + h = f+2; + while (h<g) + { + if (!isalnum(*h++)) + { + ok = FALSE; + break; + } + } + } + else + ok = false; + + h = f - 1; + if ( ok ) + { + // Skip + kdDebug(7107) << "BEFORE SECTION:" << *h << endl; + if ( ( h > c + 5 ) && ( ! memcmp( h-5, " ", 6 ) ) ) + { + h -= 6; + kdDebug(7107) << "Skip " << endl; + } + else if ( *h == ';' ) + { + // Not a non-breaking space, so probably not ok + ok = false; + } + } + + if (ok) + { + /* this might be a link */ + /* skip html makeup */ + while (h>c && *h=='>') { + while (h!=c && *h!='<') h--; + if (h!=c) h--; + } + if (isalnum(*h)) { + char t,sec, *e; + QString subsec; // ### TODO avoid using QString, as we do not know the encoding + QString fstr(f); // ### TODO avoid using QString, as we do not know the encoding + e=h+1; + sec=f[1]; + subsec=f[2]; + int index = fstr.find(')', 2); + if (index != -1) + subsec = fstr.mid(2, index - 2); + else // No closing ')' found, take first character as subsection. + subsec = fstr.mid(2, 1); + while (h>c && (isalnum(h[-1]) || h[-1]=='_' + || h[-1]==':' || h[-1]=='-' || h[-1]=='.')) + h--; + t=*h; + *h='\0'; + output_real(c); + *h=t; + t=*e; + *e='\0'; + QCString str; + if (subsec.isEmpty()) + str.sprintf("<A HREF=\"man:%s(%c)\">%s</A>", h, sec, h); + else + str.sprintf("<A HREF=\"man:%s(%c%s)\">%s</A>", h, sec, subsec.lower().latin1(), h); + output_real(str.data()); + *e=t; + c=e; + } + } + *f='\0'; + output_real(c); + *f='('; + idtest[4]=f-1; + c=f; + break; /* manpage */ + case 3: /* ftp */ + case 2: /* www */ + g=f=idtest[j]; + while (*g && (isalnum(*g) || *g=='_' || *g=='-' || *g=='+' || + *g=='.' || *g=='/')) g++; + if (g[-1]=='.') g--; + if (g-f>4) { + char t; + t=*f; *f='\0'; + output_real(c); + *f=t; t=*g;*g='\0'; + QCString str; + str.sprintf("<A HREF=\"%s://%s\">%s</A>", ((j==3)?"ftp":"http"), f, f); + output_real(str.data()); + *g=t; + c=g; + } else { + f[3]='\0'; + output_real(c); + c=f+3; + f[3]='.'; + } + break; + case 1: /* mailto */ + g=f=idtest[1]; + while (g>c && (isalnum(g[-1]) || g[-1]=='_' || g[-1]=='-' || + g[-1]=='+' || g[-1]=='.' || g[-1]=='%')) g--; + if (g-7>=c && g[-1]==':') + { + // We have perhaps an email address starting with mailto: + if (!qstrncmp("mailto:",g-7,7)) + g-=7; + } + h=f+1; + while (*h && (isalnum(*h) || *h=='_' || *h=='-' || *h=='+' || + *h=='.')) h++; + if (*h=='.') h--; + if (h-f>4 && f-g>1) { + char t; + t=*g; + *g='\0'; + output_real(c); + *g=t;t=*h;*h='\0'; + QCString str; + str.sprintf("<A HREF=\"mailto:%s\">%s</A>", g, g); + output_real(str.data()); + *h=t; + c=h; + } else { + *f='\0'; + output_real(c); + *f='@'; + idtest[1]=c; + c=f; + } + break; + case 0: /* url */ + g=f=idtest[0]; + while (g>c && isalpha(g[-1]) && islower(g[-1])) g--; + h=f+3; + while (*h && !isspace(*h) && *h!='<' && *h!='>' && *h!='"' && + *h!='&') h++; + if (f-g>2 && f-g<7 && h-f>3) { + char t; + t=*g; + *g='\0'; + output_real(c); + *g=t; t=*h; *h='\0'; + QCString str; + str.sprintf("<A HREF=\"%s\">%s</A>", g, g); + output_real(str.data()); + *h=t; + c=h; + } else { + f[1]='\0'; + output_real(c); + f[1]='/'; + c=f+1; + } + break; + default: + break; + } + nr=0; + if (idtest[0] && idtest[0]<=c) idtest[0]=strstr(c+1,"://"); + if (idtest[1] && idtest[1]<=c) idtest[1]=strchr(c+1,'@'); + if (idtest[2] && idtest[2]<c) idtest[2]=strstr(c,"www."); + if (idtest[3] && idtest[3]<c) idtest[3]=strstr(c,"ftp."); + if (idtest[4] && idtest[4]<=c) idtest[4]=strchr(c+1,'('); + if (idtest[5] && idtest[5]<=c) idtest[5]=strstr(c+1,".h>"); + for (i=0; i<numtests; i++) nr += (idtest[i]!=NULL); + } + output_real(c); +} + +static QCString current_font; +static int current_size=0; +static int fillout=1; + +static void out_html(const char *c) +{ + if (!c) return; + + // Added, probably due to the const? + char *c2 = qstrdup(c); + char *c3 = c2; + + static int obp=0; + + if (no_newline_output) { + int i=0; + no_newline_output=1; + while (c2[i]) { + if (!no_newline_output) c2[i-1]=c2[i]; + if (c2[i]=='\n') no_newline_output=0; + i++; + } + if (!no_newline_output) c2[i-1]=0; + } + if (scaninbuff) { + while (*c2) { + if (buffpos>=buffmax) { + char *h = new char[buffmax*2]; + +#ifdef SIMPLE_MAN2HTML + if (!h) + { + cerr << "Memory full, cannot output!" << endl; + exit(1); + } +#else +// modern compiler do not return a NULL for a new +#endif + memcpy(h, buffer, buffmax); + delete [] buffer; + buffer=h; + buffmax=buffmax*2; + } + buffer[buffpos++]=*c2++; + } + } else + if (output_possible) { + while (*c2) { + outbuffer[obp++]=*c2; + if (*c=='\n' || obp >= HUGE_STR_MAX) { + outbuffer[obp]='\0'; + add_links(outbuffer); + obp=0; + } + c2++; + } + } + delete [] c3; +} + +static QCString set_font( const QCString& name ) +{ + // Every font but R (Regular) creates <span> elements + QCString markup; + if ( current_font != "R" && !current_font.isEmpty() ) + markup += "</span>"; + const uint len = name.length(); + bool fontok = true; + if ( len == 1 ) + { + const char lead = name[0]; + switch (lead) + { + case 'P': // Palatino? + case 'R': break; // regular, do nothing + case 'I': markup += "<span style=\"font-style:italic\">"; break; + case 'B': markup += "<span style=\"font-weight:bold\">"; break; + case 'L': markup += "<span style=\"font-family:monospace\">"; break; // ### What's L? + default: fontok = false; + } + } + else if ( len == 2 ) + { + if ( name == "BI" ) + markup += "<span style=\"font-style:italic;font-weight:bold\">"; + // Courier + else if ( name == "CR" ) + markup += "<span style=\"font-family:monospace\">"; + else if ( name == "CW" ) // CW is used by pod2man(1) (alias PerlDoc) + markup += "<span style=\"font-family:monospace\">"; + else if ( name == "CI" ) + markup += "<span style=\"font-family:monospace;font-style:italic\">"; + else if ( name == "CB" ) + markup += "<span style=\"font-family:monospace;font-weight:bold\">"; + // Times + else if ( name == "TR" ) + markup += "<span style=\"font-family:serif\">"; + else if ( name == "TI" ) + markup += "<span style=\"font-family:serif;font-style:italic\">"; + else if ( name == "TB" ) + markup += "<span style=\"font-family:serif;font-weight:bold\">"; + // Helvetica + else if ( name == "HR" ) + markup += "<span style=\"font-family:sans-serif\">"; + else if ( name == "HI" ) + markup += "<span style=\"font-family:sans-serif;font-style:italic\">"; + else if ( name == "HB" ) + markup += "<span style=\"font-family:sans-serif;font-weight:bold\">"; + else + fontok = false; + } + else if ( len == 3 ) + { + if ( name == "CBI" ) + markup += "<span style=\"font-family:monospace;font-style:italic;font-weight:bold\">"; + else if ( name == "TBI" ) + markup += "<span style=\"font-family:serif;font-style:italic;font-weight:bold\">"; + else if ( name == "HBI" ) + markup += "<span style=\"font-family:sans-serif;font-style:italic;font-weight:bold\">"; + } + if (fontok) + current_font = name; + else + current_font = "R"; // Still nothing, then it is 'R' (Regular) + return markup; +} + +/// \deprecated +static QCString set_font( const char ch ) +#ifndef SIMPLE_MAN2HTML + KDE_DEPRECATED; + +static QCString set_font( const char ch ) +#endif +{ + const QCString name = &ch; + return set_font( name ); +} + +static QCString change_to_size(int nr) +{ + switch (nr) + { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': nr=nr-'0'; break; + case '\0': break; + default: nr=current_size+nr; if (nr>9) nr=9; if (nr< -9) nr=-9; break; + } + if ( nr == current_size ) + return ""; + const QCString font ( current_font ); + QCString markup; + markup = set_font("R"); + if (current_size) + markup += "</FONT>"; + current_size=nr; + if (nr) + { + markup += "<FONT SIZE=\""; + if (nr>0) + markup += '+'; + else + { + markup += '-'; + nr=-nr; + } + markup += char( nr + '0' ); + markup += "\">"; + } + markup += set_font( font ); + return markup; +} + +/* static int asint=0; */ +static int intresult=0; + +#define SKIPEOL while (*c && *c++!='\n') + +static bool skip_escape=false; +static bool single_escape=false; + +static char *scan_escape_direct( char *c, QCString& cstr ); + +/** + * scan a named character + * param c position +*/ +static QCString scan_named_character( char*& c ) +{ + QCString name; + if ( *c == '(' ) + { + // \*(ab Name of two characters + if ( c[1] == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+2, cstr ); + // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used. + name = cstr; + } + else + { + name+=c[1]; + name+=c[2]; + c+=3; + } + } + else if ( *c == '[' ) + { + // \*[long_name] Long name + // Named character groff(7) + // We must find the ] to get a name + c++; + while ( *c && *c != ']' && *c != '\n' ) + { + if ( *c == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+1, cstr ); + const int result = cstr.find(']'); + if ( result == -1 ) + name += cstr; + else + { + // Note: we drop the characters after the ] + name += cstr.left( result ); + } + } + else + { + name+=*c; + c++; + } + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse character name: " << name << endl; + return ""; + } + c++; + } + else if ( *c =='C' || c[1]== '\'' ) + { + // \C'name' + c+=2; + while ( *c && *c != '\'' && *c != '\n' ) + { + if ( *c == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+1, cstr ); + const int result = cstr.find('\''); + if ( result == -1 ) + name += cstr; + else + { + // Note: we drop the characters after the ] + name += cstr.left( result ); + } + } + else + { + name+=*c; + c++; + } + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse (\\C mode) character name: " << name << endl; + return ""; + } + c++; + } + // Note: characters with a one character length name doe not exist, as they would collide with other escapes + + // Now we have the name, let us find it between the string names + QMap<QCString,StringDefinition>::iterator it=s_characterDefinitionMap.find(name); + if (it==s_characterDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find character with name: " << name << endl; + // No output, as an undefined string is empty by default + return ""; + } + else + { + kdDebug(7107) << "Character with name: \"" << name << "\" => " << (*it).m_output << endl; + return (*it).m_output; + } +} + +static QCString scan_named_string(char*& c) +{ + QCString name; + if ( *c == '(' ) + { + // \*(ab Name of two characters + if ( c[1] == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+2, cstr ); + kdDebug(7107) << "\\(" << cstr << endl; + // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used. + name = cstr; + } + else + { + name+=c[1]; + name+=c[2]; + c+=3; + } + } + else if ( *c == '[' ) + { + // \*[long_name] Long name + // Named character groff(7) + // We must find the ] to get a name + c++; + while ( *c && *c != ']' && *c != '\n' ) + { + if ( *c == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+1, cstr ); + const int result = cstr.find(']'); + if ( result == -1 ) + name += cstr; + else + { + // Note: we drop the characters after the ] + name += cstr.left( result ); + } + } + else + { + name+=*c; + c++; + } + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse string name: " << name << endl; + return ""; + } + c++; + } + else + { + // \*a Name of one character + name+=*c; + c++; + } + // Now we have the name, let us find it between the string names + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); + if (it==s_stringDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find string with name: " << name << endl; + // No output, as an undefined string is empty by default + return ""; + } + else + { + kdDebug(7107) << "String with name: \"" << name << "\" => " << (*it).m_output << endl; + return (*it).m_output; + } +} + +static QCString scan_dollar_parameter(char*& c) +{ + unsigned int argno = 0; // No dollar argument number yet! + if ( *c == '0' ) + { + //kdDebug(7107) << "$0" << endl; + c++; + return s_dollarZero; + } + else if ( *c >= '1' && *c <= '9' ) + { + //kdDebug(7107) << "$ direct" << endl; + argno = ( *c - '0' ); + c++; + } + else if ( *c == '(' ) + { + //kdDebug(7107) << "$(" << endl; + if ( c[1] && c[2] && c[1] >= '0' && c[1] <= '9' && c[2] >= '0' && c[2] <= '9' ) + { + argno = ( c[1] - '0' ) * 10 + ( c[2] - '0' ); + c += 3; + } + else + { + if ( !c[1] ) + c++; + else if ( !c[2] ) + c+=2; + else + c += 3; + return ""; + } + } + else if ( *c == '[' ) + { + //kdDebug(7107) << "$[" << endl; + argno = 0; + c++; + while ( *c && *c>='0' && *c<='9' && *c!=']' ) + { + argno *= 10; + argno += ( *c - '0' ); + c++; + } + if ( *c != ']' ) + { + return ""; + } + c++; + } + else if ( ( *c == '*' ) || ( *c == '@' ) ) + { + const bool quote = ( *c == '@' ); + QValueList<char*>::const_iterator it = s_argumentList.begin(); + QCString param; + bool space = false; + for ( ; it != s_argumentList.end(); ++it ) + { + if (space) + param += " "; + if (quote) + param += '\"'; // Not as HTML, as it could be used by macros ! + param += (*it); + if (quote) + param += '\"'; // Not as HTML, as it could be used by macros! + space = true; + } + c++; + return param; + } + else + { + kdDebug(7107) << "EXCEPTION: unknown parameter $" << *c << endl; + return ""; + } + //kdDebug(7107) << "ARG $" << argno << endl; + if ( !s_argumentList.isEmpty() && argno > 0 ) + { + //kdDebug(7107) << "ARG $" << argno << " OK!" << endl; + argno--; + if ( argno >= s_argumentList.size() ) + { + kdDebug(7107) << "EXCEPTION: cannot find parameter $" << (argno+1) << endl; + return ""; + } + + return s_argumentList[argno]; + } + return ""; +} + +/// return the value of read-only number registers +static int read_only_number_register( const QCString& name ) +{ + // Internal read-only variables + if ( name == ".$" ) + { + kdDebug(7107) << "\\n[.$] == " << s_argumentList.size() << endl; + return s_argumentList.size(); + } + else if ( name == ".g" ) + return 0; // We are not groff(1) + else if ( name == ".s" ) + return current_size; +#if 0 + // ### TODO: map the fonts to a number + else if ( name == ".f" ) + return current_font; +#endif + else if ( name == ".P" ) + return 0; // We are not printing + else if ( name == ".A" ) + return s_nroff; +#ifndef SIMPLE_MAN2HTML + // Special KDE KIO man: + else if ( name == ".KDE_VERSION_MAJOR" ) + return KDE_VERSION_MAJOR; + else if ( name == ".KDE_VERSION_MINOR" ) + return KDE_VERSION_MINOR; + else if ( name == ".KDE_VERSION_RELEASE" ) + return KDE_VERSION_RELEASE; + else if ( name == ".KDE_VERSION" ) + return KDE_VERSION; +#endif + // ### TODO: should .T be set to "html"? But we are not the HTML post-processor. :-( + + // ### TODO: groff defines much more read-only number registers +#ifndef SIMPLE_MAN2HTML + kdDebug(7107) << "EXCEPTION: unknown read-only number register: " << name << endl; +#endif + + return 0; // Undefined variable + +} + +/// get the value of a number register and auto-increment if asked +static int scan_number_register( char*& c) +{ + int sign = 0; // Sign for auto-increment (if any) + switch (*c) + { + case '+': sign = 1; c++; break; + case '-': sign = -1; c++; break; + default: break; + } + QCString name; + if ( *c == '[' ) + { + c++; + if ( *c == '+' ) + |