diff options
Diffstat (limited to 'kpf/src')
64 files changed, 12019 insertions, 0 deletions
diff --git a/kpf/src/ActiveMonitor.cpp b/kpf/src/ActiveMonitor.cpp new file mode 100644 index 00000000..8bd0c34a --- /dev/null +++ b/kpf/src/ActiveMonitor.cpp @@ -0,0 +1,226 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qlayout.h> + +#include <kiconloader.h> +#include <klocale.h> + +#include "Defines.h" +#include "ActiveMonitorItem.h" +#include "ActiveMonitor.h" +#include "WebServer.h" +#include "Server.h" +#include "Utils.h" + +namespace KPF +{ + ActiveMonitor::ActiveMonitor + ( + WebServer * server, + QWidget * parent, + const char * name + ) + : QWidget (parent, name), + server_ (server) + { + view_ = new QListView(this); + + view_->setAllColumnsShowFocus(true); + view_->setSelectionMode(QListView::Extended); + + view_->addColumn(i18n("Status")); + view_->addColumn(i18n("Progress")); + view_->addColumn(i18n("File Size")); + view_->addColumn(i18n("Bytes Sent")); + view_->addColumn(i18n("Response")); + view_->addColumn(i18n("Resource")); + view_->addColumn(i18n("Host")); + + QVBoxLayout * layout = new QVBoxLayout(this); + + layout->addWidget(view_); + + connect + ( + view_, + SIGNAL(selectionChanged()), + SLOT(slotSelectionChanged()) + ); + + connect + ( + server_, + SIGNAL(connection(Server *)), + SLOT(slotConnection(Server *)) + ); + + connect + ( + server_, + SIGNAL(output(Server *, ulong)), + SLOT(slotOutput(Server *, ulong)) + ); + + connect(server_, SIGNAL(finished(Server *)), SLOT(slotFinished(Server *))); + connect(server_, SIGNAL(request(Server *)), SLOT(slotRequest(Server *))); + connect(server_, SIGNAL(response(Server *)), SLOT(slotResponse(Server *))); + + connect(&cullTimer_, SIGNAL(timeout()), SLOT(slotCull())); + + cullTimer_.start(1000); + + // Tell whoever cares about our selection status. + slotSelectionChanged(); + } + + ActiveMonitor::~ActiveMonitor() + { + } + + void + ActiveMonitor::slotConnection(Server * s) + { + ActiveMonitorItem * i = new ActiveMonitorItem(s, view_); + itemMap_[s] = i; + } + + void + ActiveMonitor::slotOutput(Server * s, ulong l) + { + ActiveMonitorItem * i = itemMap_[s]; + + if (0 != i) + i->output(l); + } + + void + ActiveMonitor::slotFinished(Server * s) + { + ActiveMonitorItem * i = itemMap_[s]; + + if (0 != i) + i->finished(); + + itemMap_.remove(s); + } + + void + ActiveMonitor::slotRequest(Server * s) + { + ActiveMonitorItem * i = itemMap_[s]; + + if (0 != i) + i->request(); + } + + void + ActiveMonitor::slotResponse(Server * s) + { + ActiveMonitorItem * i = itemMap_[s]; + + if (0 != i) + i->response(); + } + + void + ActiveMonitor::slotCull() + { + QDateTime dt = QDateTime::currentDateTime(); + + QListViewItemIterator it(view_); + + for (; it.current(); ++it) + { + ActiveMonitorItem * i = static_cast<ActiveMonitorItem *>(it.current()); + + if ((0 == i->server()) && (i->death().secsTo(dt) > 60)) + { + delete i; + ++it; + } + } + } + + void + ActiveMonitor::slotSelectionChanged() + { + for (QListViewItemIterator it(view_); it.current(); ++it) + { + ActiveMonitorItem * i = static_cast<ActiveMonitorItem *>(it.current()); + + if + ( + view_->isSelected(i) + && + (0 != i->server()) + && + (Server::Finished != i->server()->state()) + ) + { + emit(selection(true)); + return; + } + } + + emit(selection(false)); + } + + void + ActiveMonitor::slotKillSelected() + { + for (QListViewItemIterator it(view_); it.current(); ++it) + { + ActiveMonitorItem * i = static_cast<ActiveMonitorItem *>(it.current()); + + if + ( + view_->isSelected(i) + && + (0 != i->server()) + && + (Server::Finished != i->server()->state()) + ) + { + i->server()->cancel(); + } + } + } + + WebServer * + ActiveMonitor::server() + { + return server_; + } + + void + ActiveMonitor::closeEvent(QCloseEvent * e) + { + QWidget::closeEvent(e); + emit(dying(this)); + } + +} // End namespace KPF + +#include "ActiveMonitor.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ActiveMonitor.h b/kpf/src/ActiveMonitor.h new file mode 100644 index 00000000..f0f62265 --- /dev/null +++ b/kpf/src/ActiveMonitor.h @@ -0,0 +1,165 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_ACTIVE_MONITOR_H +#define KPF_ACTIVE_MONITOR_H + +#include <qmap.h> +#include <qtimer.h> +#include <qwidget.h> + +class QListView; +class QPainter; +class QPushButton; + +namespace KPF +{ + class WebServer; + class Server; + class ActiveMonitorItem; + + /** + * Shows a list of ActiveMonitorItem objects. + * + * Proxies signals from Server objects to ActiveMonitorItem objects. + * This is done to avoid making ActiveMonitorItem inherit QObject. + */ + class ActiveMonitor : public QWidget + { + Q_OBJECT + + public: + + /** + * @param server WebServer which we should connect to in order to + * receive signals. + */ + ActiveMonitor + ( + WebServer * server, + QWidget * parent = 0, + const char * name = 0 + ); + + virtual ~ActiveMonitor(); + + /** + * @return WebServer object we were given at construction. + */ + WebServer * server(); + + public slots: + + /** + * Look for selected ActiveMonitorItem objects and kill their + * connections immediately. + */ + void slotKillSelected(); + + protected slots: + + /** + * Called when a Server object has been created to handle an incoming + * connection. + * Creates an ActiveMonitorItem and associates it with the server. + * @param server The Server which was created. + */ + void slotConnection(Server * server); + + /** + * Called when a Server object has sent data to the remote client. + * Updates the associated ActiveMonitorItem. + * @param server The Server which is handling the connection. + * @param bytes Number of bytes sent by the server object. + */ + void slotOutput(Server * server, ulong bytes); + + /** + * Called when a Server object has finished all transactions with its + * remote client and is about to die. Marks the associated + * ActiveMonitorItem as defunct, for later culling via slotCull. + * @param server The Server which is handling the connection. + */ + void slotFinished(Server *); + + /** + * Called when a Server object has received a response from its remote + * client. Updates the associated ActiveMonitorItem. + * @param server The Server which is handling the connection. + */ + void slotRequest(Server *); + + /** + * Called when a Server object has sent a response to its remote + * client. Updates the associated ActiveMonitorItem. + * @param server The Server which is handling the connection. + */ + void slotResponse(Server *); + + /** + * Called periodically to remove ActiveMonitorItem objects which are no + * longer associated with a Server object. + */ + void slotCull(); + + /** + * Connected to the relevant signal of the contained QListView and used + * to update the enabled/disabled state of the button which allows + * killing connections. + */ + void slotSelectionChanged(); + + protected: + + /** + * Overridden to emit a signal when this window is closed. + */ + virtual void closeEvent(QCloseEvent *); + + signals: + + /** + * Emitted when this window is closed. + */ + void dying(ActiveMonitor *); + + /** + * Emitted when the selection of the contained QListView has changed. + * @param selectionExists true if there is a selection. + */ + void selection(bool selectionExists); + + private: + + QListView * view_; + WebServer * server_; + + QMap<Server *, ActiveMonitorItem *> itemMap_; + + QTimer cullTimer_; + }; + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ActiveMonitorItem.cpp b/kpf/src/ActiveMonitorItem.cpp new file mode 100644 index 00000000..1cc8fdcb --- /dev/null +++ b/kpf/src/ActiveMonitorItem.cpp @@ -0,0 +1,199 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qpainter.h> +#include <kiconloader.h> + +#include "Defines.h" +#include "ActiveMonitorItem.h" +#include "Server.h" +#include "Utils.h" + +namespace KPF +{ + ActiveMonitorItem::ActiveMonitorItem(Server * server, QListView * parent) + : QListViewItem (parent), + server_ (server), + size_ (0), + sent_ (0) + { + setText(Host, server_->peerAddress().toString()); + setText(Resource, "..."); + setText(Response, "..."); + setText(Size, "..."); + setText(Sent, "..."); + + updateState(); + } + + ActiveMonitorItem::~ActiveMonitorItem() + { + // Empty. + } + + void + ActiveMonitorItem::paintCell + ( + QPainter * p, + const QColorGroup & g, + int c, + int w, + int a + ) + { + if (c != Progress) + { + QListViewItem::paintCell(p, g, c, w, a); + return; + } + + p->setPen(g.dark()); + + p->setPen(g.base()); + + p->drawRect(0, 0, w, height()); + + int maxBarLength = w - 4; + + int barLength = maxBarLength; + + if (0 != size_) + barLength = int((sent_ / double(size_)) * maxBarLength); + + p->fillRect(2, 2, barLength, height() - 4, g.highlight()); + } + + int + ActiveMonitorItem::width + ( + const QFontMetrics & fm, + const QListView * lv, + int c + ) const + { + switch (c) + { + case Status: + return 16; + break; + + case Progress: + return 32; + break; + + default: + return QListViewItem::width(fm, lv, c); + break; + } + } + + void + ActiveMonitorItem::updateState() + { + if (0 != server_) + { + switch (server_->state()) + { + case Server::WaitingForRequest: + setPixmap(Status, SmallIcon("connect_creating")); + break; + + case Server::WaitingForHeaders: + setPixmap(Status, SmallIcon("connect_creating")); + break; + + case Server::Responding: + setPixmap(Status, SmallIcon("connect_established")); + break; + + case Server::Finished: + setPixmap(Status, SmallIcon("connect_no")); + break; + } + } + } + + Server * + ActiveMonitorItem::server() + { + return server_; + } + + QDateTime + ActiveMonitorItem::death() const + { + return death_; + } + + void + ActiveMonitorItem::request() + { + if (0 != server_) + { + setText(Resource, server_->request().path()); + updateState(); + } + } + + void + ActiveMonitorItem::response() + { + if (0 != server_) + { + setText(Response, translatedResponseName(server_->response().code())); + + size_ = server_->response().size(); + + setText(Size, QString::number(size_)); + + updateState(); + } + } + + void + ActiveMonitorItem::output(ulong l) + { + if (0 != server_) + { + sent_ += l; + setText(Sent, QString::number(sent_)); + updateState(); + repaint(); + } + } + + void + ActiveMonitorItem::finished() + { + if (0 != server_) + { + death_ = server_->death(); + updateState(); + } + + server_ = 0L; + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ActiveMonitorItem.h b/kpf/src/ActiveMonitorItem.h new file mode 100644 index 00000000..99cc13db --- /dev/null +++ b/kpf/src/ActiveMonitorItem.h @@ -0,0 +1,144 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_ACTIVE_MONITOR_ITEM_H +#define KPF_ACTIVE_MONITOR_ITEM_H + +#include <qlistview.h> +#include <qdatetime.h> +#include <qfontmetrics.h> +#include <qpalette.h> + +class QPainter; + +namespace KPF +{ + class Server; + + /** + * Used to display the status of a Server object. + * Created and managed by an ActiveMonitor object. + * Provides some textual information, including the requested filename + * and the response code, plus a simple graph displaying the data transfer + * progress of any response. + */ + class ActiveMonitorItem : public QListViewItem + { + public: + + enum Column + { + Status, + Progress, + Size, + Sent, + Response, + Resource, + Host + }; + + /** + * @param server the associated Server object. + */ + ActiveMonitorItem(Server * server, QListView * parent); + virtual ~ActiveMonitorItem(); + + /** + * @return the Server object passed on construction. + */ + Server * server(); + + /** + * Called by the controlling ActiveMonitor object when the associated + * Server object has received a request from its remote client. Queries + * the Server object for the Request object. + * + * May be called more than once, if a Server object handles more than + * one request (i.e. is working in `persistent' mode.) + */ + void request(); + + /** + * Called by the controlling ActiveMonitor object when the associated + * Server object has sent a response to its remote client. Queries the + * Server object for the Response object. + * + * May be called more than once, if a Server object handles more than + * one request (i.e. is working in `persistent' mode.) + */ + void response(); + + /** + * Called by the controlling ActiveMonitor object when the associated + * Server object has sent data to its remote client. + * + * This is called every time output is sent, with the total output + * since the request began. + */ + void output(ulong); + + /** + * Called by the controlling ActiveMonitor object when the associated + * Server object has completed all transactions with its remote client. + */ + void finished(); + + /** + * @return the time of death (end of transactions with remote client) + * of the associated Server object. + */ + QDateTime death() const; + + protected: + + /** + * Updates the display to reflect the current state of the connection + * held by the associated Server object. + */ + virtual void updateState(); + + /** + * Overridden to provide for drawing a graph in the cell which displays + * the number of bytes sent to the remote client by the associated + * Server object. + */ + virtual void paintCell(QPainter *, const QColorGroup &, int, int, int); + + /** + * Overridden to provide for giving reasonable sizes for columns which + * do not contain text. + */ + virtual int width(const QFontMetrics &, const QListView *, int) const; + + private: + + Server * server_; + QDateTime death_; + ulong size_; + ulong sent_; + }; + +} // End namespace KPF + +#endif // KPF_ACTIVE_MONITOR_ITEM_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ActiveMonitorWindow.cpp b/kpf/src/ActiveMonitorWindow.cpp new file mode 100644 index 00000000..ae44e4be --- /dev/null +++ b/kpf/src/ActiveMonitorWindow.cpp @@ -0,0 +1,90 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <kaction.h> +#include <klocale.h> + +#include "ActiveMonitor.h" +#include "ActiveMonitorWindow.h" +#include "ActiveMonitorWindow.moc" +#include "WebServer.h" + +namespace KPF +{ + ActiveMonitorWindow::ActiveMonitorWindow + ( + WebServer * server, + QWidget * parent, + const char * name + ) + : KMainWindow(parent, name) + { + setCaption(i18n("Monitoring %1 - kpf").arg(server->root())); + + monitor_ = new ActiveMonitor(server, this, "ActiveMonitor"); + + setCentralWidget(monitor_); + + killAction_ = + new KAction + ( + i18n("&Cancel Selected Transfers"), + "stop", + 0, + monitor_, + SLOT(slotKillSelected()), + actionCollection(), + "kill" + ); + + killAction_->setEnabled(false); + + killAction_->plug(toolBar()); + } + + ActiveMonitorWindow::~ActiveMonitorWindow() + { + // Empty. + } + + WebServer * + ActiveMonitorWindow::server() + { + return monitor_->server(); + } + + void + ActiveMonitorWindow::slotMayKill(bool b) + { + killAction_->setEnabled(b); + } + + void + ActiveMonitorWindow::closeEvent(QCloseEvent *) + { + emit(dying(this)); + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ActiveMonitorWindow.h b/kpf/src/ActiveMonitorWindow.h new file mode 100644 index 00000000..eddf1113 --- /dev/null +++ b/kpf/src/ActiveMonitorWindow.h @@ -0,0 +1,104 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_ACTIVE_MONITOR_WINDOW_H +#define KPF_ACTIVE_MONITOR_WINDOW_H + +#include <kmainwindow.h> + +class KAction; + +namespace KPF +{ + class ActiveMonitor; + class WebServer; + + /** + * Wraps an ActiveMonitor (widget) in a toplevel window. + * + * A wrapper window is used to avoid forcing ActiveMonitor to be + * toplevel, so it can be used elsewhere if desired. + */ + class ActiveMonitorWindow : public KMainWindow + { + Q_OBJECT + + public: + + /** + * @param server WebServer which we should connect to in order to + * receive signals. + */ + ActiveMonitorWindow + ( + WebServer * server, + QWidget * parent = 0, + const char * name = 0 + ); + + virtual ~ActiveMonitorWindow(); + + /** + * @return WebServer object we were given at construction. + */ + WebServer * server(); + + protected slots: + + /** + * Connected to ActiveMonitor::selection, which tells us whether we + * should enable the 'kill connection' action. + */ + void slotMayKill(bool); + + protected: + + /** + * Overridden to emit a signal when this window is closed. + */ + virtual void closeEvent(QCloseEvent *); + + signals: + + /** + * Emitted when this window is closed. + */ + void dying(ActiveMonitorWindow *); + + /** + * Emitted when the selection of the contained QListView has changed. + * @param selectionExists true if there is a selection. + */ + void selection(bool selectionExists); + + private: + + ActiveMonitor * monitor_; + + KAction * killAction_; + }; + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Applet.cpp b/kpf/src/Applet.cpp new file mode 100644 index 00000000..edd7a652 --- /dev/null +++ b/kpf/src/Applet.cpp @@ -0,0 +1,488 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qpainter.h> +#include <qtimer.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qtoolbutton.h> +#include <qpopupmenu.h> +#include <qfileinfo.h> +#include <qcursor.h> + +#include <dcopclient.h> +#include <kiconloader.h> +#include <kglobal.h> +#include <kconfig.h> +#include <kaboutapplication.h> +#include <kaboutdata.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <kurldrag.h> + +#include "System.h" +#include "Defines.h" +#include "Applet.h" +#include "AppletItem.h" +#include "WebServerManager.h" +#include "WebServer.h" +#include "ServerWizard.h" + +static const char kpfVersion[] = "1.0.1"; + +extern "C" +{ + KDE_EXPORT KPanelApplet * + init(QWidget * parent, const QString & configFile) + { + if (0 == kpf::userId() || 0 == kpf::effectiveUserId()) + { + // Don't run as root. + KMessageBox::detailedError + ( 0, + i18n("You cannot run KPF as root."), + i18n("Running as root exposes the whole system to " + "external attackers."), + i18n("Running as root.") + ); + return NULL; + } + else + { + kpf::blockSigPipe(); + + KGlobal::locale()->insertCatalogue("kpf"); + + return new KPF::Applet + ( + configFile, + KPanelApplet::Normal, + KPanelApplet::About|KPanelApplet::Help, + parent, + "kpf" + ); + } + } +} + +namespace KPF +{ + Applet::Applet + ( + const QString & configFile, + Type type, + int actions, + QWidget * parent, + const char * name + ) + : KPanelApplet (configFile, type, actions, parent, name), + wizard_ (0L), + popup_ (0L), + dcopClient_ (0L) + { + setAcceptDrops(true); + + //setFrameStyle(QFrame::Panel | QFrame::Sunken); + //setLineWidth(1); + + connect + ( + WebServerManager::instance(), + SIGNAL(serverCreated(WebServer *)), + SLOT(slotServerCreated(WebServer *)) + ); + + connect + ( + WebServerManager::instance(), + SIGNAL(serverDisabled(WebServer *)), + SLOT(slotServerDisabled(WebServer *)) + ); + + WebServerManager::instance()->loadConfig(); + + popup_ = new QPopupMenu(this); + + popup_->insertItem + (BarIcon("filenew"), i18n("New Server..."), NewServer, NewServer); + +// popup_->insertItem +// (BarIcon("quit"), i18n("Quit"), Quit, Quit); + + dcopClient_ = new DCOPClient; + dcopClient_->registerAs("kpf", false); + } + + Applet::~Applet() + { + delete dcopClient_; + WebServerManager::instance()->shutdown(); + } + + int + Applet::widthForHeight(int h) const + { + uint serverCount = itemList_.count(); + + if (0 == serverCount) + serverCount = 1; + + if (Vertical == orientation()) + return h / serverCount; + else + return h * serverCount; + } + + int + Applet::heightForWidth(int w) const + { + uint serverCount = itemList_.count(); + + if (0 == serverCount) + serverCount = 1; + + if (Vertical == orientation()) + return w * serverCount; + else + return w / serverCount; + } + + void + Applet::help() + { + kapp->invokeHelp( QString::null, "kpf" ); + } + + void + Applet::about() + { + KAboutData about + ( + "kpf", + I18N_NOOP("kpf"), + kpfVersion, + I18N_NOOP("KDE public fileserver"), + KAboutData::License_Custom, + "(C) 2001 Rik Hemsley (rikkus) <rik@kde.org>", + I18N_NOOP( + "File sharing applet, using the HTTP (Hyper Text Transfer Protocol)" + " standard to serve files." + ), + "http://rikkus.info/kpf.html" + ); + + about.setLicenseText + ( + I18N_NOOP + ( +"Permission is hereby granted, free of charge, to any person obtaining a copy\n" +"of this software and associated documentation files (the \"Software\"), to\n" +"deal in the Software without restriction, including without limitation the\n" +"rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n" +"sell copies of the Software, and to permit persons to whom the Software is\n" +"furnished to do so, subject to the following conditions:\n" +"\n" +"The above copyright notice and this permission notice shall be included in\n" +"all copies or substantial portions of the Software.\n" +"\n" +"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" +"IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" +"FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n" +"AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n" +"ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n" +"WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" + ) + ); + + KAboutApplication a(&about, this); + a.exec(); + } + + void + Applet::orientationChange(Orientation) + { + resetLayout(); + } + + void + Applet::resizeEvent(QResizeEvent *) + { + resetLayout(); + } + + void + Applet::moveEvent(QMoveEvent *) + { + QPtrListIterator<AppletItem> it(itemList_); + + for (uint i = 0; it.current(); ++it, ++i) + it.current()->setBackground(); + } + + void + Applet::resetLayout() + { + if (0 == itemList_.count()) + return; + + switch (orientation()) + { + case Vertical: + { + uint itemHeight = height() / itemList_.count(); + + QPtrListIterator<AppletItem> it(itemList_); + + for (uint i = 0; it.current(); ++it, ++i) + { + it.current()->resize(width(), itemHeight); + it.current()->move(0, i * itemHeight); + } + } + break; + + case Horizontal: + { + uint itemWidth = width() / itemList_.count(); + + QPtrListIterator<AppletItem> it(itemList_); + + for (uint i = 0; it.current(); ++it, ++i) + { + it.current()->resize(itemWidth, height()); + it.current()->move(i * itemWidth, 0); + } + } + default: + break; + } + } + + void + Applet::mousePressEvent(QMouseEvent * ev) + { + if (Qt::RightButton != ev->button() && Qt::LeftButton != ev->button()) + return; + + switch (popup_->exec(QCursor::pos())) + { + case NewServer: + slotNewServer(); + break; + + case Quit: + slotQuit(); + break; + + default: + break; + } + } + + void + Applet::slotNewServerAtLocation(const QString & location) + { + if (0 != wizard_) + { + wizard_->setLocation(location); + wizard_->show(); + } + + else + { + wizard_ = new ServerWizard; + + connect + ( + wizard_, + SIGNAL(dying(ServerWizard *)), + SLOT(slotWizardDying(ServerWizard *)) + ); + + wizard_->setLocation(location); + wizard_->show(); + } + } + + void + Applet::slotNewServer() + { + if (0 != wizard_) + wizard_->show(); + + else + { + wizard_ = new ServerWizard; + + connect + ( + wizard_, + SIGNAL(dying(ServerWizard *)), + SLOT(slotWizardDying(ServerWizard *)) + ); + + wizard_->show(); + } + } + + void + Applet::slotWizardDying(ServerWizard * wiz) + { + if (QDialog::Accepted == wiz->result()) + { + WebServerManager::instance()->createServerLocal + ( + wiz->root(), + wiz->listenPort(), + wiz->bandwidthLimit(), + wiz->connectionLimit(), + Config::DefaultFollowSymlinks, + wiz->serverName() + ); + } + + delete wizard_; + wizard_ = 0; + } + + void + Applet::drawContents(QPainter * p) + { + QPixmap px; + + if (width() > 48) + px = KGlobal::iconLoader()->loadIcon("kpf", KIcon::Panel, 48); + else if (width() > 32) + px = KGlobal::iconLoader()->loadIcon("kpf", KIcon::Panel, 32); + else if (width() > 16) + px = KGlobal::iconLoader()->loadIcon("kpf", KIcon::Panel, 16); + else + return; + + QRect r(contentsRect()); + + p->drawPixmap + ( + r.x() + r.width() / 2 - px.width() / 2, + r.y() + r.height() / 2 - px.height() / 2, + px + ); + } + + void + Applet::dragEnterEvent(QDragEnterEvent * e) + { + KURL::List l; + + if (!KURLDrag::decode(e, l)) + return; + + if (l.count() != 1) + return; + + const KURL &url = l[0]; + + if (!url.isLocalFile() || !QFileInfo(url.path()).isDir()) + return; + + e->accept(); + } + + void + Applet::dropEvent(QDropEvent * e) + { + KURL::List l; + + if (!KURLDrag::decode(e, l)) + return; + + if (l.count() != 1) + return; + + const KURL &url = l[0]; + + if (!url.isLocalFile() || !QFileInfo(url.path()).isDir()) + return; + + e->accept(); + + slotNewServerAtLocation(url.path()); + } + + void + Applet::slotServerCreated(WebServer * server) + { + AppletItem * i = new AppletItem(server, this); + + connect + ( + i, + SIGNAL(newServer()), + SLOT(slotNewServer()) + ); + + connect + ( + i, + SIGNAL(newServerAtLocation(const QString &)), + SLOT(slotNewServerAtLocation(const QString &)) + ); + + itemList_.append(i); + i->show(); + emit(updateLayout()); + resetLayout(); + } + + void + Applet::slotServerDisabled(WebServer * server) + { + QPtrListIterator<AppletItem> it(itemList_); + + for (; it.current(); ++it) + { + AppletItem * i = it.current(); + + if (i->server() == server) + { + itemList_.removeRef(i); + delete i; + emit(updateLayout()); + resetLayout(); + return; + } + } + } + + void + Applet::slotQuit() + { + // How ? + } + +} // End namespace KPF + +#include "Applet.moc" + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Applet.h b/kpf/src/Applet.h new file mode 100644 index 00000000..15802a37 --- /dev/null +++ b/kpf/src/Applet.h @@ -0,0 +1,180 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_APPLET_H +#define KPF_APPLET_H + +#include <qptrlist.h> +#include <kpanelapplet.h> + +class QPopupMenu; +class QPainter; +class DCOPClient; + +namespace KPF +{ + class AppletItem; + class ServerWizard; + class WebServer; + + /** + * Main `application' class, providing an implementation of KPanelApplet + * and managing AppletItem objects. Also provides a popup (context) menu of + * its own, to allow the user to add WebServer (and therefore AppletItem) + * objects. + */ + class Applet : public KPanelApplet + { + Q_OBJECT + + public: + + Applet + ( + const QString & configFile, + Type = Normal, + int = 0, + QWidget * = 0, + const char * = 0 + ); + + ~Applet(); + + /** + * Overridden to give correct sizing according to orientation and number + * of contains AppletItem objects. + */ + virtual int widthForHeight(int h) const; + + /** + * Overridden to give correct sizing according to orientation and number + * of contains AppletItem objects. + */ + virtual int heightForWidth(int w) const; + + protected slots: + + /** + * Called to create a new server when the path to the server is already + * known. + */ + void slotNewServerAtLocation(const QString &); + + /** + * Called to create a new server when the path to the server is unknown. + */ + void slotNewServer(); + + /** + * Called when a ServerWizard is about to close. + */ + void slotWizardDying(ServerWizard *); + + /** + * Called when a WebServer object has been created. Creates an + * AppletItem, associates it with the former, and updates the layout. + */ + void slotServerCreated(WebServer *); + + /** + * Called when a WebServer object has been disabled. + * Deletes the associated AppletItem and updates the layout. + */ + void slotServerDisabled(WebServer *); + + /** + * Called when user asks for quit (via popup menu). + */ + void slotQuit(); + + protected: + + /** + * Overridden to display help window + */ + virtual void help(); + + /** + * Overridden to provide an `about' dialog. + */ + virtual void about(); + + /** + * Overridden to keep track of orientation change and update layout + * accordingly. + */ + virtual void orientationChange(Orientation); + + /** + * Overridden to update layout accordingly. + */ + virtual void moveEvent(QMoveEvent *); + virtual void resizeEvent(QResizeEvent *); + + /** + * Overridden to provide a context menu. + */ + virtual void mousePressEvent(QMouseEvent *); + + /** + * Updates the layout, moving AppletItem objects into proper positions. + */ + virtual void resetLayout(); + + /** + * Overridden to provide something other than a blank display when there + * are no existing AppletItem objects contained. + */ + virtual void drawContents(QPainter *); + + /** + * Overridden to allow testing whether the dragged object points to a + * local directory. + */ + virtual void dragEnterEvent(QDragEnterEvent *); + + /** + * Overridden to allow creating a new WebServer when the dropped object + * points to a local directory. + */ + virtual void dropEvent(QDropEvent *); + + private: + + enum + { + NewServer, + Quit + }; + + ServerWizard * wizard_; + QPopupMenu * popup_; + DCOPClient * dcopClient_; + + QPtrList<AppletItem> itemList_; + }; +} + +#endif // KPF_APPLET_H + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/AppletItem.cpp b/kpf/src/AppletItem.cpp new file mode 100644 index 00000000..e605f692 --- /dev/null +++ b/kpf/src/AppletItem.cpp @@ -0,0 +1,385 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qlabel.h> +#include <qlayout.h> +#include <qtimer.h> +#include <qfileinfo.h> +#include <qcursor.h> + +#include <kiconloader.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <kurldrag.h> +#include <kapplication.h> + +#include "Defines.h" +#include "AppletItem.h" +#include "WebServerManager.h" +#include "WebServer.h" +#include "BandwidthGraph.h" +#include "ActiveMonitorWindow.h" +#include "SingleServerConfigDialog.h" + +namespace KPF +{ + AppletItem::AppletItem(WebServer * server, QWidget * parent) + : QWidget (parent, "KPF::AppletItem"), + server_ (server), + configDialog_ (0L), + monitorWindow_ (0L), + graph_ (0L), + popup_ (0L) + { + setBackgroundOrigin(AncestorOrigin); + setAcceptDrops(true); + + graph_ = new BandwidthGraph(server_, BandwidthGraph::UseOverlays, this); + + graph_->setAcceptDrops(true); + + graph_->installEventFilter(this); + + (new QVBoxLayout(this))->addWidget(graph_); + + QString popupTitle(i18n("kpf - %1").arg(server_->root())); + + popup_ = new KPopupMenu(this); + + popup_->insertTitle + (SmallIcon("kpf"), popupTitle, Title, Title); + + popup_->insertItem + (SmallIcon("filenew"), i18n("New Server..."), NewServer, NewServer); + + popup_->insertSeparator(Separator); + + popup_->insertItem + (SmallIcon("viewmag"), i18n("Monitor"), Monitor, Monitor); + + popup_->insertItem + (SmallIcon("configure"), i18n("Preferences..."), Configure, Configure); + + popup_->insertItem + (SmallIcon("remove"), i18n("Remove"), Remove, Remove); + + popup_->insertItem + (SmallIcon("reload"), i18n("Restart"), Restart, Restart); + + popup_->insertItem + (SmallIcon("player_pause"), i18n("Pause"), Pause, Pause); + + monitorWindow_ = new ActiveMonitorWindow(server_); + + connect + ( + monitorWindow_, + SIGNAL(dying(ActiveMonitorWindow *)), + SLOT(slotActiveMonitorWindowDying(ActiveMonitorWindow *)) + ); + } + + AppletItem::~AppletItem() + { + delete configDialog_; + configDialog_ = 0; + + delete monitorWindow_; + monitorWindow_ = 0; + } + + void AppletItem::setBackground() + { + QResizeEvent e(size(), size()); + kapp->sendEvent(graph_, &e); + graph_->update(); + } + + bool + AppletItem::eventFilter(QObject *, QEvent * ev) + { + switch (ev->type()) + { + + case QEvent::MouseButtonRelease: + { + QMouseEvent * e = static_cast<QMouseEvent *>(ev); + + if (0 == e) + { + kpfDebug + << "Hmm, should have been able to static_cast here." << endl; + break; + } + + if (!rect().contains(e->pos())) + { + break; + } + + if (Qt::LeftButton == e->button()) + { + // Show monitor. + + if (0 == monitorWindow_) + monitorServer(); + + else + { + if (monitorWindow_->isVisible()) + monitorWindow_->hide(); + else + monitorWindow_->show(); + } + } + + return true; + } + break; + + case QEvent::MouseButtonPress: + { + QMouseEvent * e = static_cast<QMouseEvent *>(ev); + + if (0 == e) + { + kpfDebug + << "Hmm, should have been able to static_cast here." << endl; + break; + } + + if (Qt::RightButton != e->button() && Qt::LeftButton != e->button()) + break; + + if (server_->paused()) + popup_->changeItem + (Pause, SmallIcon("1rightarrow"), i18n("Unpause")); + else + popup_->changeItem + (Pause, SmallIcon("player_pause"), i18n("Pause")); + + switch (popup_->exec(QCursor::pos())) + { + case NewServer: + emit(newServer()); + break; + + case Monitor: + monitorServer(); + break; + + case Configure: + configureServer(); + break; + + case Remove: + removeServer(); + break; + + case Restart: + restartServer(); + break; + + case Pause: + pauseServer(); + break; + + default: + break; + } + + return true; + } + break; + + case QEvent::DragEnter: + { + QDragEnterEvent * e = static_cast<QDragEnterEvent *>(ev); + + if (0 == e) + { + kpfDebug + << "Hmm, should have been able to static_cast here." << endl; + break; + } + + KURL::List l; + + if (!KURLDrag::decode(e, l)) + break; + + if (l.count() != 1) + break; + + const KURL &url = l[0]; + + if (!url.isLocalFile() || !QFileInfo(url.path()).isDir()) + break; + + e->accept(); + return true; + } + break; + + case QEvent::Drop: + { + QDropEvent * e = static_cast<QDropEvent *>(ev); + + if (0 == e) + { + kpfDebug + << "Hmm, should have been able to static_cast here." << endl; + break; + } + + KURL::List l; + + if (!KURLDrag::decode(e, l)) + break; + + if (l.count() != 1) + break; + + const KURL &url = l[0]; + + if (!url.isLocalFile() || !QFileInfo(url.path()).isDir()) + break; + + e->accept(); + emit(newServerAtLocation(url.path())); + return true; + } + break; + + default: + break; + } + + return false; + } + + void + AppletItem::slotActiveMonitorWindowDying(ActiveMonitorWindow *) + { + // We used to delete it here, but let's not. See if this is a CPU hog. +#if 0 + delete monitorWindow_; + monitorWindow_ = 0; +#endif + monitorWindow_->hide(); + } + + void + AppletItem::slotConfigDialogDying(SingleServerConfigDialog *) + { + graph_->setTooltip(); + + configDialog_->delayedDestruct(); + configDialog_ = 0; + } + + void + AppletItem::slotNewServer() + { + emit(newServer()); + } + + void + AppletItem::monitorServer() + { + // We used to delete it here, but let's not. See if this is a CPU hog. +#if 0 + if (0 != monitorWindow_) + { + monitorWindow_->show(); + return; + } + + monitorWindow_ = new ActiveMonitorWindow(server_); + + connect + ( + monitorWindow_, + SIGNAL(dying(ActiveMonitorWindow *)), + SLOT(slotActiveMonitorWindowDying(ActiveMonitorWindow *)) + ); +#endif + + monitorWindow_->show(); + monitorWindow_->raise(); + } + + void + AppletItem::removeServer() + { + QTimer::singleShot(0, this, SLOT(slotSuicide())); + } + + void + AppletItem::configureServer() + { + if (0 != configDialog_) + { + configDialog_->show(); + return; + } + + configDialog_ = new SingleServerConfigDialog(server_, 0); + + connect + ( + configDialog_, + SIGNAL(dying(SingleServerConfigDialog *)), + SLOT(slotConfigDialogDying(SingleServerConfigDialog *)) + ); + + configDialog_->show(); + } + + void + AppletItem::slotSuicide() + { + WebServerManager::instance()->disableServer(server_->root()); + } + + void + AppletItem::restartServer() + { + server_->restart(); + } + + void + AppletItem::pauseServer() + { + server_->pause(!server_->paused()); + } + + WebServer * + AppletItem::server() + { + return server_; + } +} + +#include "AppletItem.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/AppletItem.h b/kpf/src/AppletItem.h new file mode 100644 index 00000000..013a6b0a --- /dev/null +++ b/kpf/src/AppletItem.h @@ -0,0 +1,167 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_APPLET_ITEM_H +#define KPF_APPLET_ITEM_H + +#include <qptrlist.h> +#include <qwidget.h> + +class KPopupMenu; + +namespace KPF +{ + class ConfigDialog; + class BandwidthGraph; + class ActiveMonitorWindow; + class SingleServerConfigDialog; + class WebServer; + + /** + * Provides control of, and coarse-grained activity display for, a WebServer + * object. Contains a BandwidthGraph widget and provides a context menu + * which allows WebServer object control, plus creation of a new WebServer, + * for user convenience. + */ + class AppletItem : public QWidget + { + Q_OBJECT + + public: + + /** + * @param server The WebServer object which will be monitored and + * controlled by this object. + */ + AppletItem(WebServer * server, QWidget * parent); + + ~AppletItem(); + + /** + * @return the WebServer object given on construction. + */ + WebServer * server(); + + void setBackground(); + + protected slots: + + /** + * Called when an ActiveMonitorWindow (created by this object) is + * about to close. + */ + void slotActiveMonitorWindowDying(ActiveMonitorWindow *); + + /** + * Called when a SingleServerConfigDialog (created by this object) is + * about to close. + */ + void slotConfigDialogDying(SingleServerConfigDialog *); + + /** + * Called when the user requests a new WebServer via the context menu. + */ + void slotNewServer(); + + /** + * Called by a timer after removeServer has been called and the event + * loop has been processed once. + */ + void slotSuicide(); + + signals: + + /** + * Emitted when a new WebServer is requested from the context menu. + */ + void newServer(); + + /** + * Emitted when an URL pointing to a local directory has been dropped. + */ + void newServerAtLocation(const QString &); + + protected: + + /** + * Overridden to provide a context menu plus DnD capabilities. + */ + bool eventFilter(QObject *, QEvent *); + + /** + * Called when the appropriate item is selected from the context menu. + * Creates an ActiveMonitorWindow. + */ + void monitorServer (); + + /** + * Called when the appropriate item is selected from the context menu. + * Asks the WebServerManager instance to remove the associated + * WebServer. + */ + void removeServer (); + + /** + * Called when the appropriate item is selected from the context menu. + * Creates a configuration dialog for the associated WebServer. + */ + void configureServer (); + + /** + * Called when the appropriate item is selected from the context menu. + * Restarts the associated WebServer. + */ + void restartServer (); + + /** + * Called when the appropriate item is selected from the context menu. + * Pauses the associated WebServer. + */ + void pauseServer (); + + private: + + enum + { + Title, + NewServer, + Separator, + Monitor, + Configure, + Remove, + Restart, + Pause + }; + + + WebServer * server_; + SingleServerConfigDialog * configDialog_; + ActiveMonitorWindow * monitorWindow_; + BandwidthGraph * graph_; + KPopupMenu * popup_; + }; +} + +#endif // KPF_APPLET_H + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/BandwidthGraph.cpp b/kpf/src/BandwidthGraph.cpp new file mode 100644 index 00000000..a7f6c311 --- /dev/null +++ b/kpf/src/BandwidthGraph.cpp @@ -0,0 +1,335 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qdrawutil.h> +#include <qpainter.h> +#include <qtooltip.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <kiconeffect.h> + +#include "Defines.h" +#include "Utils.h" +#include "BandwidthGraph.h" +#include "WebServer.h" + +namespace KPF +{ + BandwidthGraph::BandwidthGraph + ( + WebServer * server, + OverlaySelect overlaySelect, + QWidget * parent, + const char * name + ) + : QWidget (parent, name, WRepaintNoErase), + server_ (server), + max_ (0L), + overlaySelect_ (overlaySelect) + { + setBackgroundOrigin(AncestorOrigin); + history_.resize(width()); + history_.fill(0L); + + connect + ( + server_, + SIGNAL(wholeServerOutput(ulong)), + SLOT(slotOutput(ulong)) + ); + + if (UseOverlays == overlaySelect_) + { + connect + ( + server_, + SIGNAL(contentionChange(bool)), this, + SLOT(slotServerContentionChange(bool)) + ); + + connect + ( + server_, + SIGNAL(pauseChange(bool)), this, + SLOT(slotServerPauseChange(bool)) + ); + } + + setTooltip(); + } + + BandwidthGraph::~BandwidthGraph() + { + // Empty. + } + + void + BandwidthGraph::setTooltip() + { + QToolTip::add(this, i18n( "%1 on port %2" ) + .arg( server_->root() ).arg( server_->listenPort() ) ); + } + + QRect + BandwidthGraph::contentsRect() const + { + return QRect(1, 1, width() - 2, height() - 2); + } + + void + BandwidthGraph::updateContents() + { + QRect r(contentsRect()); + + uint w = r.width(); + uint h = r.height(); + + buffer_.fill(this, 0, 0); + + QPainter p(&buffer_); + + p.drawPixmap( ( width()-bgPix_.width() )/2, + ( height()-bgPix_.height() )/2, bgPix_ ); + + p.setPen(colorGroup().dark()); + + for (uint i = 0; i < history_.size(); i++) + { + ulong l = history_[i]; + + if (0 != l) + { + uint barLength = + static_cast<uint>(l / float(max_) * h); + + p.drawLine(i + 1, h, i + 1, h - barLength); + } + } + + drawOverlays(p); + + update(); + } + + void + BandwidthGraph::paintEvent(QPaintEvent * e) + { + bitBlt(this, e->rect().topLeft(), &buffer_, e->rect()); + } + + void + BandwidthGraph::resizeEvent(QResizeEvent *) + { + buffer_.resize(size()); + + if ( width() > 48 ) + bgPix_ = KGlobal::iconLoader()->loadIcon( "kpf", KIcon::Panel, 48 ); + else if ( width() > 32 ) + bgPix_ = KGlobal::iconLoader()->loadIcon( "kpf", KIcon::Panel, 32 ); + else if ( width() > 16 ) + bgPix_ = KGlobal::iconLoader()->loadIcon( "kpf", KIcon::Panel, 16 ); + else + bgPix_.fill( this, QPoint( 0, 0 ) ); + + KIconEffect::semiTransparent( bgPix_ ); + + if (width() < 2) + { + // We have 0 space. Make history 0 size. + history_ = QMemArray<ulong>(); + return; + } + + uint w = width() - 2; + + if (w < history_.size()) + { + QMemArray<ulong> newHistory(w); + + uint sizeDiff = history_.size() - w; + + for (uint i = sizeDiff; i < history_.size(); i++) + newHistory[i - sizeDiff] = history_[i]; + + history_ = newHistory; + } + else if (w > history_.size()) + { + QMemArray<ulong> newHistory(w); + + uint sizeDiff = w - history_.size(); + + for (uint i = 0; i < sizeDiff; i++) + { + newHistory[i] = 0L; + } + + for (uint i = 0; i < history_.size(); i++) + newHistory[sizeDiff + i] = history_[i]; + + history_ = newHistory; + } + + updateContents(); + } + + void + BandwidthGraph::slotOutput(ulong l) + { + QRect r(contentsRect()); + + uint w = r.width(); + uint h = r.height(); + + if (0 == w || 0 == h) + return; + + ulong oldMax = max_; + + max_ = 0L; + + if (history_.size() != w) + return; + + for (uint i = 1; i < w; i++) + { + history_[i - 1] = history_[i]; + max_ = max(history_[i], max_); + } + + history_[w - 1] = l; + max_ = max(l, max_); + + if (max_ != oldMax) + emit(maximumChanged(max_)); + + updateContents(); + } + + void + BandwidthGraph::drawOverlays(QPainter & p) + { + if (NoOverlays == overlaySelect_) + return; + + if (!overlayPixmap_.isNull()) + { + p.drawPixmap(3, 3, overlayPixmap_); + } + + if (width() < 32 || height() < 32) + return; + + if (overlayPixmap_.isNull()) + { + QString maxString; + + QString bs(i18n("%1 b/s")); + QString kbs(i18n("%1 kb/s")); + QString mbs(i18n("%1 Mb/s")); + + if (max_ > 1024) + if (max_ > 1024 * 1024) + maxString = mbs.arg(max_ / (1024 * 1024)); + else + maxString = kbs.arg(max_ / 1024); + else if ( max_ > 0 ) + maxString = bs.arg(max_); + else + maxString = i18n( "Idle" ); + + p.setPen(Qt::white); + + p.drawText + ( + 4, + 4 + fontMetrics().ascent(), + maxString + ); + + p.setPen(Qt::black); + + p.drawText + ( + 3, + 3 + fontMetrics().ascent(), + maxString + ); + } + } + + QSize + BandwidthGraph::sizeHint() const + { + return QSize(32, 32); + } + + QSize + BandwidthGraph::minimumSizeHint() const + { + return QSize(12, 12); + } + + WebServer * + BandwidthGraph::server() + { + return server_; + } + + void + BandwidthGraph::slotServerContentionChange(bool) + { + updateOverlayPixmap(); + } + + void + BandwidthGraph::slotServerPauseChange(bool) + { + updateOverlayPixmap(); + } + + void + BandwidthGraph::updateOverlayPixmap() + { + if (server_->paused()) + { + overlayPixmap_ = SmallIcon("player_pause"); + } + else + { + if (server_->portContention()) + { + overlayPixmap_ = SmallIcon("connect_creating"); + } + else + { + overlayPixmap_ = QPixmap(); + } + } + } + +} // End namespace KPF + +#include "BandwidthGraph.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/BandwidthGraph.h b/kpf/src/BandwidthGraph.h new file mode 100644 index 00000000..e0cf51dd --- /dev/null +++ b/kpf/src/BandwidthGraph.h @@ -0,0 +1,163 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_BANDWIDTH_GRAPH_H +#define KPF_BANDWIDTH_GRAPH_H + +#include <qwidget.h> +#include <qmemarray.h> +#include <qpixmap.h> +#include <qrect.h> + +class QPainter; + +namespace KPF +{ + class WebServer; + + /** + * Draws a graph of the bandwidth usage of a WebServer over time. + * May also displays an overlayed icon to show the status of a WebServer, + * i.e. whether it is active (no icon,) paused or in contention for a port. + */ + class BandwidthGraph : public QWidget + { + Q_OBJECT + + public: + + enum OverlaySelect + { + UseOverlays, + NoOverlays + }; + + /** + * @param server WebServer to monitor. + * @param overlaySelect if UseOverlays, draw overlay icons to reflect + * server status. + */ + BandwidthGraph + ( + WebServer * server, + OverlaySelect overlaySelect, + QWidget * parent = 0, + const char * name = 0 + ); + + virtual ~BandwidthGraph(); + + /** + * Set the tooltip showing shared directory name and port + */ + void setTooltip(); + + /** + * Overridden to provide reasonable default size and shape. + */ + virtual QSize sizeHint() const; + + /** + * Overridden to provide reasonable minimum size and shape. + */ + virtual QSize minimumSizeHint() const; + + /** + * @return the WebServer object given on construction. + */ + WebServer * server(); + + protected slots: + + /** + * Connected to associated WebServer to receive notification of output. + */ + void slotOutput(ulong); + + /** + * Connected to associated WebServer to receive notification of port + * contention. + */ + void slotServerContentionChange(bool); + + /** + * Connected to associated WebServer to receive notification of pause + * or unpause. + */ + void slotServerPauseChange(bool); + + protected: + + /** + * Overridden to provide graph drawing. + */ + virtual void paintEvent(QPaintEvent *); + + /** + * Overridden to assist graph drawing. + */ + virtual void resizeEvent(QResizeEvent *); + + /** + * Draw overlay icons to reflect status of associated WebServer. + */ + virtual void drawOverlays(QPainter &); + + /** + * Provides a rectangle in which to draw the graph itself. + */ + virtual QRect contentsRect() const; + + /** + * Called when the status of the associated WebServer changes. + */ + virtual void updateOverlayPixmap(); + + signals: + + /** + * Emitted when the maximum displayed value has changed. + */ + void maximumChanged(ulong); + + private: + + void updateContents(); + + QMemArray<ulong> history_; + + WebServer * server_; + + QPixmap buffer_, bgPix_; + + ulong max_; + + OverlaySelect overlaySelect_; + + QPixmap overlayPixmap_; + }; + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ByteRange.cpp b/kpf/src/ByteRange.cpp new file mode 100644 index 00000000..cdd0624b --- /dev/null +++ b/kpf/src/ByteRange.cpp @@ -0,0 +1,176 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qstringlist.h> + +#include "Defines.h" +#include "ByteRange.h" + +namespace KPF +{ + ByteRange::ByteRange() + : first_ (0L), + last_ (0L), + haveLast_ (false) + { + // Empty. + } + + ByteRange::ByteRange(ulong first) + : first_ (first), + last_ (0L), + haveLast_ (false) + { + // Empty. + } + + + ByteRange::ByteRange(ulong first, ulong last) + : first_ (first), + last_ (last), + haveLast_ (true) + { + } + + ulong + ByteRange::first() const + { + return first_; + } + + ulong + ByteRange::last() const + { + return last_; + } + + bool + ByteRange::haveLast() const + { + return haveLast_; + } + + void + ByteRange::setFirst(ulong l) + { + first_ = l; + } + + void + ByteRange::setLast(ulong l) + { + last_ = l; + haveLast_ = true; + } + + bool + ByteRange::valid() const + { + return haveLast_ ? (first_ < last_) : true; + } + + void + ByteRange::clear() + { + first_ = last_ = 0L; + haveLast_ = false; + } + + ByteRangeList::ByteRangeList() + { + // Empty. + } + + ByteRangeList::ByteRangeList(const QString & _s, float /* protocol */) + { + kpfDebug << "ByteRangeList parsing `" << _s << "'" << endl; + + // Hey, parsing time :) + + QString s(_s); + + if ("bytes=" == s.left(6)) + { + s.remove(0, 6); + s = s.stripWhiteSpace(); + } + + QStringList byteRangeSpecList(QStringList::split(',', s)); + + QStringList::ConstIterator it; + + for (it = byteRangeSpecList.begin(); it != byteRangeSpecList.end(); ++it) + addByteRange(*it); + } + + void + ByteRangeList::addByteRange(const QString & s) + { + kpfDebug << "addByteRange(" << s << ")" << endl; + + int dashPos = s.find('-'); + + if (-1 == dashPos) + { + kpfDebug << "No dash" << endl; + return; + } + + QString firstByte(s.left(dashPos).stripWhiteSpace()); + + QString lastByte(s.mid(dashPos + 1).stripWhiteSpace()); + + ulong first; + + if (firstByte.isEmpty()) + first = 0L; + else + first = firstByte.toULong(); + + ulong last; + + bool haveLast = !lastByte.isEmpty(); + + if (haveLast) + last = lastByte.toULong(); + else + last = 0L; + + if (haveLast) + { + if (first < last) + { + kpfDebug << "range: " << first << "d - " << last << "d" << endl; + append(ByteRange(first, last)); + } + } + else + { + kpfDebug << "range: " << first << "d - end" << endl; + append(ByteRange(first)); + } + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ByteRange.h b/kpf/src/ByteRange.h new file mode 100644 index 00000000..4dd0b841 --- /dev/null +++ b/kpf/src/ByteRange.h @@ -0,0 +1,128 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_BYTE_RANGE_H +#define KPF_BYTE_RANGE_H + +#include <qstring.h> +#include <qvaluelist.h> + +namespace KPF +{ + /** + * Parse and store an HTTP 'byte range'. + * A range consists of a first and, optionally, a last byte. + * If the last byte is unspecified, it should be assumed to be + * the last byte of the resource being retrieved. + * A valid range has first <= last. + */ + class ByteRange + { + public: + + /** + * Constructs an empty range. + */ + ByteRange(); + + /** + * Constructs a range with the first byte specified and the last + * byte set to 'none'. + */ + ByteRange(ulong first); + + /** + * Constructs a range with the first and last bytes specified. + */ + ByteRange(ulong first, ulong last); + + /** + * @return first byte in range. + */ + ulong first() const; + + /** + * @return last byte in range, if specified. Otherwise undefined. + */ + ulong last() const; + + /** + * @return true if last byte was specified. + */ + bool haveLast() const; + + /** + * Specify the first byte of the range. + */ + void setFirst (ulong l); + + /** + * Specify the last byte of the range. After setting this + * value, haveLast() will return true. + */ + void setLast (ulong l); + + /** + * @return true if first <= last or last is undefined. + */ + bool valid() const; + + /** + * Reset to initial state. + */ + void clear(); + + private: + + ulong first_; + ulong last_; + bool haveLast_; + }; + + /** + * Encapsulates a list of ByteRange. + */ + class ByteRangeList : public QValueList<ByteRange> + { + public: + + ByteRangeList(); + + /** + * Contructs a ByteRangeList from a string, which is parsed. + * @param protocol specifies the HTTP protocol which should + * be assumed whilst parsing. + */ + ByteRangeList(const QString &, float protocol); + + /** + * Parses a byte range represented as a string and, if successful, + * appends the resultant ByteRange object to this list. + */ + void addByteRange(const QString &); + }; + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ConfigDialogPage.cpp b/kpf/src/ConfigDialogPage.cpp new file mode 100644 index 00000000..395bb681 --- /dev/null +++ b/kpf/src/ConfigDialogPage.cpp @@ -0,0 +1,318 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qwhatsthis.h> +#include <qlayout.h> +#include <qspinbox.h> +#include <qlabel.h> +#include <qcheckbox.h> +#include <qlineedit.h> + +#include <klocale.h> +#include <kseparator.h> +#include <kfiledialog.h> + +#include "Defines.h" +#include "ErrorMessageConfigDialog.h" +#include "ConfigDialogPage.h" +#include "WebServerManager.h" +#include "WebServer.h" +#include "Help.h" + +#include <dnssd/servicebrowser.h> + +namespace KPF +{ + ConfigDialogPage::ConfigDialogPage(WebServer * server, QWidget * parent) + : QWidget (parent, "KPF::ConfigDialogPage"), + server_ (server), + errorMessageConfigDialog_ (0L) + { + l_listenPort_ = new QLabel(i18n("&Listen port:"), this); + l_bandwidthLimit_ = new QLabel(i18n("&Bandwidth limit:"), this); +// l_connectionLimit_ = new QLabel(i18n("Connection &limit"), this); + + sb_listenPort_ = new QSpinBox(1, 65535, 1, this); + sb_bandwidthLimit_ = new QSpinBox(1, 999999, 1, this); +// sb_connectionLimit_ = new QSpinBox(1, 9999, 1, this); + + l_serverName_ = new QLabel(i18n("&Server name:"), this); + le_serverName_ = new QLineEdit(this); + + bool canPublish = DNSSD::ServiceBrowser::isAvailable() == DNSSD::ServiceBrowser::Working; + l_serverName_->setEnabled(canPublish); + le_serverName_->setEnabled(canPublish); + + cb_followSymlinks_ = new QCheckBox(i18n("&Follow symbolic links"), this); + +// cb_customErrorMessages_ = +// new QCheckBox(i18n("Use custom error messages"), this); + +// pb_errorMessages_ = new QPushButton(i18n("&Configure..."), this); + +// pb_errorMessages_->setEnabled(false); + + l_listenPort_ ->setBuddy(sb_listenPort_); + l_bandwidthLimit_ ->setBuddy(sb_bandwidthLimit_); + l_serverName_ ->setBuddy(le_serverName_); +// l_connectionLimit_ ->setBuddy(sb_connectionLimit_); + + sb_listenPort_ + ->setValue(WebServerManager::instance()->nextFreePort()); + + sb_bandwidthLimit_ ->setValue(Config::DefaultBandwidthLimit); + sb_bandwidthLimit_ ->setSuffix(i18n(" kB/s")); +// sb_connectionLimit_ ->setValue(Config::DefaultConnectionLimit); + cb_followSymlinks_ ->setChecked(Config::DefaultFollowSymlinks); + + QVBoxLayout * l0 = new QVBoxLayout(this, 0, KDialog::spacingHint()); + + QGridLayout * l2 = new QGridLayout(l0); + + l2->addWidget(l_listenPort_, 0, 0); + l2->addWidget(sb_listenPort_, 0, 1); + l2->addWidget(l_bandwidthLimit_, 1, 0); + l2->addWidget(sb_bandwidthLimit_, 1, 1); + l2->addWidget(l_serverName_, 2, 0); + l2->addWidget(le_serverName_, 2, 1); +// l2->addWidget(l_connectionLimit_, 2, 0); +// l2->addWidget(sb_connectionLimit_, 2, 1); + + l0->addWidget(cb_followSymlinks_); + +#if 0 + QHBoxLayout * l3 = new QHBoxLayout(l0); + + l3->addWidget(cb_customErrorMessages_); + l3->addWidget(pb_errorMessages_); +#endif + + l0->addStretch(1); + +#if 0 + connect + ( + cb_customErrorMessages_, + SIGNAL(toggled(bool)), + SLOT(slotCustomErrorMessagesToggled(bool)) + ); +#endif + +#if 0 + connect + ( + pb_errorMessages_, + SIGNAL(clicked()), + SLOT(slotConfigureErrorMessages()) + ); +#endif + + QString listenPortHelp = + i18n + ( + "<p>" + "Specify the network `port' on which the server should" + " listen for connections." + "</p>" + ); + + QString bandwidthLimitHelp = + i18n + ( + "<p>" + "Specify the maximum amount of data (in kilobytes) that will be" + " sent out per second." + "</p>" + "<p>" + "This allows you to keep some bandwidth for yourself instead" + " of allowing connections with kpf to hog your connection." + "</p>" + ); + + QString connectionLimitHelp = + i18n + ( + "<p>" + "Specify the maximum number of connections allowed at" + " any one time." + "</p>" + ); + + QString followSymlinksHelp = + i18n + ( + "<p>" + "Allow serving of files which have a symbolic link in" + " the path from / to the file, or are a symbolic link" + " themselves." + "</p>" + "<p>" + "<strong>Warning !</strong> This could be a security" + " risk. Use only if you understand the issues involved." + "</p>" + ); + + QString errorMessagesHelp = + i18n + ( + "<p>" + "Specify the text that will be sent upon an error," + " such as a request for a page that does not exist" + " on this server." + "</p>" + ); + + QString serverNameHelp = KPF::HelpText::getServerNameHelp(); + QWhatsThis::add(l_listenPort_, listenPortHelp); + QWhatsThis::add(sb_listenPort_, listenPortHelp); + QWhatsThis::add(l_bandwidthLimit_, bandwidthLimitHelp); + QWhatsThis::add(sb_bandwidthLimit_, bandwidthLimitHelp); +// QWhatsThis::add(l_connectionLimit_, connectionLimitHelp); +// QWhatsThis::add(sb_connectionLimit_, connectionLimitHelp); + QWhatsThis::add(cb_followSymlinks_, followSymlinksHelp); + QWhatsThis::add(l_serverName_, serverNameHelp); + QWhatsThis::add(le_serverName_, serverNameHelp); +// QWhatsThis::add(pb_errorMessages_, errorMessagesHelp); + + connect + ( + sb_listenPort_, + SIGNAL(valueChanged(int)), + SLOT(slotListenPortChanged(int)) + ); + + connect + ( + sb_bandwidthLimit_, + SIGNAL(valueChanged(int)), + SLOT(slotBandwidthLimitChanged(int)) + ); + + connect + ( + cb_followSymlinks_, + SIGNAL(toggled(bool)), + SLOT(slotFollowSymlinksToggled(bool)) + ); + + + load(); + } + + ConfigDialogPage::~ConfigDialogPage() + { + // Empty. + } + + void + ConfigDialogPage::load() + { + sb_listenPort_ ->setValue(server_->listenPort()); + sb_bandwidthLimit_ ->setValue(server_->bandwidthLimit()); +// sb_connectionLimit_ ->setValue(server_->connectionLimit()); + cb_followSymlinks_ ->setChecked(server_->followSymlinks()); + le_serverName_ ->setText(server_->serverName()); +// cb_customErrorMessages_ ->setChecked(server_->customErrorMessages()); + } + + void + ConfigDialogPage::save() + { + server_->setListenPort (sb_listenPort_->value()); + server_->setBandwidthLimit (sb_bandwidthLimit_->value()); +// server_->setConnectionLimit (sb_connectionLimit_->value()); + server_->setFollowSymlinks (cb_followSymlinks_->isChecked()); + server_->setCustomErrorMessages (cb_followSymlinks_->isChecked()); + server_->setServerName (le_serverName_->text()); + } + + void + ConfigDialogPage::slotCustomErrorMessagesToggled(bool) + { +// pb_errorMessages_->setEnabled(b); + } + + void + ConfigDialogPage::slotConfigureErrorMessages() + { + if (0 == errorMessageConfigDialog_) + errorMessageConfigDialog_ = new ErrorMessageConfigDialog(server_, this); + + errorMessageConfigDialog_->show(); + } + + void + ConfigDialogPage::slotListenPortChanged(int) + { + kpfDebug << "slotBandwidthLimitChanged" << endl; + checkOkAndEmit(); + } + + void ConfigDialogPage::checkOk() + { + kpfDebug << "slotBandwidthLimitChanged" << endl; + checkOkAndEmit(); + } + + void ConfigDialogPage::slotBandwidthLimitChanged(int) + { + kpfDebug << "slotBandwidthLimitChanged" << endl; + checkOkAndEmit(); + } + + void ConfigDialogPage::slotFollowSymlinksToggled(bool) + { + kpfDebug << "slotBandwidthLimitChanged" << endl; + checkOkAndEmit(); + } + + void ConfigDialogPage::checkOkAndEmit() + { + int newPort = sb_listenPort_->value(); + + if (newPort <= 1024) + { + emit(ok(false)); + return; + } + + QPtrList<WebServer> + serverList(WebServerManager::instance()->serverListLocal()); + + for (QPtrListIterator<WebServer> it(serverList); it.current(); ++it) + { + if (it.current() == server_) + continue; + + if (it.current()->listenPort() == uint(newPort)) + { + emit(ok(false)); + return; + } + } + + emit(ok(true)); + } +} +#include "ConfigDialogPage.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ConfigDialogPage.h b/kpf/src/ConfigDialogPage.h new file mode 100644 index 00000000..8d41ea83 --- /dev/null +++ b/kpf/src/ConfigDialogPage.h @@ -0,0 +1,107 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_CONFIG_DIALOG_PAGE_H +#define KPF_CONFIG_DIALOG_PAGE_H + +#include <qptrlist.h> +#include <qwidget.h> + +class QLabel; +class QSpinBox; +class QCheckBox; +class QPushButton; +class QLineEdit; + +namespace KPF +{ + class WebServer; + class ErrorMessageConfigDialog; + + /** + * Allows user configuration of a WebServer object. + */ + class ConfigDialogPage : public QWidget + { + Q_OBJECT + + public: + + ConfigDialogPage(WebServer *, QWidget * parent); + + virtual ~ConfigDialogPage(); + + /** + * Read settings from associated WebServer object and update controls. + */ + void load(); + + /** + * Set attributes of associated WebServer object from controls. + */ + void save(); + + void checkOk(); + + protected slots: + + void slotConfigureErrorMessages(); + void slotCustomErrorMessagesToggled(bool); + void slotListenPortChanged(int); + void slotBandwidthLimitChanged(int); + void slotFollowSymlinksToggled(bool); + + protected: + + void checkOkAndEmit(); + + signals: + + void ok(bool); + + private: + + WebServer * server_; + + QLabel * l_listenPort_; + QLabel * l_bandwidthLimit_; + QLabel * l_connectionLimit_; + QLabel * l_serverName_; + + QSpinBox * sb_listenPort_; + QSpinBox * sb_bandwidthLimit_; + QSpinBox * sb_connectionLimit_; + + QCheckBox * cb_followSymlinks_; + + QLineEdit * le_serverName_; + + QCheckBox * cb_customErrorMessages_; + QPushButton * pb_errorMessages_; + + ErrorMessageConfigDialog * errorMessageConfigDialog_; + }; +} + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Defaults.cpp b/kpf/src/Defaults.cpp new file mode 100644 index 00000000..691ca0f2 --- /dev/null +++ b/kpf/src/Defaults.cpp @@ -0,0 +1,97 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "Defines.h" +#include "Defaults.h" + +namespace KPF +{ + namespace Config + { + const uint DefaultListenPort = 8001; + const uint DefaultBandwidthLimit = 4; + const uint DefaultConnectionLimit = 64; + const bool DefaultFollowSymlinks = false; + const bool DefaultCustomErrors = false; + const bool DefaultPaused = false; + const QString& DefaultServername = QString::null; + + static const char Name[] = "kpfappletrc"; + static const char KeyServerRootList[] = "ServerRootList"; + static const char KeyGroupPrefix[] = "Server_"; + static const char KeyListenPort[] = "ListenPort"; + static const char KeyBandwidthLimit[] = "BandwidthLimit"; + static const char KeyConnectionLimit[] = "ConnectionLimit"; + static const char KeyFollowSymlinks[] = "FollowSymlinks"; + static const char KeyCustomErrors[] = "CustomErrors"; + static const char KeyPaused[] = "Paused"; + static const char KeyServerName[] = "ServerName"; + + QString name() + { + return QString::fromUtf8(Name); + } + + QString key(Option o) + { + switch (o) + { + case ServerRootList: + return QString::fromUtf8(KeyServerRootList); + + case GroupPrefix: + return QString::fromUtf8(KeyGroupPrefix); + + case ListenPort: + return QString::fromUtf8(KeyListenPort); + + case BandwidthLimit: + return QString::fromUtf8(KeyBandwidthLimit); + + case ConnectionLimit: + return QString::fromUtf8(KeyConnectionLimit); + + case FollowSymlinks: + return QString::fromUtf8(KeyFollowSymlinks); + + case CustomErrors: + return QString::fromUtf8(KeyCustomErrors); + + case Paused: + return QString::fromUtf8(KeyPaused); + + case ServerName: + return QString::fromUtf8(KeyServerName); + + /* default intentionally left out to have the compiler generate + * warnings in case we add values to the enumeration but forget + * to extend this switch. + */ + } + return QString::null; + } + } // End namespace Config + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Defaults.h b/kpf/src/Defaults.h new file mode 100644 index 00000000..d49339fe --- /dev/null +++ b/kpf/src/Defaults.h @@ -0,0 +1,71 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_DEFAULTS_H +#define KPF_DEFAULTS_H + +#include <qstring.h> + +namespace KPF +{ + /** + * Used to provide a single point of access to config defaults and key names. + */ + namespace Config + { + extern const uint DefaultListenPort; + extern const uint DefaultBandwidthLimit; + extern const uint DefaultConnectionLimit; + extern const bool DefaultFollowSymlinks; + extern const bool DefaultCustomErrors; + extern const bool DefaultPaused; + + enum Option + { + ServerRootList, + GroupPrefix, + ListenPort, + BandwidthLimit, + ConnectionLimit, + FollowSymlinks, + CustomErrors, + Paused, + ServerName + }; + + /** + * Name to be used for configuration file. + */ + QString name(); + + /** + * Config key to use when accessing option. + */ + QString key(Option); + + } // End namespace Config + +} // End namespace KPF + +#endif // KPF_DEFAULTS_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Defines.h b/kpf/src/Defines.h new file mode 100644 index 00000000..5c724e9a --- /dev/null +++ b/kpf/src/Defines.h @@ -0,0 +1,33 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_DEFINES_H +#define KPF_DEFINES_H + +#include <kdebug.h> + +#define kpfDebug kdDebug(5007) << k_lineinfo << k_funcinfo << endl +//#define kpfDebug kndDebug() + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/DirSelectWidget.cpp b/kpf/src/DirSelectWidget.cpp new file mode 100644 index 00000000..bd36ffeb --- /dev/null +++ b/kpf/src/DirSelectWidget.cpp @@ -0,0 +1,115 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qdir.h> +#include <qfileinfo.h> + +#include "DirSelectWidget.h" +#include "DirSelectWidget.moc" + +namespace KPF +{ + class DirSelectWidget::Private + { + public: + + QString pathToMakeVisible; + }; + + DirSelectWidget::DirSelectWidget + ( + const QString & pathToMakeVisible, + QWidget * parent, + const char * name + ) + : KListView(parent, name) + { + d = new Private; + d->pathToMakeVisible = pathToMakeVisible; + + setRootIsDecorated(true); + + connect + ( + this, + SIGNAL(expanded(QListViewItem *)), + SLOT(slotExpanded(QListViewItem *)) + ); + + QListViewItem * root = new QListViewItem(this, "/"); + + root->setExpandable(true); + + startTimer(0); + } + + DirSelectWidget::~DirSelectWidget() + { + delete d; + } + + void + DirSelectWidget::timerEvent(QTimerEvent *) + { + killTimers(); + + if (0 != firstChild()) + firstChild()->setOpen(true); + } + + void + DirSelectWidget::slotExpanded(QListViewItem * item) + { + if (0 != item->firstChild()) + return; + + QString p(path(item)); + + QDir dir(p); + + const QFileInfoList * entryInfoList = + dir.entryInfoList(QDir::Dirs | QDir::Readable); + + for (QFileInfoListIterator it(*entryInfoList); it.current(); ++it) + { + if (it.current()->isDir() && it.current()->isReadable()) + { + QListViewItem * i = new QListViewItem(item, it.current()->fileName()); + i->setExpandable(true); + } + } + } + + QString + DirSelectWidget::path(QListViewItem * item) const + { + QString ret(item->text(0)); + + while (0 != (item = item->parent())) + ret.prepend("/" + item->text(0)); + + return ret; + } +} + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/DirSelectWidget.h b/kpf/src/DirSelectWidget.h new file mode 100644 index 00000000..1f4c9eaf --- /dev/null +++ b/kpf/src/DirSelectWidget.h @@ -0,0 +1,66 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_DIR_SELECT_WIDGET_H +#define KPF_DIR_SELECT_WIDGET_H + +#include <klistview.h> + +namespace KPF +{ + /** + * Allows the user to choose a directory, with some restrictions. + */ + class DirSelectWidget : public KListView + { + Q_OBJECT + + public: + + DirSelectWidget + ( + const QString & pathToMakeVisible = "/", + QWidget * = 0, + const char * = 0 + ); + + virtual ~DirSelectWidget(); + + protected slots: + + void slotExpanded(QListViewItem * item); + + protected: + + void timerEvent(QTimerEvent *); + QString path(QListViewItem * item) const; + + private: + + class Private; + Private * d; + }; +} + +#endif // KPF_DIR_SELECT_WIDGET_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/DirectoryLister.cpp b/kpf/src/DirectoryLister.cpp new file mode 100644 index 00000000..bfd0127b --- /dev/null +++ b/kpf/src/DirectoryLister.cpp @@ -0,0 +1,345 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <cmath> + +#include <qapplication.h> +#include <qdir.h> +#include <qstring.h> +#include <qstylesheet.h> +#include <qpalette.h> +#include <qtextstream.h> +#include <kglobalsettings.h> +#include <klocale.h> +#include <kmimetype.h> +#include <kurl.h> + +#include "Defines.h" +#include "DirectoryLister.h" +#include "Utils.h" + +namespace KPF +{ + class DirectoryLister::Private + { + public: + + Private() + { + } + }; + + QString colorToCSS(const QColor &c) + { + return + "rgb(" + + QString::number(c.red()) + + ", " + + QString::number(c.green()) + + ", " + + QString::number(c.blue()) + + ")"; + } + + QByteArray buildHTML(const QString & title, const QString & body) + { + QPalette pal = qApp->palette(); + QByteArray temp_string; + QTextStream html(temp_string, IO_WriteOnly); + + html.setEncoding(QTextStream::UnicodeUTF8); + + html + << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + << endl + << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"" + << endl + << "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" + << endl + << "<html xmlns=\"http://www.w3.org/1999/xhtml\">" + << endl + << "\t<head>" + << endl + << "\t\t<title>" + << title + << "</title>" + << endl + << "<style type=\"text/css\">" + << endl + << "<!--" + << endl + << "table.filelist { " + << "color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Foreground)) + << "; " + << "background-color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Background)) + << "; " + << "border: thin outset; " + << "width: 100%; " + << "}" + << endl + << "td { " + << "margin: 0px; " + << "white-space: nowrap; " + << "}" + << endl + << "td.norm { " + << "background-color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Base)) + << "; " + << "color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Foreground)) + << "; " + << "}" + << endl + << "td.alt { " + << "background-color: " + << colorToCSS + ( + KGlobalSettings::calculateAlternateBackgroundColor + (pal.color(QPalette::Normal, QColorGroup::Base)) + ) + << "; " + << "color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Foreground)) + << "; " + << "}" + << endl + << "a { " + << "color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Text)) + << "; " + << "text-decoration: none; " + << "}" + << endl + << "th.listheading { " + << "color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::ButtonText)) + << "; " + << "background-color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Button)) + << "; " + << "text-align: left; " + << "white-space: nowrap; " + << "border: thin outset; " + << "}" + << endl + << "a.direntry { " + << "font-weight: bold; " + << "}" + << endl + << "div.sizeentry { " + << "color: " + << colorToCSS(pal.color(QPalette::Normal, QColorGroup::Text)) + << "; " + << "text-align: right; " + << "}" + << endl + << "-->" + << endl + << "</style>" + << endl + << "\t</head>" + << endl + << "\t<body>" + << endl + << body + << "\t</body>" + << endl + << "</html>" + << endl + ; + + return temp_string; + } + + QString prettySize(uint size) + { + QString suffix; + QString temp; + float floated_size; + + if (size > 1023) + { + if (size > 1048575) + { + floated_size = size / 1048576.0; + suffix = i18n(" MB"); + } + else + { + floated_size = size / 1024.0; + suffix = i18n(" KB"); + } + } + else + { + temp.setNum(size); + temp += i18n(" bytes"); + return temp; + } + + temp.setNum(floated_size, 'f', 1); + temp += suffix; + return temp; + } + + DirectoryLister * DirectoryLister::instance_ = 0L; + + DirectoryLister * + DirectoryLister::instance() + { + if (0 == instance_) + instance_ = new DirectoryLister; + + return instance_; + } + + DirectoryLister::DirectoryLister() + { + d = new Private; + } + + DirectoryLister::~DirectoryLister() + { + delete d; + } + + QByteArray + DirectoryLister::html(const QString & root, const QString & _path) + { + kpfDebug << "root: " << root << " path: " << _path << endl; + + QString path; + + if (_path.right(1) != "/") + path = _path + "/"; + else + path = _path; + + if (path[0] == '/') + path + ""; + + QDir d(root + path); + + if (!d.exists()) + { + return buildHTML + ( + i18n("Error"), + i18n("Directory does not exist: %1 %2").arg(root).arg(path) + ); + } + + const QFileInfoList * infoList = + d.entryInfoList(QDir::DefaultFilter, QDir::Name | QDir::DirsFirst); + + if (0 == infoList) + { + return buildHTML + ( + i18n("Error"), + i18n("Directory unreadable: %1 %2").arg(root).arg(path) + ); + } + + QString html; + + html += "<table"; + html += " width=\"100%\""; + html += " class=\"filelist\">\n"; + + html += "<tr>\n"; + html += "<th align=\"left\" class=\"listheading\">Name</th>\n"; + html += "<th align=\"left\" class=\"listheading\">Size</th>\n"; + html += "</tr>\n"; + + for (QFileInfoListIterator it(*infoList); it.current(); ++it) + { + static int counter = 0; + + QFileInfo * fi(it.current()); + + if ( + (fi->fileName()[0] == '.') + && ((fi->fileName() != "..") || path == "/") + ) + { + // Don't show hidden files + continue; + } + + ++counter; + + QString td_class = (counter % 2) ? "alt" : "norm"; + + html += "<tr>\n"; + + html += "<td class=\"" + td_class + "\">"; + + QString item_class = QString((fi->isDir()) ? "direntry" : "fileentry"); + + KURL fu(path+fi->fileName()); + html += + "<a href=\"" + + fu.encodedPathAndQuery() + + (fi->isDir() ? "/" : "") + + "\" class=\"" + + item_class + + "\">"; + + if (fi->fileName() != "..") + html += QStyleSheet::escape(fi->fileName()); + else + html += i18n("Parent Directory"); + + html += "</a>"; + + if (fi->isDir()) + html += "/"; + + html += "</td>\n"; + + html += "<td class=\"" + td_class + "\">"; + + if (!fi->isDir()) + html + += "<div class=\"sizeentry\">" + prettySize(fi->size()) + "</div>"; + + html += "</td>\n"; + html += "</tr>\n"; + } + + html += "</table>\n"; + + return buildHTML + ( + i18n("Directory listing for %1").arg(QStyleSheet::escape(path)), + html + ); + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/DirectoryLister.h b/kpf/src/DirectoryLister.h new file mode 100644 index 00000000..e1809202 --- /dev/null +++ b/kpf/src/DirectoryLister.h @@ -0,0 +1,72 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_DIRECTORY_LISTER_H +#define KPF_DIRECTORY_LISTER_H + +#include <qstring.h> +#include <qcstring.h> + +namespace KPF +{ + /** + * Creates an HTML document by listing the contents of a directory. + */ + class DirectoryLister + { + public: + + /** + * @return a pointer to the single instance of DirectoryLister. + */ + static DirectoryLister * instance(); + + virtual ~DirectoryLister(); + + /** + * Get a directory listing (HTML) for the specified path. Uses + * cache if directory has not been modified since last read. + */ + QByteArray html(const QString &root, const QString & path); + + uint headerLength() const; + uint footerLength() const; + uint emptyEntryLength() const; + + private: + + /** + * Constructs a directory listing object. + */ + DirectoryLister(); + + static DirectoryLister * instance_; + + class Private; + Private * d; + }; + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ErrorMessageConfigDialog.cpp b/kpf/src/ErrorMessageConfigDialog.cpp new file mode 100644 index 00000000..2f7cb43a --- /dev/null +++ b/kpf/src/ErrorMessageConfigDialog.cpp @@ -0,0 +1,154 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "ErrorMessageConfigDialog.h" +#include "ErrorMessageConfigDialog.moc" + +#include <qlabel.h> +#include <qframe.h> +#include <qlayout.h> + +#include <kurlrequester.h> +#include <kconfig.h> +#include <klocale.h> +#include <kdialog.h> + +#include "Defines.h" +#include "Defaults.h" +#include "Utils.h" + +namespace KPF +{ + ErrorMessageConfigDialog::ErrorMessageConfigDialog + ( + WebServer * webServer, + QWidget * parent + ) + : KDialogBase + ( + parent, + "ErrorMessageConfigDialog", + false, + i18n("Configure error messages"), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Cancel, + true // Use a separator. + ), + server_(webServer) + { + QValueList<uint> codeList; + + codeList << 400 << 403 << 404 << 412 << 416 << 500 << 501; + + QFrame * w = makeMainWidget(); + + QVBoxLayout * layout = + new QVBoxLayout(w, KDialog::marginHint(), KDialog::spacingHint()); + + QLabel * info = + new QLabel + ( + i18n + ( + "<p>Here you may select files to use instead of the default error" + " messages passed to a client.</p>" + "<p>The files may contain anything you wish, but by convention" + " you should report the error code and the English version of" + " the error message (e.g. \"Bad request\"). Your file should" + " also be valid HTML.</p>" + "<p>The strings ERROR_MESSAGE, ERROR_CODE and RESOURCE, if" + " they exist in the file, will be replaced with the English" + " error message, the numeric error code and the path of the" + " requested resource, respectively.</p>" + ), + w + ); + + layout->addWidget(info); + + QGridLayout * grid = new QGridLayout(layout, codeList.count(), 2); + + QString pattern(i18n("%1 %2")); + + KConfig config(Config::name()); + + config.setGroup("ErrorMessageOverrideFiles"); + + QValueList<uint>::ConstIterator it; + + for (it = codeList.begin(); it != codeList.end(); ++it) + { + QString originalPath = + config.readPathEntry(QString::number(*it)); + + QString responseName(translatedResponseName(*it)); + + KURLRequester * requester = new KURLRequester(originalPath, w); + + itemList_.append(new Item(*it, requester, responseName, originalPath)); + + QLabel * l = new QLabel(pattern.arg(*it).arg(responseName), w); + + l->setBuddy(requester); + + grid->addWidget(l, *it, 0); + grid->addWidget(requester, *it, 1); + } + } + + ErrorMessageConfigDialog::~ErrorMessageConfigDialog() + { + itemList_.setAutoDelete(true); + itemList_.clear(); + } + + void + ErrorMessageConfigDialog::slotURLRequesterTextChanged(const QString &) + { + } + + void + ErrorMessageConfigDialog::accept() + { + KConfig config(Config::name()); + + config.setGroup("ErrorMessageOverrideFiles"); + + QPtrListIterator<Item> it(itemList_); + + for (; it.current(); ++it) + { + config.writePathEntry + ( + QString::number(it.current()->code), + it.current()->urlRequester->url() + ); + } + + config.sync(); + + KDialogBase::accept(); + } +} + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ErrorMessageConfigDialog.h b/kpf/src/ErrorMessageConfigDialog.h new file mode 100644 index 00000000..51bf4830 --- /dev/null +++ b/kpf/src/ErrorMessageConfigDialog.h @@ -0,0 +1,88 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_ERROR_MESSAGE_CONFIG_DIALOG_H +#define KPF_ERROR_MESSAGE_CONFIG_DIALOG_H + +#include <qmap.h> +#include <kdialogbase.h> + +class KURLRequester; + +namespace KPF +{ + class WebServer; + + /** + * Currently unused pending implementation. + */ + class ErrorMessageConfigDialog : public KDialogBase + { + Q_OBJECT + + public: + + ErrorMessageConfigDialog(WebServer *, QWidget * parent); + + virtual ~ErrorMessageConfigDialog(); + + protected slots: + + void slotURLRequesterTextChanged(const QString &); + + protected: + + void accept(); + + private: + + WebServer * server_; + + /** + * Provides a graphical interface to allow the user to pick a file + * to be used when reporting an error code. + */ + class Item + { + public: + + Item(uint i, KURLRequester * r, QString s, QString p) + : code (i), + urlRequester (r), + report (s), + originalPath (p) + { + } + + uint code; + KURLRequester * urlRequester; + QString report; + QString originalPath; + }; + + QPtrList<Item> itemList_; + }; +} + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Help.cpp b/kpf/src/Help.cpp new file mode 100644 index 00000000..19c6ae26 --- /dev/null +++ b/kpf/src/Help.cpp @@ -0,0 +1,62 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "Help.h" +#include <klocale.h> + +#include <dnssd/servicebrowser.h> + +namespace KPF +{ + namespace HelpText + { + + QString getServerNameHelp() + { + switch(DNSSD::ServiceBrowser::isAvailable()) { + case DNSSD::ServiceBrowser::Working: + return i18n("<p>Specify the name that will be used when announcing" + " this server on network.</p>"); + case DNSSD::ServiceBrowser::Stopped: + return i18n("<p>The Zeroconf daemon is not running. See the Handbook" + " for more information.<br/>" + "Other users will not see this system when browsing" + " the network via zeroconf, but sharing will still work.</p>"); + case DNSSD::ServiceBrowser::Unsupported: + return i18n("<p>Zeroconf support is not available in this version of KDE." + " See the Handbook for more information.<br/>" + "Other users will not see this system when browsing" + " the network via zeroconf, but sharing will still work.</p>"); + default: + return i18n("<p>Unknown error with Zeroconf.<br/>" + "Other users will not see this system when browsing" + " the network via zeroconf, but sharing will still work.</p>"); + } + } + + + } // End namespace HelpText + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Help.h b/kpf/src/Help.h new file mode 100644 index 00000000..12584213 --- /dev/null +++ b/kpf/src/Help.h @@ -0,0 +1,44 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_HELP_H +#define KPF_HELP_H + +#include <qstring.h> + +namespace KPF +{ + /** + * Used to provide a single point of access to config defaults and key names. + */ + namespace HelpText + { + + QString getServerNameHelp(); + + } // End namespace HelpText + +} // End namespace KPF + +#endif // KPF_DEFAULTS_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/KPFInterface.cpp b/kpf/src/KPFInterface.cpp new file mode 100644 index 00000000..d12c751d --- /dev/null +++ b/kpf/src/KPFInterface.cpp @@ -0,0 +1,130 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "Defines.h" +#include "WebServer.h" +#include "WebServerManager.h" +#include "KPFInterface.h" + +KPFInterface::KPFInterface() + : DCOPObject("KPFInterface") +{ + // Empty. +} + +KPFInterface::~KPFInterface() +{ + // Empty. +} + + QStringList +KPFInterface::serverRootList() +{ + QList<KPF::WebServer> l(KPF::WebServerManager::instance()->serverListLocal()); + + QStringList ret; + + for (QListIterator<KPF::WebServer> it(l); it.current(); ++it) + ret << it.current()->root(); + + return ret; +} + + bool +KPFInterface::createServer +( + QString root, + uint port, + uint bandwidthLimit, + uint connectionLimit, + bool followSymlinks +) +{ + kpfDebug << "KPFInterface::createServer(" << root << ", " << + port << ", " << bandwidthLimit << ", " << connectionLimit << ", " + << (followSymlinks ? "true" : "false") << ")" << endl; + + KPF::WebServer * s = + KPF::WebServerManager::instance()->createServer + ( + root, + port, + bandwidthLimit, + connectionLimit, + followSymlinks + ); + + if (0 == s) + { + kpfDebug << "KPFInterface::createServer(): failed" << endl; + return false; + } + else + { + kpfDebug << "KPFInterface::createServer(): ok" << endl; + return true; + } +} + + bool +KPFInterface::disableServer(QString root) +{ + kpfDebug << "KPFInterface::disableServer(" << root << ")" << endl; + return KPF::WebServerManager::instance()->disableServer(root); +} + + bool +KPFInterface::restartServer(QString root) +{ + kpfDebug << "KPFInterface::restartServer(" << root << ")" << endl; + return KPF::WebServerManager::instance()->restartServer(root); +} + + bool +KPFInterface::reconfigureServer(QString root) +{ + kpfDebug << "KPFInterface::reconfigureServer(" << root << ")" << endl; + return KPF::WebServerManager::instance()->reconfigureServer(root); +} + + bool +KPFInterface::pauseServer(QString root) +{ + kpfDebug << "KPFInterface::pauseServer(" << root << ")" << endl; + return KPF::WebServerManager::instance()->pauseServer(root, true); +} + + bool +KPFInterface::unpauseServer(QString root) +{ + kpfDebug << "KPFInterface::unpauseServer(" << root << ")" << endl; + return KPF::WebServerManager::instance()->pauseServer(root, false); +} + + bool +KPFInterface::serverPaused(QString root) +{ + kpfDebug << "KPFInterface::serverPaused(" << root << ")" << endl; + return KPF::WebServerManager::instance()->serverPaused(root); +} +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/KPFInterface.h b/kpf/src/KPFInterface.h new file mode 100644 index 00000000..facd482c --- /dev/null +++ b/kpf/src/KPFInterface.h @@ -0,0 +1,68 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_INTERFACE_H +#define KPF_INTERFACE_H + +#include <dcopobject.h> + +#include "Defaults.h" + +/** + * DCOP interface to kpf. + */ +class KPFInterface : virtual public DCOPObject +{ + K_DCOP + + public: + + KPFInterface (); + ~KPFInterface (); + + k_dcop: + + /** + * @return list of root directories used by WebServer objects. + */ + virtual QStringList serverRootList(); + + virtual bool createServer + ( + QString root, + uint port, + uint bandwidthLimit = KPF::Config::DefaultBandwidthLimit, + uint connectionLimit = KPF::Config::DefaultConnectionLimit, + bool followSymlinks = KPF::Config::DefaultFollowSymlinks + ); + + virtual bool disableServer (QString root); + virtual bool restartServer (QString root); + virtual bool reconfigureServer (QString root); + virtual bool pauseServer (QString root); + virtual bool unpauseServer (QString root); + virtual bool serverPaused (QString root); +}; + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Makefile.am b/kpf/src/Makefile.am new file mode 100644 index 00000000..5d419f0f --- /dev/null +++ b/kpf/src/Makefile.am @@ -0,0 +1,89 @@ +INCLUDES = $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kpf_panelapplet.la kpfpropertiesdialog.la + +kpf_panelapplet_la_SOURCES = \ + Utils.cpp \ + DirectoryLister.cpp \ + ByteRange.cpp \ + DirSelectWidget.cpp \ + PortValidator.cpp \ + Request.cpp \ + Response.cpp \ + Resource.cpp \ + RootValidator.cpp \ + Server.cpp \ + ServerPrivate.cpp \ + ServerSocket.cpp \ + WebServer.cpp \ + WebServer.skel \ + WebServer.stub \ + WebServerSocket.cpp \ + WebServerManager.cpp \ + WebServerManager.skel \ + SingleServerConfigDialog.cpp \ + System.cpp \ + ConfigDialogPage.cpp \ + ErrorMessageConfigDialog.cpp \ + ActiveMonitor.cpp \ + ActiveMonitorItem.cpp \ + ActiveMonitorWindow.cpp \ + BandwidthGraph.cpp \ + ServerWizard.cpp \ + AppletItem.cpp \ + Applet.cpp \ + Defaults.cpp \ + Help.cpp + +kpf_panelapplet_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module + +kpf_panelapplet_la_LIBADD = $(LIB_KIO) -lkdnssd + +kpfpropertiesdialog_la_SOURCES = \ + PropertiesDialogPlugin.cpp \ + StartingKPFDialog.cpp \ + WebServer.stub \ + WebServerManager.stub \ + Defaults.cpp \ + Help.cpp + +kpfpropertiesdialog_la_LIBADD = $(LIB_KIO) -lkdnssd + +kpfpropertiesdialog_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module + +noinst_HEADERS = \ + Utils.h \ + Defaults.h \ + DirectoryLister.h \ + DirSelectWidget.h \ + ByteRange.h \ + PortValidator.h \ + Request.h \ + Response.h \ + Resource.h \ + RootValidator.h \ + Server.h \ + ServerPrivate.h \ + ServerSocket.h \ + WebServer.h \ + WebServerSocket.h \ + WebServerManager.h \ + SingleServerConfigDialog.h \ + ConfigDialogPage.h \ + PropertiesDialogPlugin.h \ + StartingKPFDialog.h \ + ErrorMessageConfigDialog.h \ + ActiveMonitor.h \ + ActiveMonitorItem.h \ + ActiveMonitorWindow.h \ + BandwidthGraph.h \ + ServerWizard.h \ + AppletItem.h \ + Applet.h \ + Help.h + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kpf.pot + diff --git a/kpf/src/PortValidator.cpp b/kpf/src/PortValidator.cpp new file mode 100644 index 00000000..01c2074e --- /dev/null +++ b/kpf/src/PortValidator.cpp @@ -0,0 +1,57 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "PortValidator.h" +#include "WebServerManager.h" +#include "WebServer.h" + +namespace KPF +{ + PortValidator::PortValidator(QObject * parent, const char * name) + : QValidator(parent, name) + { + // Empty. + } + + QValidator::State + PortValidator::validate(QString & input, int & /* unused */) const + { + uint port(input.toUInt()); + + QPtrList<WebServer> + serverList(WebServerManager::instance()->serverListLocal()); + + for (QPtrListIterator<WebServer> it(serverList); it.current(); ++it) + { + if (it.current()->listenPort() == port) + { + return Intermediate; + } + } + + return Valid; + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/PortValidator.h b/kpf/src/PortValidator.h new file mode 100644 index 00000000..c86d0d01 --- /dev/null +++ b/kpf/src/PortValidator.h @@ -0,0 +1,47 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_PORT_VALIDATOR_H +#define KPF_PORT_VALIDATOR_H + +#include <qvalidator.h> + +namespace KPF +{ + /** + * Used for checking that a port input by the user is not the same as + * one used by an existing server. + */ + class PortValidator : public QValidator + { + public: + + PortValidator(QObject * parent, const char * name = 0); + + virtual State validate(QString & input, int & pos) const; + }; + +} // End namespace KPF + +#endif // KPF_PORT_VALIDATOR_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/PropertiesDialogPlugin.cpp b/kpf/src/PropertiesDialogPlugin.cpp new file mode 100644 index 00000000..e86743e3 --- /dev/null +++ b/kpf/src/PropertiesDialogPlugin.cpp @@ -0,0 +1,890 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qlayout.h> +#include <qcheckbox.h> +#include <qspinbox.h> +#include <qlabel.h> +#include <qframe.h> +#include <qwhatsthis.h> +#include <qpushbutton.h> +#include <qwidgetstack.h> +#include <qtimer.h> +#include <qdir.h> +#include <qlineedit.h> + +#include <kapplication.h> +#include <kglobal.h> +#include <dcopclient.h> +#include <kdialogbase.h> +#include <kmessagebox.h> +#include <kurl.h> +#include <kconfig.h> +#include <klocale.h> +#include <kseparator.h> +#include <kgenericfactory.h> + +#include "Defines.h" +#include "Defaults.h" +#include "PropertiesDialogPlugin.h" +#include "StartingKPFDialog.h" +#include "WebServerManager_stub.h" +#include "WebServer_stub.h" +#include "Help.h" + +#include <dnssd/servicebrowser.h> + +namespace KPF +{ + class ServerState + { + public: + + ServerState() + : shared (false), + listenPort (Config::DefaultListenPort), + bandwidthLimit (Config::DefaultBandwidthLimit), +// connectionLimit (Config::DefaultConnectionLimit), + followSymlinks (Config::DefaultFollowSymlinks) + { + } + + bool operator == (const ServerState & other) const + { + return + ( + other.shared == shared + && + other.listenPort == listenPort + && + other.bandwidthLimit == bandwidthLimit + && +// other.connectionLimit == connectionLimit +// && + other.followSymlinks == followSymlinks + ); + } + + bool operator != (const ServerState & other) const + { + return + ( + other.shared != shared + || + other.listenPort != listenPort + || + other.bandwidthLimit != bandwidthLimit + || +// other.connectionLimit != connectionLimit +// || + other.followSymlinks != followSymlinks + ); + } + + + bool shared; + uint listenPort; + uint bandwidthLimit; +// uint connectionLimit; + QString serverName; + bool followSymlinks; + }; + + class PropertiesDialogPlugin::Private + { + public: + + Private() + : l_listenPort (0L), + l_bandwidthLimit (0L), +// l_connectionLimit (0L), + sb_listenPort (0L), + sb_bandwidthLimit (0L), +// sb_connectionLimit (0L), + le_serverName (0L), + cb_followSymlinks (0L), + cb_share (0L), + stack (0L), + initWidget (0L), + configWidget (0L), + webServerManagerInterface (0L), + kpfRunning (false) + { + } + + QLabel * l_listenPort; + QLabel * l_bandwidthLimit; +// QLabel * l_connectionLimit; + QLabel * l_serverName; + QLabel * l_kpfStatus; + + QSpinBox * sb_listenPort; + QSpinBox * sb_bandwidthLimit; +// QSpinBox * sb_connectionLimit; + QLineEdit * le_serverName; + + QCheckBox * cb_followSymlinks; + QCheckBox * cb_share; + + QPushButton * pb_startKPF; + + QWidgetStack * stack; + QWidget * initWidget; + QWidget * configWidget; + + WebServerManager_stub * webServerManagerInterface; + + bool kpfRunning; + DCOPRef webServerRef; + KURL url; + + ServerState currentState; + ServerState wantedState; + }; + + PropertiesDialogPlugin::PropertiesDialogPlugin(KPropertiesDialog * dialog, + const char *, + const QStringList &) + : KPropsDlgPlugin(dialog) + { + d = new Private; + + d->webServerManagerInterface = + new WebServerManager_stub("kpf", "WebServerManager"); + + d->url = dialog->kurl(); + + if ( + d->url == QDir::homeDirPath() + || d->url == "file:" + QDir::homeDirPath() + ) + { + // Don't even show ourselves if it's the home dir + return; + } + + QWidget * widget = dialog->addPage(i18n("&Sharing")); + + d->stack = new QWidgetStack(widget); + + QVBoxLayout * stackLayout = new QVBoxLayout(widget); + stackLayout->addWidget(d->stack); + + d->initWidget = createInitWidget(d->stack); + d->configWidget = createConfigWidget(d->stack); + + d->stack->addWidget(d->initWidget, 0); + d->stack->addWidget(d->configWidget, 1); + + kapp->dcopClient()->setNotifications(true); + + connect + ( + kapp->dcopClient(), + SIGNAL(applicationRegistered(const QCString &)), + SLOT(slotApplicationRegistered(const QCString &)) + ); + + connect + ( + kapp->dcopClient(), + SIGNAL(applicationRemoved(const QCString &)), + SLOT(slotApplicationUnregistered(const QCString &)) + ); + + d->kpfRunning = kapp->dcopClient()->isApplicationRegistered("kpf"); + + if (!d->kpfRunning) + { + d->stack->raiseWidget(d->initWidget); + } + else + { + getServerRef(); + updateGUIFromCurrentState(); + d->stack->raiseWidget(d->configWidget); + } + } + + PropertiesDialogPlugin::~PropertiesDialogPlugin() + { + delete d->webServerManagerInterface; + d->webServerManagerInterface = 0; + + delete d; + d = 0; + } + + void + PropertiesDialogPlugin::slotSharingToggled(bool b) + { + if (b) + { + if (!userAcceptsWarning()) + { + // Avoid loop. + d->cb_share->blockSignals(true); + d->cb_share->setChecked(false); + d->cb_share->blockSignals(false); + b = false; + } + } + + setControlsEnabled(b); + } + + void + PropertiesDialogPlugin::setControlsEnabled(bool b) + { + + bool canPublish = b && DNSSD::ServiceBrowser::isAvailable() == DNSSD::ServiceBrowser::Working; + + d->l_serverName->setEnabled(canPublish); + d->l_listenPort ->setEnabled(b); + d->l_bandwidthLimit ->setEnabled(b); +// d->l_connectionLimit ->setEnabled(b); + d->l_serverName ->setEnabled(canPublish); + + d->sb_listenPort ->setEnabled(b); + d->sb_bandwidthLimit ->setEnabled(b); +// d->sb_connectionLimit ->setEnabled(b); + d->le_serverName ->setEnabled(canPublish); + d->cb_followSymlinks ->setEnabled(b); + } + + QWidget * + PropertiesDialogPlugin::createInitWidget(QWidget * parent) + { + QWidget * w = new QWidget(parent); + + QLabel * about = + new QLabel + ( + i18n + ( + "<p>To share files via the web, you need to be" + " running an 'applet' in your KDE panel. This" + " 'applet' is a small program which provides" + " file sharing capabilities." + "</p>" + ), + w + ); + + d->pb_startKPF + = new QPushButton(i18n("Start Applet"), w); + + QVBoxLayout * l = new QVBoxLayout(w); + + l->addWidget(about); + + d->l_kpfStatus = + new QLabel(i18n("Applet status: <strong>not running</strong>"), w); + + l->addWidget(d->l_kpfStatus); + + QHBoxLayout * l2 = new QHBoxLayout(l); + + l2->addStretch(1); + l2->addWidget(d->pb_startKPF); + + l->addStretch(1); + + connect(d->pb_startKPF, SIGNAL(clicked()), SLOT(slotStartKPF())); + + return w; + } + + QWidget * + PropertiesDialogPlugin::createConfigWidget(QWidget * parent) + { + QWidget * w = new QWidget(parent); + + d->cb_share = + new QCheckBox(i18n("Share this directory on the &Web"), w); + + d->l_listenPort = new QLabel(i18n("&Listen port:"), w); + d->l_bandwidthLimit = new QLabel(i18n("&Bandwidth limit:"), w); +// d->l_connectionLimit = new QLabel(i18n("Connection &limit"), w); + d->l_serverName = new QLabel(i18n("&Server name:"), w); + bool canPublish = DNSSD::ServiceBrowser::isAvailable() == DNSSD::ServiceBrowser::Working; + d->l_serverName->setEnabled(canPublish); + + d->sb_listenPort = new QSpinBox(1000, 999999, 1, w); + d->sb_bandwidthLimit = new QSpinBox(1, 999999, 1, w); +// d->sb_connectionLimit = new QSpinBox(1, 9999, 1, w); + d->le_serverName = new QLineEdit( w); + d->le_serverName->setEnabled(canPublish); + + d->cb_followSymlinks = + new QCheckBox(i18n("&Follow symbolic links"), w); + + d->l_listenPort ->setBuddy(d->sb_listenPort); + d->l_serverName ->setBuddy(d->le_serverName); + d->l_bandwidthLimit ->setBuddy(d->sb_bandwidthLimit); +// d->l_connectionLimit ->setBuddy(d->sb_connectionLimit); + + d->sb_listenPort ->setValue(Config::DefaultListenPort); + d->sb_bandwidthLimit ->setValue(Config::DefaultBandwidthLimit); + d->sb_bandwidthLimit ->setSuffix(i18n("kB/s")); +// d->sb_connectionLimit ->setValue(Config::DefaultConnectionLimit); + d->cb_followSymlinks ->setChecked(Config::DefaultFollowSymlinks); + + QVBoxLayout * l0 = + new QVBoxLayout(w, KDialog::marginHint(), KDialog::spacingHint()); + + l0->addWidget(d->cb_share); + + l0->addWidget(new KSeparator(QFrame::HLine, w)); + + QGridLayout * l2 = new QGridLayout(l0); + + l2->addWidget(d->l_listenPort, 0, 0); + l2->addWidget(d->sb_listenPort, 0, 1); + l2->addWidget(d->l_bandwidthLimit, 1, 0); + l2->addWidget(d->sb_bandwidthLimit, 1, 1); +// l2->addWidget(d->l_connectionLimit, 2, 0); +// l2->addWidget(d->sb_connectionLimit, 2, 1); + l2->addWidget(d->l_serverName, 2, 0); + l2->addWidget(d->le_serverName, 2, 1); + + l0->addWidget(d->cb_followSymlinks); + + l0->addStretch(1); + + QString shareHelp = + i18n + ( + "<p>" + "Setting this option makes all files in this directory and" + " any subdirectories available for reading to anyone" + " who wishes to view them." + "</p>" + "<p>" + "To view your files, a web browser or similar program" + " may be used." + "</p>" + "<p>" + "<strong>Warning!</strong> Before sharing a directory," + " you should be sure that it does not contain sensitive" + " information, such as passwords, company secrets, your" + " addressbook, etc." + "</p>" + "<p>" + "Note that you cannot share your home directory" + " (%1)" + "</p>" + ) + .arg(QDir::homeDirPath()); + + QString listenPortHelp = + i18n + ( + "<p>" + "Specify the network `port' on which the server should" + " listen for connections." + "</p>" + ); + + QString bandwidthLimitHelp = + i18n + ( + "<p>" + "Specify the maximum amount of data (in kilobytes) that will be" + " sent out per second." + "</p>" + "<p>" + "This allows you to keep some bandwidth for yourself instead" + " of allowing connections with kpf to hog your connection." + "</p>" + ); + + QString connectionLimitHelp = + i18n + ( + "<p>" + "Specify the maximum number of connections allowed at" + " any one time." + "</p>" + ); + + QString followSymlinksHelp = + i18n + ( + "<p>" + "Allow serving of files which have a symbolic link in" + " the path from / to the file, or are a symbolic link" + " themselves." + "</p>" + "<p>" + "<strong>Warning!</strong> This could be a security" + " risk. Use only if you understand the issues involved." + "</p>" + ); + QString serverNameHelp = KPF::HelpText::getServerNameHelp(); + + QWhatsThis::add(d->cb_share, shareHelp); + QWhatsThis::add(d->l_listenPort, listenPortHelp); + QWhatsThis::add(d->sb_listenPort, listenPortHelp); + QWhatsThis::add(d->l_bandwidthLimit, bandwidthLimitHelp); + QWhatsThis::add(d->sb_bandwidthLimit, bandwidthLimitHelp); +// QWhatsThis::add(d->l_connectionLimit, connectionLimitHelp); +// QWhatsThis::add(d->sb_connectionLimit, connectionLimitHelp); + QWhatsThis::add(d->l_serverName, serverNameHelp); + QWhatsThis::add(d->le_serverName, serverNameHelp); + QWhatsThis::add(d->cb_followSymlinks, followSymlinksHelp); + + connect(d->cb_share, SIGNAL(toggled(bool)), SLOT(slotSharingToggled(bool))); + + slotSharingToggled(false); + + connect + ( + d->cb_share, + SIGNAL(toggled(bool)), + SLOT(slotChanged()) + ); + + connect + ( + d->sb_listenPort, + SIGNAL(valueChanged(int)), + SLOT(slotChanged()) + ); + + connect + ( + d->sb_bandwidthLimit, + SIGNAL(valueChanged(int)), + SLOT(slotChanged()) + ); + +#if 0 + connect + ( + d->sb_connectionLimit, + SIGNAL(valueChanged(int)), + SLOT(slotChanged()) + ); +#endif + connect + ( + d->le_serverName, + SIGNAL(textChanged(const QString&)), + SLOT(slotChanged()) + ); + + connect + ( + d->cb_followSymlinks, + SIGNAL(toggled(bool)), + SLOT(slotChanged()) + ); + + return w; + } + + void + PropertiesDialogPlugin::slotStartKPF() + { + d->l_kpfStatus + ->setText(i18n("Applet status: <strong>starting...</strong>")); + + kapp->dcopClient() + ->send("kicker", "default", "addApplet(QString)", "kpfapplet.desktop"); + + QTimer::singleShot(4 * 1000, this, SLOT(slotStartKPFFailed())); + } + + void + PropertiesDialogPlugin::slotStartKPFFailed() + { + d->l_kpfStatus + ->setText(i18n("Applet status: <strong>failed to start</strong>")); + + d->pb_startKPF->setEnabled(true); + } + + void + PropertiesDialogPlugin::slotApplicationRegistered(const QCString & s) + { + if ("kpf" == s) + { + d->kpfRunning = true; + + d->l_kpfStatus + ->setText(i18n("Applet status: <strong>running</strong>")); + + d->pb_startKPF->setEnabled(false); + + getServerRef(); + updateGUIFromCurrentState(); + d->stack->raiseWidget(d->configWidget); + } + } + + void + PropertiesDialogPlugin::slotApplicationUnregistered(const QCString & s) + { + if ("kpf" == s) + { + d->kpfRunning = false; + + d->webServerRef.clear(); + + d->pb_startKPF->setEnabled(true); + + d->l_kpfStatus + ->setText(i18n("Applet status: <strong>not running</strong>")); + + d->stack->raiseWidget(d->initWidget); + } + } + + void + PropertiesDialogPlugin::readSettings() + { + d->currentState = ServerState(); + + if (!d->kpfRunning || d->webServerRef.isNull()) + return; + + d->currentState.shared = true; + + WebServer_stub webServer(d->webServerRef.app(), d->webServerRef.object()); + + d->currentState.listenPort = webServer.listenPort(); + + if (DCOPStub::CallFailed == webServer.status()) + { + // TODO: warn user ? + kpfDebug << "WebServer_stub call failed" << endl; + d->currentState.listenPort = Config::DefaultListenPort; + return; + } + + d->currentState.bandwidthLimit = webServer.bandwidthLimit(); + + if (DCOPStub::CallFailed == webServer.status()) + { + // TODO: warn user ? + kpfDebug << "WebServer_stub call failed" << endl; + d->currentState.bandwidthLimit = Config::DefaultBandwidthLimit; + return; + } + +#if 0 + d->currentState.connectionLimit = webServer.connectionLimit(); + + if (DCOPStub::CallFailed == webServer.status()) + { + // TODO: warn user ? + kpfDebug << "WebServer_stub call failed" << endl; + d->currentState.connectionLimit = Config::DefaultConnectionLimit; + return; + } +#endif + + d->currentState.serverName = webServer.serverName(); + if (DCOPStub::CallFailed == webServer.status()) + { + // TODO: warn user ? + kpfDebug << "WebServer_stub call failed" << endl; + d->currentState.serverName = ""; + return; + } + + + d->currentState.followSymlinks = webServer.followSymlinks(); + + if (DCOPStub::CallFailed == webServer.status()) + { + // TODO: warn user ? + kpfDebug << "WebServer_stub call failed" << endl; + d->currentState.followSymlinks = Config::DefaultFollowSymlinks; + return; + } + } + + void + PropertiesDialogPlugin::getServerRef() + { + QValueList<DCOPRef> serverRefList = + d->webServerManagerInterface->serverList(); + + if (DCOPStub::CallFailed == d->webServerManagerInterface->status()) + { + // TODO: warn + kpfDebug << "webServerManagerInterface.serverList call failed" << endl; + return; + } + + d->webServerRef.clear(); + + QValueList<DCOPRef>::ConstIterator it(serverRefList.begin()); + + for (; it != serverRefList.end(); ++it) + { + DCOPRef serverRef(*it); + + WebServer_stub webServer(serverRef.app(), serverRef.object()); + + if (webServer.root() == d->url.path()) + { + d->webServerRef = serverRef; + break; + } + } + } + + bool + PropertiesDialogPlugin::userAcceptsWarning() const + { + QString noWarningKey("DoNotWarnAboutSharingDirectoriesViaHTTP"); + + KConfig * config(KGlobal::config()); + + if (config->readBoolEntry(noWarningKey, false)) + return true; + + return + ( + KMessageBox::Continue + == + KMessageBox::warningContinueCancel + ( + d->configWidget, + i18n( + "<p>" + "Before you share a directory, be <strong>absolutely" + " certain</strong> that it does not contain sensitive" + " information." + "</p>" + "<p>" + "Sharing a directory makes all information" + " in that directory <strong>and all subdirectories</strong>" + " available to <strong>anyone</strong> who wishes to read it." + "</p>" + "<p>" + "If you have a system administrator, please ask for permission" + " before sharing a directory in this way." + "</p>" + ), + i18n("Warning - Sharing Sensitive Information?"), + i18n("&Share Directory"), + noWarningKey, + true + ) + ); + } + + void + PropertiesDialogPlugin::slotChanged() + { + kpfDebug << "PropertiesDialogPlugin::slotChanged" << endl; + readSettings(); + updateWantedStateFromGUI(); + + setDirty(d->currentState != d->wantedState); + kpfDebug << "Dirty: " << isDirty() << endl; + emit(changed()); + } + + void + PropertiesDialogPlugin::applyChanges() + { + readSettings(); + updateWantedStateFromGUI(); + + enum Action + { + None, + Enable, + Disable, + Reconfigure + }; + + bool needRestart = false; + + Action action = None; + + if (!d->currentState.shared && d->wantedState.shared) + { +// kpfDebug << "Not shared, but want to be. Action is Enable" << endl; + action = Enable; + } + else if (d->currentState.shared && !d->wantedState.shared) + { +// kpfDebug << "Shared, but don't want to be. Action is Disable" << endl; + action = Disable; + } + else if + ( + d->currentState.listenPort != d->wantedState.listenPort + || + d->currentState.bandwidthLimit != d->wantedState.bandwidthLimit + || +// d->currentState.connectionLimit != d->wantedState.connectionLimit +// || + d->currentState.serverName != d->wantedState.serverName + || + d->currentState.followSymlinks != d->wantedState.followSymlinks + ) + { +// kpfDebug << "Config changed. Action is Reconfigure" << endl; + action = Reconfigure; + + if (d->currentState.listenPort != d->wantedState.listenPort) + needRestart = true; + } + + if (None == action) + { +// kpfDebug << "Nothing changed. Action = None" << endl; + return; + } + + switch (action) + { + case Enable: + { + DCOPRef ref = + d->webServerManagerInterface->createServer + ( + d->url.path(), + d->wantedState.listenPort, + d->wantedState.bandwidthLimit, + Config::DefaultConnectionLimit,//d->wantedState.connectionLimit, + d->wantedState.followSymlinks, + d->wantedState.serverName + ); + + if (ref.isNull()) + { + // TODO: Warn user. + kpfDebug + << "kpf refused to create server - warn user here !" << endl; + break; + } + else + { + d->webServerRef = ref; + } + } + break; + + case Disable: + if (d->webServerRef.isNull()) + { + // TODO: Warn user. + kpfDebug << "Disable, but d->webServerRef is null" << endl; + } + else + { + d->webServerManagerInterface->disableServer(d->webServerRef); + } + break; + + case Reconfigure: + + if (d->webServerRef.isNull()) + { + kpfDebug << "Need restart, but d->webServerRef is null" << endl; + } + else + { + WebServer_stub webServer + (d->webServerRef.app(), d->webServerRef.object()); + + webServer.set + ( + d->wantedState.listenPort, + d->wantedState.bandwidthLimit, + Config::DefaultConnectionLimit,//d->wantedState.connectionLimit, + d->wantedState.followSymlinks, + d->wantedState.serverName + ); + + if (DCOPStub::CallFailed == webServer.status()) + { + // TODO: Warn user. + kpfDebug << "Reconfigure failed" << endl; + } + + if (needRestart) + { + webServer.restart(); + + if (DCOPStub::CallFailed == webServer.status()) + { + // TODO: Warn user. + kpfDebug << "Restart failed" << endl; + } + } + } + break; + + default: + kpfDebug << "Code error in KPF::PropertiesDialogPlugin." << endl; + break; + } + } + + void + PropertiesDialogPlugin::updateGUIFromCurrentState() + { + readSettings(); + + // We don't want slotSharingToggled to be called. + d->cb_share->blockSignals(true); + d->cb_share->setChecked(d->currentState.shared); + d->cb_share->blockSignals(false); + + d->sb_listenPort ->setValue (d->currentState.listenPort); + d->sb_bandwidthLimit ->setValue (d->currentState.bandwidthLimit); +// d->sb_connectionLimit ->setValue (d->currentState.connectionLimit); + d->le_serverName ->setText (d->currentState.serverName); + d->cb_followSymlinks ->setChecked (d->currentState.followSymlinks); + + setControlsEnabled(d->currentState.shared); + } + + void + PropertiesDialogPlugin::updateWantedStateFromGUI() + { + d->wantedState.shared = d->cb_share->isChecked(); + d->wantedState.listenPort = d->sb_listenPort->value(); + d->wantedState.bandwidthLimit = d->sb_bandwidthLimit->value(); +// d->wantedState.connectionLimit = d->sb_connectionLimit->value(); + d->wantedState.serverName = d->le_serverName->text(); + d->wantedState.followSymlinks = d->cb_followSymlinks->isChecked(); + } + + typedef KGenericFactory<PropertiesDialogPlugin, KPropertiesDialog> PropertiesDialogPluginFactory; +} + +K_EXPORT_COMPONENT_FACTORY( kpfpropertiesdialog, + KPF::PropertiesDialogPluginFactory( "kpf" ) ) + +#include "PropertiesDialogPlugin.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/PropertiesDialogPlugin.h b/kpf/src/PropertiesDialogPlugin.h new file mode 100644 index 00000000..5881080a --- /dev/null +++ b/kpf/src/PropertiesDialogPlugin.h @@ -0,0 +1,82 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_PROPERTIES_DIALOG_PLUGIN_H +#define KPF_PROPERTIES_DIALOG_PLUGIN_H + +#include <kpropertiesdialog.h> + +namespace KPF +{ + /** + * Provides an implementation of KPropsDlgPlugin which is plugged into + * Konqueror's directory properties dialog. Allows creating and configuring + * WebServer objects via DCOP conversations with the KPFInterface. Also + * allows starting the kpf applet if it is not already loaded by kicker. + */ + class PropertiesDialogPlugin : public KPropsDlgPlugin + { + Q_OBJECT + + public: + + PropertiesDialogPlugin(KPropertiesDialog *, const char *, const QStringList &); + + virtual ~PropertiesDialogPlugin(); + + virtual void applyChanges(); + + protected slots: + + void slotSharingToggled(bool); + void slotStartKPF(); + void slotStartKPFFailed(); + + void slotApplicationRegistered(const QCString &); + void slotApplicationUnregistered(const QCString &); + + void slotChanged(); + + protected: + + void getServerRef(); + + void updateGUIFromCurrentState(); + void updateWantedStateFromGUI(); + + QWidget * createInitWidget(QWidget * parent); + QWidget * createConfigWidget(QWidget * parent); + + void readSettings(); + void setControlsEnabled(bool); + bool userAcceptsWarning() const; + + private: + + class Private; + Private * d; + }; +} + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Request.cpp b/kpf/src/Request.cpp new file mode 100644 index 00000000..01735363 --- /dev/null +++ b/kpf/src/Request.cpp @@ -0,0 +1,377 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <climits> // For ULONG_MAX + +#include <qregexp.h> +#include <kurl.h> + +#include "Defines.h" +#include "Utils.h" +#include "Request.h" + +namespace KPF +{ + Request::Request() + : protocolMajor_ (0), + protocolMinor_ (9), + method_ (Unsupported), + haveHost_ (false), + haveIfModifiedSince_ (false), + haveIfUnmodifiedSince_ (false), + expectContinue_ (false), + haveRange_ (false), + persist_ (false) + { + } + + Request::~Request() + { + } + + void + Request::parseHeaders(const QStringList & buf) + { + for (QStringList::ConstIterator it(buf.begin()); it != buf.end(); ++it) + { + QString line(*it); + + int colonPos = line.find(':'); + + if (-1 != colonPos) + { + QString name(line.left(colonPos).stripWhiteSpace().lower()); + QString value(line.mid(colonPos + 1).stripWhiteSpace()); + handleHeader(name, value); + } + } + } + + void + Request::handleHeader(const QString & name, const QString & value) + { + if ("host" == name) + { + setHost(value); + } + if ("range" == name) + { + setRange(value); + } + else if ("if-modified-since" == name) + { + QDateTime dt; + + if (parseDate(value, dt)) + setIfModifiedSince(dt); + } + else if ("if-unmodified-since" == name) + { + QDateTime dt; + + if (parseDate(value, dt)) + setIfUnmodifiedSince(dt); + } + else if ("connection" == name) + { + QString v(value.lower()); + + if ("keep-alive" == v) + { + setPersist(true); + } + else if ("close" == v) + { + setPersist(false); + } + } + } + + void + Request::setProtocol(const QString & _s) + { + QString s(_s); + + s.remove(0, 5); + + int dotPos = s.find('.'); + + if (-1 != dotPos) + { + protocolMajor_ = s.left(dotPos).toUInt(); + protocolMinor_ = s.mid(dotPos + 1).toUInt(); + } + } + + void + Request::setProtocol(uint major, uint minor) + { + protocolMajor_ = major; + protocolMinor_ = minor; + } + + void + Request::setMethod(const QString & s) + { + if ("GET" == s) + method_ = Get; + else if ("HEAD" == s) + method_ = Head; + else + method_ = Unsupported; + } + + void + Request::setMethod(Method m) + { + method_ = m; + } + + void + Request::setPath(const QString & s) + { + KURL p(s); + path_ = clean(p.path()); + +#if 0 + if ('/' == path_.at(path_.length() - 1)) + { + path_.append("index.html"); + } +#endif + } + + void + Request::setHost(const QString & s) + { + host_ = s; + haveHost_ = true; + } + + void + Request::setIfModifiedSince(const QDateTime & dt) + { + ifModifiedSince_ = dt; + haveIfModifiedSince_ = true; + } + + void + Request::setIfUnmodifiedSince(const QDateTime & dt) + { + ifUnmodifiedSince_ = dt; + haveIfUnmodifiedSince_ = true; + } + + uint + Request::protocolMajor() const + { + return protocolMajor_; + } + + uint + Request::protocolMinor() const + { + return protocolMinor_; + } + + float + Request::protocol() const + { + return (float(protocolMajor_) + float(protocolMinor_) / 10.0); + } + + Request::Method + Request::method() const + { + return method_; + } + + bool + Request::haveHost() const + { + return haveHost_; + } + + bool + Request::haveIfModifiedSince() const + { + return haveIfModifiedSince_; + } + + bool + Request::haveIfUnmodifiedSince() const + { + return haveIfUnmodifiedSince_; + } + + QString + Request::path() const + { + return path_; + } + + QString + Request::host() const + { + return host_; + } + + QDateTime + Request::ifModifiedSince() const + { + return ifModifiedSince_; + } + + QDateTime + Request::ifUnmodifiedSince() const + { + return ifUnmodifiedSince_; + } + + QCString + Request::protocolString() const + { + QCString s("HTTP/"); + s += QCString().setNum(protocolMajor_); + s += '.'; + s += QCString().setNum(protocolMinor_); + return s; + } + + void + Request::setExpectContinue(bool b) + { + expectContinue_ = b; + } + + bool + Request::expectContinue() const + { + return expectContinue_; + } + + void + Request::setRange(const QString & s) + { + kpfDebug << "Request::setRange(`" << s << "')" << endl; + + haveRange_ = true; + + ByteRangeList l(s, protocol()); + + ulong first (ULONG_MAX); + ulong last (0L); + bool haveLast (false); + + for (ByteRangeList::ConstIterator it(l.begin()); it != l.end(); ++it) + { + ByteRange r(*it); + first = min(r.first(), first); + + if (r.haveLast()) + { + haveLast = true; + last = max(r.last(), last); + } + } + + kpfDebug << "Request::setRange(): first == " << first << "d" << endl; + + range_.setFirst(first); + + if (haveLast) + { + kpfDebug << "Request::setRange(): last == " << last << "d" << endl; + range_.setLast(last); + } + kpfDebug << "Request::setRange(): no last" << endl; + } + + ByteRange + Request::range() const + { + return range_; + } + + bool + Request::haveRange() const + { + return haveRange_; + } + + void + Request::setPersist(bool b) + { + if (protocol() > 1.0) // Bad, but makes wget work. + { + persist_ = b; + } + } + + bool + Request::persist() const + { + return persist_; + } + + void + Request::clear() + { + protocolMajor_ = 0; + protocolMinor_ = 9; + method_ = Unsupported; + haveHost_ = false; + haveIfModifiedSince_ = false; + haveIfUnmodifiedSince_ = false; + expectContinue_ = false; + haveRange_ = false; + persist_ = false; + path_ = QString::null; + host_ = QString::null; + ifModifiedSince_ = QDateTime(); + ifUnmodifiedSince_ = QDateTime(); + range_.clear(); + } + + QString + Request::clean(const QString & _path) const + { + QString s(_path); + + while (s.endsWith("/./")) + s.truncate(s.length() - 2); + + while (s.endsWith("/.")) + s.truncate(s.length() - 1); + + // Double slash -> slash. + + QRegExp r("\\/\\/+"); + s.replace(r, "/"); + + return s; + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Request.h b/kpf/src/Request.h new file mode 100644 index 00000000..92034c9c --- /dev/null +++ b/kpf/src/Request.h @@ -0,0 +1,252 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_REQUEST_H +#define KPF_REQUEST_H + +#include <qcstring.h> +#include <qstringlist.h> +#include <qdatetime.h> + +#include "ByteRange.h" + +namespace KPF +{ + /** + * Represents an HTTP request. + */ + class Request + { + public: + + /** + * HTTP/1.1 specifies many request types, known as 'methods'. + * An HTTP/1.1 compliant server must implement HEAD and GET. + * We support only these two. All others are rejected either + * because they are of limited use or because they are a security + * risk. + */ + enum Method { Head, Get, Unsupported }; + + /** + * Construct a Request object and set parameters to defaults. + */ + Request(); + virtual ~Request(); + + /** + * Take a list of headers and parse them, setting our values + * appropriately. + */ + void parseHeaders(const QStringList &); + + /** + * Parse one header line and set whatever value is appropriate. + */ + void handleHeader(const QString & name, const QString & value); + + /** + * HTTP has had a few revisions (protocols) since birth. Here you may + * specify the protocol which you believe the client is using. This + * version of setProtocol parses a string of the form + * "HTTP/major.minor", where major and minor are integers. + */ + void setProtocol (const QString &); + + /** + * HTTP has had a few revisions (protocols) since birth. Here you may + * specify the protocol which you believe the client is using. + */ + void setProtocol (uint major, uint minor); + + /** + * Specify the 'method' requested by the client. This version parses + * a string. + */ + void setMethod (const QString &); + + /** + * Specify the 'method' requested by the client. + */ + void setMethod (Method); + + /** + * Set the path to the requested resource. The path is + * immediately decoded and canonicalised. + */ + void setPath (const QString &); + + /** + * HTTP/1.1 requests must have a "Host:" header. + */ + void setHost (const QString &); + + /** + * HTTP/1.1 allows an "If-Modified-Since" header, specifying that + * the requested resource should only be retrieved if the resource + * has been modified since a certain date and time. + */ + void setIfModifiedSince (const QDateTime &); + + /** + * HTTP/1.1 allows an "If-Unmodified-Since" header, specifying that + * the requested resource should only be retrieved if the resource + * has NOT been modified since a certain date and time. + */ + void setIfUnmodifiedSince (const QDateTime &); + + /** + * HTTP/1.1 allows an "Expect: 100-continue" header, specifying + * that the server should immediately respond with "100 Continue". + */ + void setExpectContinue (bool); + + /** + * Specify a range of bytes which should be retrieved from the + * resource. See RFC 2616. + */ + void setRange (const QString &); + + /** + * HTTP/1.1 allows "persistent" connections. These may be specified + * by "Keep-Alive:" or "Connection: Keep-Alive" headers. + */ + void setPersist (bool); + + /** + * @return major version of protocol which was set. + */ + uint protocolMajor() const; + + /** + * @return minor version of protocol which was set. + */ + uint protocolMinor() const; + + /** + * @return version of protocol which was set, as major.minor, e.g. + * if the protocol was set to HTTP/0.9, this would return 0.9. + */ + float protocol() const; + + /** + * @return the (enumerated) request type ('method'). + */ + Method method() const; + + /** + * @return true if @ref setHost was called previously. + */ + bool haveHost() const; + + /** + * @return true if @ref setIfModifiedSince was called previously. + */ + bool haveIfModifiedSince() const; + + /** + * @return true if @ref setIfUnmodifiedSince was called previously. + */ + bool haveIfUnmodifiedSince() const; + + /** + * @return the path that was set with (and modified by) setPath() + */ + QString path() const; + + /** + * @return the host that was set previously. + */ + QString host() const; + + /** + * @return the date/time set by @ref setIfModifiedSince previously. + */ + QDateTime ifModifiedSince() const; + + /** + * @return the date/time set by @ref setIfUnmodifiedSince previously. + */ + QDateTime ifUnmodifiedSince() const; + + /** + * @return the protocol as a string, e.g. "HTTP/1.1". + */ + QCString protocolString() const; + + /** + * @return true if @ref setExpectContinue was used previously. + */ + bool expectContinue() const; + + /** + * @return byte range set by @ref setRange previously. + */ + ByteRange range() const; + + /** + * @return true if @ref setRange was called previously. + */ + bool haveRange() const; + + /** + * @return true if @ref setPersist was called previously. + */ + bool persist() const; + + /** + * Reset to initial state. + */ + void clear(); + + /** + * Clean up the path given in a request, so it's more like what we + * expect. + */ + QString clean(const QString & path) const; + + private: + + // Order dependency + uint protocolMajor_; + uint protocolMinor_; + Method method_; + bool haveHost_; + bool haveIfModifiedSince_; + bool haveIfUnmodifiedSince_; + bool expectContinue_; + bool haveRange_; + bool persist_; + // End order dependency + + QString path_; + QString host_; + QDateTime ifModifiedSince_; + QDateTime ifUnmodifiedSince_; + ByteRange range_; + }; + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Resource.cpp b/kpf/src/Resource.cpp new file mode 100644 index 00000000..d5e77072 --- /dev/null +++ b/kpf/src/Resource.cpp @@ -0,0 +1,346 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qstringlist.h> +#include <qdir.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <qregexp.h> +#include <kglobal.h> + +#include <kmimetype.h> + +#include "Utils.h" +#include "Defines.h" +#include "Resource.h" +#include "DirectoryLister.h" + +namespace KPF +{ + enum FileType { Dir, File }; + + class Resource::Private + { + public: + + Private() + : size(0), + sizeCalculated(false), + offset(0) + { + } + + QString root; + FileType fileType; + QString path; + QFile file; + QFileInfo fileInfo; + QDir dir; + uint size; + bool sizeCalculated; + uint offset; + + QByteArray html; + }; + + Resource::Resource() + { + d = new Private; + } + + Resource::~Resource() + { + delete d; + d = 0; + } + + void + Resource::setPath(const QString & root, const QString & relativePath) + { + kpfDebug << "setPath(`" << root << "',`" << relativePath << "'" << endl; + + d->root = root; + d->path = relativePath; + d->size = 0; + d->offset = 0; + d->sizeCalculated = false; + + d->file.close(); + + // Fix root if it doesn't have a trailing slash. + + if ('/' != d->root.at(d->root.length() - 1)) + d->root += '/'; + + if (d->path.right(1) == "/") + { + // A directory was requested + kpfDebug << "Directory requested" << endl; + + // Does the path actually point to a directory ? + + if (QFileInfo(d->root + d->path).isDir()) + { + kpfDebug << "Path points to directory" << endl; + + // Does an index.html exist in that directory ? + + if (QFileInfo(d->root + d->path + "index.html").exists()) + { + kpfDebug << "Found index.html" << endl; + + // Ok, add `index.html'. + + d->path += "index.html"; + } + else + { + kpfDebug << "NOT Found index.html" << endl; + } + + } + else + { + kpfDebug << "NOT Path points to directory" << endl; + } + } + else + { + kpfDebug << "NOT Directory requested" << endl; + } + + kpfDebug << "QFileInfo::setFile(`" << d->root << "' + `" << d->path << "'" << endl; + d->fileInfo.setFile(d->root + d->path); + } + + bool + Resource::open() + { + if (!d->fileInfo.exists()) + { + kpfDebug << "File doesn't exist" << endl; + return false; + } + + if (d->fileInfo.isDir()) + { + d->fileType = Dir; + d->dir.setPath(d->root + d->path); + + if (!d->dir.isReadable()) + { + kpfDebug << "Dir isn't readable" << endl; + return false; + } + else + { + generateHTML(); + } + } + else + { + d->fileType = File; + d->file.setName(d->root + d->path); + + if (!d->file.open(IO_ReadOnly)) + { + kpfDebug << "File isn't readable" << endl; + return false; + } + } + + calculateSize(); + return true; + } + + void + Resource::close() + { + if (File == d->fileType) + d->file.close(); + } + + bool + Resource::seek(int pos) + { + if (File == d->fileType) + { + return d->file.at(pos); + } + else + { + // TODO STUB + return false; + } + } + + int + Resource::readBlock(char * data, uint maxlen) + { + int bytesRead(0); + + if (File == d->fileType) + { + bytesRead = d->file.readBlock(data, maxlen); + } + else + { + if (d->offset < d->size) + { + uint bytesAvailable = QMIN(maxlen, d->size - d->offset); + + memcpy(data, d->html.data() + d->offset, bytesAvailable); + + d->offset += bytesAvailable; + + return bytesAvailable; + } + else + { + // Else bytesRead is still 0, because the read was out of bounds. + + kpfDebug << "Out of bounds in html" << endl; + } + } + + return bytesRead; + } + + uint + Resource::size() const + { + return d->size; + } + + int + Resource::at() const + { + return d->offset; + } + + bool + Resource::atEnd() const + { + if (File == d->fileType) + { + return d->file.atEnd(); + } + else + { + return d->offset >= d->size; + } + } + + void + Resource::calculateSize() + { + if (File == d->fileType) + { + d->size = d->fileInfo.size(); + } + else + { + d->size = d->html.size() - 1; + } + } + + + bool + Resource::readable() const + { + return d->fileInfo.isReadable(); + } + + QDateTime + Resource::lastModified() const + { + return d->fileInfo.lastModified(); + } + + bool + Resource::exists() const + { + bool b = d->fileInfo.exists(); + + if (!b) + { + kpfDebug << "File doesn't exist" << endl; + } + + return b; + } + + bool + Resource::symlink() const + { + if (d->fileInfo.isSymLink()) + return true; + + QString path(d->fileInfo.dirPath()); + + QStringList l(QStringList::split('/', path)); + + QString testPath; + + for (QStringList::ConstIterator it(l.begin()); it != l.end(); ++it) + { + testPath += '/'; + testPath += *it; + + if (QFileInfo(testPath).isSymLink()) + return true; + } + + return false; + } + + bool + Resource::seekable() const + { + return !(d->fileInfo.isDir()); + } + + QString + Resource::mimeType() const + { + if (d->fileInfo.isDir()) + return "text/html; charset=utf-8"; + return KMimeType::findByPath( d->root + d->path )->name(); + } + + void + Resource::generateHTML() + { + d->html = DirectoryLister::instance()->html(d->root, d->path); + } + + void + Resource::clear() + { + delete d; + d = new Private; + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Resource.h b/kpf/src/Resource.h new file mode 100644 index 00000000..870daed7 --- /dev/null +++ b/kpf/src/Resource.h @@ -0,0 +1,149 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_RESOURCE_H +#define KPF_RESOURCE_H + +#include <qstring.h> +#include <qdatetime.h> + +namespace KPF +{ + /** + * Provide any resource the client requests. This may be a file, a + * directory listing, or nothing at all, depending on the flabbiness + * of the client's arse. + */ + class Resource + { + public: + + /** + * Default ctor, object unusable until you setPath(). + */ + Resource(); + + /** + * Closes all open files. + */ + virtual ~Resource(); + + /** + * Reset this object and tell it what the new paths are. + */ + void setPath(const QString & root, const QString & relativePath); + + /** + * @return true if the file was opened ok or the dir was readable. + */ + bool open(); + + /** + * Just close. + */ + void close(); + + /** + * Seek to the specified position. + * @return false if this is a dir. + */ + bool seek(int); + + /** + * Read a block of the file or the generated HTML. + */ + int readBlock(char * data, uint maxlen); + + /** + * @return false if the file or directory doesn't exist. + */ + bool exists() const; + + /** + * Performs a search through the entire path, looking for symbolic links. + * + * Expensive ! + * + * @return true if the path contains a symbolic link. + */ + bool symlink() const; + + /** + * @return true if the resource is readable. + */ + bool readable() const; + + /** + * @return mtime of resource. + */ + QDateTime lastModified() const; + + /** + * @return size of file, or size of HTML that will be generated. + */ + uint size() const; + + /** + * @return current file position. + */ + int at() const; + + /** + * @return true if nothing left to read. + */ + bool atEnd() const; + + /** + * @return true if file, false if dir. Perhaps I'll make the HTML + * seekable later. + */ + bool seekable() const; + + /** + * @return mime type of file if available. If dir, returns text/html. + * If nothing available, returns text/plain. + */ + QString mimeType() const; + + /** + * Reset to initial state. + */ + void clear(); + + private: + + /** + * Update d->size; + */ + void calculateSize(); + + void generateHTML(); + + class Private; + Private * d; + }; + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Response.cpp b/kpf/src/Response.cpp new file mode 100644 index 00000000..f9ce46d8 --- /dev/null +++ b/kpf/src/Response.cpp @@ -0,0 +1,194 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qfile.h> +#include <qtextstream.h> +#include <qregexp.h> + +#include <kconfig.h> + +#include "Defines.h" +#include "Utils.h" +#include "Response.h" +#include "Defaults.h" + +namespace KPF +{ + Response::Response() + : code_(0), + size_(0) + { + // Empty. + } + + void + Response::setCode(uint code) + { + code_ = code; + } + + void + Response::setSize(ulong size) + { + size_ = size; + } + + Response::~Response() + { + // Empty. + } + + bool + Response::valid() const + { + return 0 != code_; + } + + ulong + Response::size() const + { + return size_; + } + + uint + Response::code() const + { + return code_; + } + + QCString + Response::text(const Request & request) const + { + QString s; + + // XXX: Ensure that all codes we know about are enumerated here. + switch (code_) + { + case 200: + case 206: + case 304: + if (request.protocol() >= 1.0) + { + s = QString(request.protocolString()) + + QString(" %1 %2\r\n").arg(code_).arg(responseName(code_)); + } + break; + + case 400: + case 403: + case 404: + case 412: + case 416: + case 500: + case 501: + case 505: + s = QString(request.protocolString()) + + QString(" %1 %2\r\n").arg(code_).arg(responseName(code_)) + + data(code_, request); + break; + + default: + kpfDebug << "Huh ?" << endl; + break; + } + + return s.utf8(); + } + + QString + Response::data(uint code, const Request & request) const + { + QString contentType = "Content-Type: text/html; charset=utf-8\r\n"; + + KConfig config(Config::name()); + + config.setGroup("General"); + + QString html; + + if + ( + config.readBoolEntry + (Config::key(Config::CustomErrors), Config::DefaultCustomErrors) + ) + { + config.setGroup("ErrorMessageOverrideFiles"); + + QString filename = config.readPathEntry(QString::number(code)); + + if (!filename.isEmpty()) + { + QFile f(filename); + + if (f.open(IO_ReadOnly)) + { + QRegExp regexpMessage ("ERROR_MESSAGE"); + QRegExp regexpCode ("ERROR_CODE"); + QRegExp regexpResource ("RESOURCE"); + + QTextStream str(&f); + + while (!str.atEnd()) + { + QString line(str.readLine()); + + line.replace(regexpMessage, responseName(code)); + line.replace(regexpCode, QString::number(code)); + line.replace(regexpResource, request.path()); + + html = line + "\r\n"; + } + } + } + } + else + { + html = "<html>\r\n"; + html += "<head>\r\n"; + html += "<title>\r\n" + responseName(code) + "</title>\r\n"; + html += "<style type=\"text/css\">\r\n"; + html += "BODY { color: black; background-color: rgb(228, 228, 228); }\r\n"; + html += "H1 { font-size: 1.7em; color: rgb(60, 85, 110); }\r\n"; + html += "P { margin: 40px, 40px, 10px, 10px; }\r\n"; + html += "</style>\r\n"; + html += "</head>\r\n"; + html += "<body>\r\n<h1>\r\nError: " + responseName(code) + "\r\n</h1>\r\n"; + html += "<p>Requested resource: " + request.path() + "</p>\r\n"; + html += "</body>\r\n</html>\r\n"; + } + + QString contentLength = + QString("Content-Length: %1\r\n").arg(html.length()); + + return (contentType + contentLength + "\r\n" + html); + } + + void + Response::clear() + { + code_ = size_ = 0; + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Response.h b/kpf/src/Response.h new file mode 100644 index 00000000..c24ba69c --- /dev/null +++ b/kpf/src/Response.h @@ -0,0 +1,102 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_RESPONSE_H +#define KPF_RESPONSE_H + +#include "Request.h" + +namespace KPF +{ + /** + * Represents some of the data which is used as a reponse to an + * HTTP request. + */ + class Response + { + public: + + /** + * + */ + Response(); + + /** + * + */ + virtual ~Response(); + + /** + * Each response has a code. See the HTTP specification. + */ + void setCode(uint); + + /** + * Set the size, in bytes, of the resource that will be transferred + * to the client. + */ + void setSize(ulong); + + /** + * @return true if code isn't 0. + */ + bool valid() const; + + /** + * @return size of requested resource. + */ + ulong size() const; + + /** + * @return HTTP response code. + */ + uint code() const; + + /** + * @return header/body data to send to the client. This string is + * constructed differently depending on HTTP response code. + */ + QCString text(const Request &) const; + + /** + * Reset to initial state. + */ + void clear(); + + protected: + + /** + * @internal + * Create HTML. + */ + QString data(uint, const Request &) const; + + private: + + uint code_; + uint size_; + }; + +} // End namespace KPF +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/RootValidator.cpp b/kpf/src/RootValidator.cpp new file mode 100644 index 00000000..17d6a43a --- /dev/null +++ b/kpf/src/RootValidator.cpp @@ -0,0 +1,66 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qfileinfo.h> +#include <qdir.h> + +#include "RootValidator.h" +#include "WebServerManager.h" + +namespace KPF +{ + RootValidator::RootValidator(QObject * parent, const char * name) + : QValidator(parent, name) + { + } + + QValidator::State + RootValidator::validate(QString & input, int & /* unused */) const + { + QString root(input); + + if ('/' == root.at(root.length() - 1)) + { + root.truncate(root.length() - 1); + } + + // Duplicate ? + + if (0 != WebServerManager::instance()->server(root)) + return Intermediate; + + QFileInfo fi(root); + + if (!fi.isDir()) + return Intermediate; + +// Disabling disallowing of ~, on request. +// if (fi.dirPath() == QDir::homeDirPath()) +// return Intermediate; + + return Acceptable; + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/RootValidator.h b/kpf/src/RootValidator.h new file mode 100644 index 00000000..5ac96829 --- /dev/null +++ b/kpf/src/RootValidator.h @@ -0,0 +1,47 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_ROOT_VALIDATOR_H +#define KPF_ROOT_VALIDATOR_H + +#include <qvalidator.h> + +namespace KPF +{ + /** + * Used for checking that a path input by the user is acceptable + * as a server root directory. + */ + class RootValidator : public QValidator + { + public: + + RootValidator(QObject * parent, const char * name = 0); + + virtual State validate(QString & input, int & pos) const; + }; + +} // End namespace KPF + +#endif // KPF_ROOT_VALIDATOR_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Server.cpp b/kpf/src/Server.cpp new file mode 100644 index 00000000..3e59281a --- /dev/null +++ b/kpf/src/Server.cpp @@ -0,0 +1,1137 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "Defines.h" +#include "DirectoryLister.h" +#include "WebServer.h" +#include "Server.h" +#include "ServerPrivate.h" +#include "Utils.h" + +#undef KPF_TRAFFIC_DEBUG + +namespace KPF +{ + static const uint IncomingDataLimit = 8 * 1024; // kB. + static const uint Timeout = 60 * 1000; // seconds. + static const uint MaxKeepAlive = 20; // transactions. + + Server::Server + ( + const QString & dir, + bool followSymlinks, + int socket, + WebServer * parent + ) + : QObject(parent, "Server") + { + d = new Private; + + kpfDebug << "New server: " << d->id << endl; + + d->dir = dir; + + d->followSymlinks = followSymlinks; + + d->birth = QDateTime::currentDateTime(); + + d->socket.setSocket(socket); + + connect(&(d->socket), SIGNAL(readyRead()), this, SLOT(slotReadyRead())); + + connect + ( + &(d->socket), + SIGNAL(bytesWritten(int)), + SLOT(slotBytesWritten(int)) + ); + + connect + ( + &(d->socket), + SIGNAL(connectionClosed()), + SLOT(slotConnectionClosed()) + ); + + connect + ( + &(d->idleTimer), + SIGNAL(timeout()), + SLOT(slotTimeout()) + ); + + connect + ( + &(d->readTimer), + SIGNAL(timeout()), + SLOT(slotRead()) + ); + + // If nothing happens for a bit, cancel ourselves. + + d->idleTimer.start(Timeout, true); + } + + Server::~Server() + { + delete d; + d = 0; + } + + void + Server::slotReadyRead() + { + kpfDebug << d->id << ":slotReadyRead" << endl; + + // DoS protection. + + d->dataRead += static_cast<uint>(d->socket.bytesAvailable()); + + if (d->dataRead > IncomingDataLimit) + { + kpfDebug + << d->id + << ": Read would breach limit. Assuming DoS -> finished" + << endl; + + setFinished(NoFlush /* Don't bother flushing socket */); + return; + } + + // Reset idle timer. + + d->idleTimer.start(Timeout, true); + + // Read all available data to incomingLineBuffer. + + while (d->socket.canReadLine()) + { + kpfDebug << d->id << ": socket.canReadLine" << endl; + + QString line(d->socket.readLine().stripWhiteSpace()); + +#ifdef KPF_TRAFFIC_DEBUG + kpfDebug + << d->id + << ": Adding line to incomingLineBuffer: " + << line + << endl; +#endif + + d->incomingLineBuffer.append(line); + } + + if (!d->incomingLineBuffer.isEmpty()) + { + kpfDebug + << d->id + << ": incomingLineBuffer isn't empty - calling slotRead directly" + << endl; + + slotRead(); + } + else + { + kpfDebug + << d->id + << ": incomingLineBuffer is empty. Nothing to do." + << endl; + } + } + + void + Server::slotRead() + { + kpfDebug << d->id << ": slotRead" << endl; + + if (d->incomingLineBuffer.isEmpty()) + { + kpfDebug << d->id << ": incomingLineBuffer is empty !" << endl; + return; + } + + // There is data available in incomingLineBuffer. + + switch (d->state) + { + case WaitingForRequest: + kpfDebug << d->id << ": I was waiting for a request" << endl; + (void) readRequest(d->incomingLineBuffer.first()); + d->incomingLineBuffer.remove(d->incomingLineBuffer.begin()); + break; + + case WaitingForHeaders: + kpfDebug << d->id << ": I was waiting for headers" << endl; + readHeaders(); + break; + + case Responding: + case Finished: + default: + kpfDebug << d->id << ": I was responding or finished" << endl; + break; + } + } + + bool + Server::readRequest(const QString & line) + { + ++d->requestCount; + +#ifdef KPF_TRAFFIC_DEBUG + kpfDebug + << d->id + << ": (request #" << d->requestCount << ") readRequest: `" + << line << "'" << endl; +#endif + + QStringList l(QStringList::split(' ', line)); + + // A request usually looks like METHOD PATH PROTOCOL but we don't + // require PROTOCOL - we just assume HTTP/0.9 and act accordingly. + + if (l.count() == 2) + { + kpfDebug << d->id << ": readRequest: HTTP/0.9 ???" << endl; + emit(request(this)); + d->state = Responding; + respond(400); + emit(readyToWrite(this)); + return false; + } + + // The Request object handles setting parsing the strings we pass it here. + // It converts GET/HEAD/whatever to an enum, fixes up the path and + // converts the protocol string to a number. + + d->request.setMethod (l[0]); + d->request.setPath (l[1]); + d->request.setProtocol (l.count() == 3 ? l[2] : QString::null); + + // Before we check the request, say we received it. + + emit(request(this)); + + return checkRequest(); + } + + bool + Server::checkRequest() + { + // We only handle METHOD of GET or HEAD. + + if (Request::Unsupported == d->request.method()) + { + kpfDebug << d->id << ": readRequest: method unsupported" << endl; + d->state = Responding; + respond(501); + emit(readyToWrite(this)); + return false; + } + + // If there's .. or ~ in the path, we disallow. Either there's a mistake + // or someone's trying to h@x0r us. I wouldn't have worried about ~ + // normally, because I don't do anything with it, so the resource would + // simply not be found, but I'm worried that the QDir/QFile/QFileInfo + // stuff might try to expand it, so I'm not taking any chances. + + if (d->request.path().contains("..") || d->request.path().contains('~')) + { + kpfDebug << d->id << ": readRequest: bogus path" << endl; + d->state = Responding; + respond(403); + emit(readyToWrite(this)); + return false; + } + + if (d->request.protocol() > 1.1) + { + if (d->request.protocol() >= 2.0) + { + kpfDebug + << d->id + << ": readRequest: unsupported protocol major number" + << endl; + + d->request.setProtocol(1, 1); + + d->state = Responding; + respond(505); + emit(readyToWrite(this)); + return false; + } + else + { + kpfDebug + << d->id + << ": readRequest: unsupported protocol minor number" + << endl; + + d->request.setProtocol(1, 1); + } + } + + if (d->request.protocol() >= 1.0) + { + kpfDebug + << d->id + << ": readRequest: need to wait for headers now" + << endl; + + if (d->request.protocol() > 1.0) + { + d->request.setPersist(true); + } + + d->state = WaitingForHeaders; + d->readTimer.start(0, true); + } + else + { + kpfDebug + << d->id + << ": readRequest: immediate response" + << endl; + + d->state = Responding; + prepareResponse(); + emit(readyToWrite(this)); + return true; + } + + return true; + } + + void + Server::readHeaders() + { + kpfDebug << d->id << ": readHeaders" << endl; + + // Pop lines from front of incomingLineBuffer and add to + // incomingHeaderLineBuffer until we reach the end of the headers, when we + // generate a response to the request. + + while (!d->incomingLineBuffer.isEmpty()) + { + // This would be cleaner if there was a QValueQueue. + // Basically we 'dequeue' the first line from incomingHeaderBuffer. + + QString line(d->incomingLineBuffer.first()); + d->incomingLineBuffer.remove(d->incomingLineBuffer.begin()); + + // Unless the line is empty, this is (in theory) a header. + + if (!line.isEmpty()) + { + kpfDebug << d->id << ": Header line: " << line << endl; + d->incomingHeaderLineBuffer << line; + } + else + { + kpfDebug << d->id << ": Blank line - end of headers" << endl; + + // We have a buffer filled with all the header data received. + // First parse those headers. + + d->request.parseHeaders(d->incomingHeaderLineBuffer); + + // Clear out the buffer because we won't need to use it again + // and leaving all that data in memory is pointless. + + d->incomingHeaderLineBuffer.clear(); + + // We've parsed the headers so the next thing we do is respond. + + d->state = Responding; + prepareResponse(); + + // When the response has been prepared, we're ready to write + // some data back into that socket. + + kpfDebug << d->id << ": Ready to write" << endl; + + emit(readyToWrite(this)); + + return; + } + } + + // Because we haven't found an empty line and therefore parsed + // headers + returned, we must wait for more headers. + + kpfDebug + << d->id + << ": readHeaders: No lines left in header buffer." + << " Setting state to WaitingForHeaders" + << endl; + + d->state = WaitingForHeaders; + } + + void + Server::prepareResponse() + { + // The path to the requested resource is relative to our root. + + QString filename = d->dir + '/' + d->request.path(); + + kpfDebug << d->id << ": filename: " << filename << endl; + + d->resource.setPath(d->dir, d->request.path()); + + if (!d->resource.exists()) + { + kpfDebug << d->id << ": Resource doesn't exist: %s" << filename << endl; + + // No file ? Perhaps we should give a directory listing. + + if (!(/*d->generateDirectoryListings && */ d->request.path() == "/")) + { + // Either index.html wasn't the file requested, or we're not supposed + // to generated listings. + + respond(404); + return; + } + } + + if (!d->followSymlinks && d->resource.symlink()) + { + // If we're not supposed to follow symlinks and there's a symlink + // somewhere on the path, deny. + + respond(403); + return; + } + + if (!d->resource.readable()) + { + // Deny even HEAD for unreadable files. + + respond(403); + return; + } + +// if ((Request::Get == d->request.method()) && !d->resource.open()) + // Open resource even if we're asked for HEAD. We need to ensure + // Content-Length is sent correctly. + + if (!d->resource.open()) + { + // Couldn't open the file. XXX why not ? + + respond(403); + return; + } + + if (d->request.haveRange()) + { + // There was a byte range specified in the request so handleRange() + // to check that the range makes sense for the requested resource. + + kpfDebug << d->id << ": Byte range in request" << endl; + + if (!handleRange(d->request.range())) + { + // handleRange() takes care of sending the necessary response. + return; + } + } + else + { + kpfDebug + << d->id + << "No byte range in request." + << endl; + + if (d->request.haveIfModifiedSince()) + { + // If we saw an If-Modified-Since header and the resource hasn't + // been modified since that date, we respond with '304 Not modified' + + if (toGMT(d->resource.lastModified()) <= d->request.ifModifiedSince()) + { + kpfDebug + << d->id + << "Got IfModifiedSince and will respond with 304 (unmodified)" + << endl; + + respond(304); + // We will not serve the file, so don't the size. + } + else + { + kpfDebug + << d->id + << "Got IfModifiedSince and will serve whole file (modified)" + << endl; + + // We will serve the file, so set the size. + d->fileBytesLeft = d->resource.size(); + } + } + else if (d->request.haveIfUnmodifiedSince()) + { + // As above, except the logic is reversed. + + if (toGMT(d->resource.lastModified()) > d->request.ifUnmodifiedSince()) + { + kpfDebug + << d->id + << "Got IfUnmodifiedSince and will respond with 412 (modified)" + << endl; + + respond(412); + // We not serve the file, so don't set the size. + } + else + { + kpfDebug + << d->id + << "Got IfUnmodifiedSince and will serve whole file (unmodified)" + << endl; + + // We will serve the file, so set the size. + d->fileBytesLeft = d->resource.size(); + } + } + else + { + // We will serve the file, so set the size. + d->fileBytesLeft = d->resource.size(); + } + + // If we haven't set the response up yet, that means we are not using a + // special response due to a modification time condition. Therefore we + // are doing the 'usual' 200 response. + + if (0 == d->response.code()) + respond(200, d->fileBytesLeft); + } + + kpfDebug + << d->id + << "Done setting up response. Code will be " + << responseName(d->response.code()) + << endl; + + + // Send some headers back to the client, but only if the protocol + // they asked us to use is new enough to require this. + + if (d->request.protocol() >= 1.0) + { + writeLine("Server: kpf"); + writeLine("Date: " + dateString()); + writeLine("Last-Modified: " + dateString(d->resource.lastModified())); + writeLine("Content-Type: " + d->resource.mimeType()); + + // Generate a Content-Range header if we are sending partial content. + + if (206 == d->response.code()) + { + QString line = "Content-Range: bytes "; + + line += QString::number(d->request.range().first()); + + line += '-'; + + if (d->request.range().haveLast()) + line += QString::number(d->request.range().last()); + else + line += QString::number(d->resource.size() - 1); + + line += '/'; + + line += QString::number(d->resource.size()); + + writeLine(line); + } + + writeLine("Content-Length: " + QString::number(d->fileBytesLeft)); + } + + if (d->requestCount >= MaxKeepAlive && d->request.protocol() >= 1.1) + { + // We have made many transactions on this connection. Time to + // give up and let the client re-connect. If we don't do this, + // they could keep this connection open indefinitely. + + writeLine("Connection: close"); + } + else if (d->request.protocol() == 1.0) + { + // wget seems broken. If it sends keep-alive, it hangs waiting for + // nothing at all. Ignore its keep-alive request. + writeLine("Connection: close"); + } + else if (d->request.protocol() == 1.1) { + writeLine("Connection: keep-alive"); + } + + // End of headers so send a newline. + + if (d->request.protocol() >= 1.0) + { + writeLine(""); + } + } + + bool + Server::handleRange(const ByteRange & r) + { + // Here we check if the given ByteRange makes sense for the + // requested resource. + + // Is the range just plain broken ? + + if (!r.valid()) + { + kpfDebug << d->id << ": Invalid byte range" << endl; + respond(416); + return false; + } + + // Does the range start before the end of our resource ? + + else if (r.first() > d->resource.size()) + { + kpfDebug << d->id << ": Range starts after EOF" << endl; + respond(416); + return false; + } + + // Does the range end after the end of our resource ? + + else if (r.haveLast() && r.last() > d->resource.size()) + { + kpfDebug << d->id << ": Range end after EOF" << endl; + respond(416); + return false; + } + + // Ok, in theory the range should be satisfiable ... + + else + { + // ... but maybe we can't seek to the start of the range. + + if (!d->resource.seek(r.first())) + { + kpfDebug << d->id << ": Invalid byte range (couldn't seek)" << endl; + // Should this be 501 ? + respond(416); + return false; + } + + kpfDebug << d->id << ": Ok, setting fileBytesLeft" << endl; + + // Work out how many bytes are left to send to the client. Careful + // with the off-by-one errors here, eh ? + + if (r.haveLast()) + { + d->fileBytesLeft = r.last() + 1 - r.first(); + } + else + { + d->fileBytesLeft = d->resource.size() - r.first(); + } + + kpfDebug << d->id << ": fileBytesLeft = " + << d->fileBytesLeft << "d" << endl; + + respond(206, d->fileBytesLeft); + } + + return true; + } + + void + Server::slotBytesWritten(int i) + { + // Don't you just love it when people forget 'unsigned' ? + + if (i > 0) + d->bytesWritten += i; + + emit(output(this, i)); + + // Reset idle timer. + d->idleTimer.start(Timeout, true); + } + + void + Server::slotConnectionClosed() + { + kpfDebug << d->id << ": slotConnectionClosed -> finished" << endl; + setFinished(Flush); + } + + void + Server::writeLine(const QString & line) + { + // Fill a buffer. We're not allowed to send anything out until our + // controller calls write(). + + QCString s(line.utf8() + "\r\n"); + + d->headerBytesLeft += s.length(); + d->outgoingHeaderBuffer += s; + } + + void + Server::cancel() + { + kpfDebug << d->id << ": cancel -> finished" << endl; + setFinished(NoFlush); + } + + void + Server::respond(uint code, ulong fileSize) + { + // Set the code of our Response object. + + d->response.setCode(code); + + // Request from the Response object the text that should be sent + // back to the client. + + QCString s(d->response.text(d->request)); + + // Tell our Response object how long it will be in total (it doesn't + // know its total size until we tell it about the resource size.) + + d->response.setSize(s.length() + fileSize); + + // Tell the world we've finished setting up our response. + + emit(response(this)); + + // Add the response text to the outgoing header buffer. + + d->headerBytesLeft += s.length(); + d->outgoingHeaderBuffer += s; + } + + void + Server::setFinished(FlushSelect flushSelect) + { + if (Finished == d->state) // Already finished. + return; + + d->state = Finished; + + kpfDebug + << d->id + << ": finished(" + << (Flush == flushSelect ? "flush" : "no flush") + << ")" + << endl; + + if (Flush == flushSelect) + d->socket.flush(); + + d->socket.close(); + + d->death = QDateTime::currentDateTime(); + + emit(finished(this)); + } + + QHostAddress + Server::peerAddress() const + { + return d->socket.peerAddress(); + } + + ulong + Server::bytesLeft() const + { + // Return the combined size of the two output buffers. + + return d->headerBytesLeft + d->fileBytesLeft; + } + + ulong + Server::write(ulong maxBytes) + { + // We must be in 'Responding' state here. If not, there's a problem + // in the code. + + if (Responding != d->state) + { + kpfDebug << d->id << ": write() but state != Responding -> finished"; + setFinished(Flush); + return 0; + } + + // If the socket has been closed (e.g. the remote end hung up) + // then we just give up. + + if (QSocket::Connection != d->socket.state()) + { + kpfDebug << d->id << ": Socket closed by client -> finished" << endl; + setFinished(Flush); + return 0; + } + + kpfDebug << d->id << ": Response code is " << d->response.code() << " (" + << responseName(d->response.code()) << ")" << endl; + + ulong bytesWritten = 0; + + // Write header data. + + ulong headerBytesWritten = 0; + + if (!writeHeaderData(maxBytes, headerBytesWritten)) + { + return 0; + } + + maxBytes -= headerBytesWritten; + bytesWritten += headerBytesWritten; + + // If we are only sending headers (response code != 2xx or request type + // was HEAD) or we reached the end of the file we were sending, give up. + + if (d->response.code() < 200 || d->response.code() > 299) + { + kpfDebug << d->id << ": We are only sending headers -> finished" << endl; + + // If we're sending 'Not modified' then we don't need to drop + // the connection just yet. + + if (d->response.code() == 304 && d->request.persist()) + { + kpfDebug + << d->id + << ": 304 and persist. Not dropping connection yet." + << endl; + + reset(); + } + else + { + setFinished(Flush); + } + + return bytesWritten; + } + + // Just HEAD ? Ok, then if we're set to persistent mode we go back + // and wait for another request. Otherwise we're done and can go home. + + if (Request::Head == d->request.method()) + { + if (d->request.persist()) + { + reset(); + } + else + { + setFinished(Flush); + } + + return bytesWritten; + } + + // If we've written our limit then wait until next time. + + if (0 == maxBytes) + { + return bytesWritten; + } + + // Write resource data. + + ulong fileBytesWritten = 0; + + // writeFileData() returns true if the op was successful and also + // returns the number of bytes written via the second parameter. + + if (!writeFileData(maxBytes, fileBytesWritten)) + { + return 0; + } + + kpfDebug << "Wrote " << fileBytesWritten << " from file" << endl; + + maxBytes -= fileBytesWritten; + bytesWritten += fileBytesWritten; + + // Did we finish sending the resource data ? + + if (0 == d->fileBytesLeft) + { + kpfDebug << d->id << ": No bytes left to write. Closing file." << endl; + + d->resource.close(); + + // If we're in persistent mode, don't quit just yet. + + if (d->requestCount < MaxKeepAlive && d->request.persist()) + { + kpfDebug + << d->id + << ": Request included Keep-Alive, so we set state" + << " to WaitingForRequest and don't send finished()" + << endl; + + reset(); + } + else + { + kpfDebug + << d->id + << ": No keep-alive or hit MaxKeepAlive, so finished." + << endl; + + setFinished(Flush); + } + } + else + { + // Ok, we have some data to send over the socket. Tell the world. + + kpfDebug + << d->id + << "Still have data left to send." + << endl; + + emit(readyToWrite(this)); + } + + return bytesWritten; + } + + + bool + Server::writeHeaderData(ulong max, ulong & bytesWritten) + { + // Is there some header data left to write ? + + if (0 == d->headerBytesLeft) + return true; + + // Calculate where to start reading from the buffer. + + uint headerPos = + d->outgoingHeaderBuffer.length() - d->headerBytesLeft; + + // Calculate how many bytes we should write this session. + + uint bytesToWrite = min(d->headerBytesLeft, max); + + // Calculate how many bytes we _may_ write. + + bytesToWrite = min(bytesToWrite, d->socket.outputBufferLeft()); + + // Get a pointer to the data, offset by the position we start reading. + + char * data = d->outgoingHeaderBuffer.data() + headerPos; + + // Write the data, or at least as much as the socket buffers will + // take, and remember how much we wrote. + + int headerBytesWritten = d->socket.writeBlock(data, bytesToWrite); + + // <rant> + // Using -1 to signal an error is fucking evil. + // Return false instead or add a 'bool & ok' parameter. + // If you're not going to use exceptions, at least don't use + // crap C conventions for error handling. + // </rant> + + if (-1 == headerBytesWritten) + { + // Socket error. + + kpfDebug << d->id << ": Socket error -> finished" << endl; + setFinished(Flush); + return false; + } + +#ifdef KPF_TRAFFIC_DEBUG + kpfDebug + << d->id + << ": Wrote header data: `" + << d->outgoingHeaderBuffer.left(headerPos) + << "'" + << endl; +#endif + + // Subtract the number of bytes we wrote from the number of bytes + // left to write. + + bytesWritten += headerBytesWritten; + d->headerBytesLeft -= headerBytesWritten; + + // We may be doing a long file send next, so clear the header buffer + // because we don't need that data hanging around in memory anymore. + + if (0 == d->headerBytesLeft) + d->outgoingHeaderBuffer.resize(0); + + return true; + } + + bool + Server::writeFileData(ulong maxBytes, ulong & bytesWritten) + { + // Nothing left in the file ? + + if (d->resource.atEnd()) + { + d->resource.close(); + kpfDebug << d->id << ": file at end -> finished" << endl; + setFinished(Flush); + return false; + } + + // Calculate how much data we may write this session. + // If none, give up. + + uint bytesToWrite = min(d->fileBytesLeft, maxBytes); + + if (0 == bytesToWrite) + return true; + + bytesToWrite = min(bytesToWrite, d->socket.outputBufferLeft()); + + QByteArray a(bytesToWrite); + + if (0 == bytesToWrite) + return true; + + // Read some data (maximum = bytesToWrite) from the file. + + int fileBytesRead = d->resource.readBlock(a.data(), bytesToWrite); + + // Write that data to the socket and remember how much was actually + // written (may be less than requested if socket buffers are full.) + + int fileBytesWritten = d->socket.writeBlock(a.data(), fileBytesRead); + + // Was there an error writing to the socket ? + + if (-1 == fileBytesWritten) + { + // Socket error. + kpfDebug << d->id << ": Socket error -> finished" << endl; + d->resource.close(); + setFinished(Flush); + return false; + } + +#ifdef KPF_TRAFFIC_DEBUG + kpfDebug + << d->id + << ": Wrote file data: `" + << QCString(a.data(), fileBytesWritten) + << "'" + << endl; +#endif + + // We should have been able to write the full amount to the socket, + // because we tested d->socket.outputBufferLeft(). If we didn't + // manage to write that much, either we have a bug or QSocket does. + + if (fileBytesWritten < fileBytesRead) + { + kpfDebug << d->id << ": Short write !" << endl; + d->resource.close(); + setFinished(Flush); + return false; + } + + // Subtract the amount of bytes written from the number left to write. + + bytesToWrite -= fileBytesWritten; + bytesWritten += fileBytesWritten; + d->fileBytesLeft -= fileBytesWritten; + + return true; + } + + void + Server::slotTimeout() + { + kpfDebug << d->id << ": Timeout -> finished" << endl; + setFinished(NoFlush); + } + + Request + Server::request() const + { + return d->request; + } + + Response + Server::response() const + { + return d->response; + } + + ulong + Server::output() const + { + return d->bytesWritten; + } + + Server::State + Server::state() const + { + return d->state; + } + + QDateTime + Server::birth() const + { + return d->birth; + } + + QDateTime + Server::death() const + { + return d->death; + } + + void + Server::reset() + { + kpfDebug << d->id << ": Resetting for another request" << endl; + + d->request .clear(); + d->response .clear(); + d->resource .clear(); + + d->state = WaitingForRequest; + d->readTimer.start(0, true); + } + +} // End namespace KPF + +#include "Server.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Server.h b/kpf/src/Server.h new file mode 100644 index 00000000..8d26a167 --- /dev/null +++ b/kpf/src/Server.h @@ -0,0 +1,186 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_SERVER_H +#define KPF_SERVER_H + +#include <qdatetime.h> +#include <qstring.h> +#include <qhostaddress.h> + +#include "Request.h" +#include "Response.h" + +namespace KPF +{ + class WebServer; + + /** + * Converses with a remote client. Handles requests and generates responses. + * Bandwidth is controlled by parent (WebServer). + */ + class Server : public QObject + { + Q_OBJECT + + public: + + /** + * A Server can be in one of four states, enumerated here. + */ + enum State { WaitingForRequest, WaitingForHeaders, Responding, Finished }; + + /** + * @param dir Root directory. Files not contained in this directory + * and subdirectories thereof will not be server. + * + * @param followSymlinks If false, an expensive algorithm will ensure + * that no symbolic links are present anywhere in the path from / + * to the requested resource. + * + * @param socket The system's socket device, used for communication. + * + * @param parent A WebServer, which will manage this Server. + */ + Server + ( + const QString & dir, + bool followSymlinks, + int socket, + WebServer * parent + ); + + /** + * Free internal data and close connection if still open. + */ + virtual ~Server(); + + /** + * @return address of client that has connected to us. + */ + QHostAddress peerAddress() const; + + /** + * @return Request object associated with this connection. + * When persistent connections are used, the object may + * differ depending on the current state. + */ + Request request() const; + + /** + * @return Response object associated with this connection. + * When persistent connections are used, the object may + * differ depending on the current state. + */ + Response response() const; + + /** + * @return number of bytes sent out over the socket since this object + * was created. + */ + ulong output() const; + + /** + * @return current state. + * @see State. + */ + State state() const; + + /** + * @return date and time this object was created. + */ + QDateTime birth() const; + + /** + * @return date and time all activity was completed. + */ + QDateTime death() const; + + /** + * @return number of bytes remaining to send to client. + */ + ulong bytesLeft() const; + + /** + * Send no more than maxBytes to the client. + * + * @return number of bytes sent. + */ + ulong write(ulong maxBytes); + + /** + * Stop negotiating with client and sending data. Emit @ref finished. + * Do not flush output. + */ + void cancel(); + + protected slots: + + void slotReadyRead (); + void slotRead (); + void slotBytesWritten (int); + void slotConnectionClosed (); + void slotTimeout (); + + signals: + + void readyToWrite (Server *); + void output (Server *, ulong); + void finished (Server *); + void response (Server *); + void request (Server *); + + private: + + // Disable copying. + + Server(const Server &); + Server & operator = (const Server &); + + enum FlushSelect + { + Flush, + NoFlush + }; + + void setFinished (FlushSelect); + void writeLine (const QString &); + void prepareResponse (); + void respond (uint code, ulong size = 0); + bool readRequest (const QString &); + bool checkRequest (); + void handleRequest (); + void readHeaders (); + bool handleRange (const ByteRange &); + bool writeHeaderData (ulong, ulong &); + bool writeFileData (ulong, ulong &); + void reset (); + + class Private; + Private * d; + }; + +} // End namespace KPF + +#endif // KPF_SERVER_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ServerPrivate.cpp b/kpf/src/ServerPrivate.cpp new file mode 100644 index 00000000..079fb29b --- /dev/null +++ b/kpf/src/ServerPrivate.cpp @@ -0,0 +1,54 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "Defines.h" +#include "ServerPrivate.h" +#include "Defaults.h" + +namespace KPF +{ + ulong Server::Private::ID = 0L; + + Server::Private::Private() + : socket (0, "KPF::Server::Private.socket"), + state (WaitingForRequest), + bytesWritten (0L), + headerBytesLeft (0L), + fileBytesLeft (0L), + dataRead (0L), + followSymlinks (Config::DefaultFollowSymlinks), + generateDirectoryListings (false), + requestCount (0), + id (ID++) + { + // Empty. + } + + Server::Private::~Private() + { + // Empty. + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ServerPrivate.h b/kpf/src/ServerPrivate.h new file mode 100644 index 00000000..fcf7fd39 --- /dev/null +++ b/kpf/src/ServerPrivate.h @@ -0,0 +1,78 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_SERVER_PRIVATE_H +#define KPF_SERVER_PRIVATE_H + +#include <qcstring.h> +#include <qtimer.h> + +#include "ServerSocket.h" +#include "Server.h" +#include "Request.h" +#include "Response.h" +#include "Resource.h" + +namespace KPF +{ + /** + * Data for Server class. Kept here to speed up recompilation. + */ + class Server::Private + { + public: + + Private(); + ~Private(); + + // Order dependency + ServerSocket socket; + Server::State state; + ulong bytesWritten; + ulong headerBytesLeft; + ulong fileBytesLeft; + ulong dataRead; + bool followSymlinks; + bool generateDirectoryListings; + uint requestCount; + // End order dependency + + QString dir; + Request request; + Response response; + Resource resource; + QStringList incomingHeaderLineBuffer; + QStringList incomingLineBuffer; + QDateTime birth; + QDateTime death; + QCString outgoingHeaderBuffer; + QTimer idleTimer; + QTimer readTimer; + ulong id; + static ulong ID; + }; + +} // End namespace KPF + +#endif // KPF_SERVER_PRIVATE_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ServerSocket.cpp b/kpf/src/ServerSocket.cpp new file mode 100644 index 00000000..9f39ac57 --- /dev/null +++ b/kpf/src/ServerSocket.cpp @@ -0,0 +1,46 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qsocketdevice.h> + +#include "Defines.h" +#include "ServerSocket.h" + +namespace KPF +{ + ServerSocket::ServerSocket(QObject * parent, const char * name) + : QSocket(parent, name) + { + // Empty. + } + + uint + ServerSocket::outputBufferLeft() + { + return socketDevice()->sendBufferSize() - bytesToWrite(); + } + +} // End namespace KPF + +// vim:ts=2:sw=2:tw=78:et + diff --git a/kpf/src/ServerSocket.h b/kpf/src/ServerSocket.h new file mode 100644 index 00000000..14a2e2d8 --- /dev/null +++ b/kpf/src/ServerSocket.h @@ -0,0 +1,47 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_SERVER_SOCKET_H +#define KPF_SERVER_SOCKET_H + +#include <qsocket.h> + +namespace KPF +{ + /** + * Used to help calculate how much of a QSocket's output buffer we can + * use before data will be sent out. + */ + class ServerSocket : public QSocket + { + public: + + ServerSocket(QObject * parent, const char * name = 0); + + uint outputBufferLeft(); + }; + +} // End namespace KPF + +#endif // KPF_SERVER_SOCKET_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ServerWizard.cpp b/kpf/src/ServerWizard.cpp new file mode 100644 index 00000000..8ca47e22 --- /dev/null +++ b/kpf/src/ServerWizard.cpp @@ -0,0 +1,410 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qlabel.h> +#include <qlayout.h> +#include <qspinbox.h> +#include <qdir.h> +#include <qptrlist.h> +#include <qlineedit.h> + +#include <kapplication.h> +#include <klineedit.h> +#include <kdialog.h> +#include <klocale.h> +#include <kurlrequester.h> +#include <kfiledialog.h> + +#include "Defines.h" +#include "ServerWizard.h" +#include "WebServerManager.h" +#include "WebServer.h" +#include "Help.h" +#include <dnssd/servicebrowser.h> + +#include <unistd.h> + +namespace KPF +{ + ServerWizard::ServerWizard(QWidget * parent) + : KWizard(parent, "KPF::ServerWizard", true) + { + setCaption(i18n("New Server - %1").arg("kpf")); + + page1_ = new QWidget(this); + page2_ = new QWidget(this); + page3_ = new QWidget(this); +// page4_ = new QWidget(this); + page5_ = new QWidget(this); + + QLabel * l_rootDirectoryHelp = + new QLabel + ( + i18n + ( + "<p>" + "Specify the directory which contains the files" + " you wish to share." + "</p>" + "<p>" + "<em>Warning</em>: Do not share any directories that contain" + " sensitive information!" + "</p>" + ), + page1_ + ); + + QLabel * l_listenPortHelp = + new QLabel + ( + i18n + ( + "<p>" + "Specify the network `port' on which the server should" + " listen for connections." + "</p>" + ), + page2_ + ); + + QLabel * l_bandwidthLimitHelp = + new QLabel + ( + i18n + ( + "<p>" + "Specify the maximum amount of data (in kilobytes) that will be" + " sent out per second." + "</p>" + "<p>" + "This allows you to keep some bandwidth for yourself instead" + " of allowing connections with kpf to hog your connection." + "</p>" + ), + page3_ + ); +/* + QLabel * l_connectionLimitHelp = + new QLabel + ( + i18n + ( + "<p>" + "Specify the maximum number of connections allowed at" + " any one time." + "</p>" + ), + page4_ + ); +*/ + bool canPublish = DNSSD::ServiceBrowser::isAvailable() == DNSSD::ServiceBrowser::Working; + QLabel * l_serverNameHelp = + new QLabel + ( + KPF::HelpText::getServerNameHelp(), + page5_ + ); + + QLabel * l_root_ = + new QLabel(i18n("&Root directory:"), page1_); + + QLabel * l_listenPort_ = + new QLabel(i18n("&Listen port:"), page2_); + + QLabel * l_bandwidthLimit_ = + new QLabel(i18n("&Bandwidth limit:"), page3_); + +// QLabel * l_connectionLimit_ = +// new QLabel(i18n("Connection &limit"), page4_); + + QLabel * l_serverName_ = + new QLabel(i18n("&Server name:"), page5_); + + if(!canPublish) + l_serverName_->setEnabled(false); + + kur_root_ = new KURLRequester(page1_); + + sb_listenPort_ = new QSpinBox(1, 65535, 1, page2_); + + sb_bandwidthLimit_ = new QSpinBox(1, 999999, 1, page3_); +// sb_connectionLimit_ = new QSpinBox(1, 9999, 1, page4_); + + char hostname[255]; + gethostname(hostname, 255-2); + hostname[255-1]=0; + le_serverName_ = new QLineEdit(hostname, page5_); + + if(!canPublish) + le_serverName_->setEnabled(false); + + l_root_ ->setBuddy(kur_root_); + l_listenPort_ ->setBuddy(sb_listenPort_); + l_bandwidthLimit_ ->setBuddy(sb_bandwidthLimit_); + l_serverName_ ->setBuddy(le_serverName_); +// l_connectionLimit_ ->setBuddy(sb_connectionLimit_); + + sb_listenPort_ + ->setValue(WebServerManager::instance()->nextFreePort()); + + sb_bandwidthLimit_ ->setValue(Config::DefaultBandwidthLimit); + sb_bandwidthLimit_ ->setSuffix(i18n(" kB/s")); +// sb_connectionLimit_ ->setValue(Config::DefaultConnectionLimit); + + QVBoxLayout * layout1 = + new QVBoxLayout(page1_, KDialog::marginHint(), KDialog::spacingHint()); + + QVBoxLayout * layout2 = + new QVBoxLayout(page2_, KDialog::marginHint(), KDialog::spacingHint()); + + QVBoxLayout * layout3 = + new QVBoxLayout(page3_, KDialog::marginHint(), KDialog::spacingHint()); + +// QVBoxLayout * layout4 = +// new QVBoxLayout(page4_, KDialog::marginHint(), KDialog::spacingHint()); + + QVBoxLayout * layout5 = + new QVBoxLayout(page5_, KDialog::marginHint(), KDialog::spacingHint()); + + layout1->addWidget(l_rootDirectoryHelp); + layout2->addWidget(l_listenPortHelp); + layout3->addWidget(l_bandwidthLimitHelp); +// layout4->addWidget(l_connectionLimitHelp); + layout5->addWidget(l_serverNameHelp); + + QHBoxLayout * layout10 = new QHBoxLayout(layout1); + + layout10->addWidget(l_root_); + layout10->addWidget(kur_root_); + + layout1->addStretch(1); + + QHBoxLayout * layout20 = new QHBoxLayout(layout2); + + layout20->addWidget(l_listenPort_); + layout20->addWidget(sb_listenPort_); + + layout2->addStretch(1); + + QHBoxLayout * layout30 = new QHBoxLayout(layout3); + + layout30->addWidget(l_bandwidthLimit_); + layout30->addWidget(sb_bandwidthLimit_); + + layout3->addStretch(1); + +// QHBoxLayout * layout40 = new QHBoxLayout(layout4); + +// layout40->addWidget(l_connectionLimit_); +// layout40->addWidget(sb_connectionLimit_); + +// layout4->addStretch(1); + + QHBoxLayout * layout50 = new QHBoxLayout(layout5); + + layout50->addWidget(l_serverName_); + layout50->addWidget(le_serverName_); + + addPage(page1_, i18n("Root Directory")); + addPage(page2_, i18n("Listen Port")); + addPage(page3_, i18n("Bandwidth Limit")); +// addPage(page4_, i18n("Connection Limit")); + addPage(page5_, i18n("Server Name")); + + kur_root_->setURL(QDir::homeDirPath() + "/public_html"); + kur_root_->setMode + (KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); + +// setFinishEnabled(page4_, true); + setFinishEnabled(page5_, true); + + // This slot sets the 'Next' button on page 1 to enabled/disabled + // depending on whether the path is OK. + + connect + ( + kur_root_, + SIGNAL(textChanged(const QString &)), + SLOT(slotServerRootChanged(const QString &)) + ); + + // Used for setting the caption on the file dialog. + + connect + ( + kur_root_, + SIGNAL(openFileDialog(KURLRequester *)), + SLOT(slotOpenFileDialog(KURLRequester *)) + ); + + // This slot sets the 'Next' button on page 2 to enabled/disabled + // depending on whether the port is OK. + + connect + ( + sb_listenPort_, + SIGNAL(valueChanged(int)), + SLOT(slotListenPortChanged(int)) + ); + + // Update 'Next' button on page 1. + + slotServerRootChanged(kur_root_->url()); + + // Update 'Next' button on page 2. + + slotListenPortChanged(sb_listenPort_->value()); + } + + ServerWizard::~ServerWizard() + { + } + + void + ServerWizard::setLocation(const QString & location) + { + kur_root_->setURL(location); + } + + QString + ServerWizard::root() const + { + return kur_root_->url(); + } + + uint + ServerWizard::listenPort() const + { + return sb_listenPort_->value(); + } + + uint + ServerWizard::bandwidthLimit() const + { + return sb_bandwidthLimit_->value(); + } + QString + + ServerWizard::serverName() const + { + return le_serverName_->text(); + } + + uint + ServerWizard::connectionLimit() const + { + return Config::DefaultConnectionLimit; // sb_connectionLimit_->value(); + } + + void + ServerWizard::accept() + { + QWizard::accept(); + emit(dying(this)); + } + + void + ServerWizard::reject() + { + QWizard::reject(); + emit(dying(this)); + } + + void + ServerWizard::slotListenPortChanged(int newPort) + { + if (newPort <= 1024) + { + setNextEnabled(page2_, false); + return; + } + + QPtrList<WebServer> + serverList(WebServerManager::instance()->serverListLocal()); + + for (QPtrListIterator<WebServer> it(serverList); it.current(); ++it) + { + if (it.current()->listenPort() == uint(newPort)) + { + setNextEnabled(page2_, false); + return; + } + } + + setNextEnabled(page2_, true); + } + + void + ServerWizard::slotServerRootChanged(const QString & _root) + { + QString root(_root); + + kpfDebug << root << endl; + + // Duplicate ? + + if (WebServerManager::instance()->hasServer(root)) + { + kpfDebug << "Already have a server at " << root << endl; + setNextEnabled(page1_, false); + return; + } + + if ("/" != root.right(1)) + root += "/"; + + QFileInfo fi(root); + + if (!fi.isDir()) // || (fi.dirPath() == QDir::homeDirPath())) + { + kpfDebug << root << " isn't a dir" << endl; //, or it's $HOME" << endl; + setNextEnabled(page1_, false); + return; + } + + setNextEnabled(page1_, true); + } + + void + ServerWizard::slotOpenFileDialog(KURLRequester * urlRequester) + { + KFileDialog * fileDialog = urlRequester->fileDialog(); + + if (0 == fileDialog) + { + kpfDebug << "URL requester's file dialog is 0" << endl; + return; + } + + fileDialog->setCaption(i18n("Choose Directory to Share - %1").arg("kpf")); + } + + void + ServerWizard::help() + { + kapp->invokeHelp("share-config", "kpf"); + } +} + +#include "ServerWizard.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/ServerWizard.h b/kpf/src/ServerWizard.h new file mode 100644 index 00000000..a484b8eb --- /dev/null +++ b/kpf/src/ServerWizard.h @@ -0,0 +1,90 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_SERVER_WIZARD_H +#define KPF_SERVER_WIZARD_H + +#include <kwizard.h> + +class QSpinBox; +class KURLRequester; +class QLineEdit; + +namespace KPF +{ + /** + * Used to allow easy creation and configuration of a WebServer object. + */ + class ServerWizard : public KWizard + { + Q_OBJECT + + public: + + ServerWizard(QWidget * parent = 0); + + virtual ~ServerWizard(); + + void setLocation(const QString &); + + QString root() const; + uint listenPort() const; + uint bandwidthLimit() const; + uint connectionLimit() const; + QString serverName() const; + + signals: + + void dying(ServerWizard *); + + protected slots: + + virtual void accept(); + virtual void reject(); + void slotServerRootChanged(const QString &); + void slotListenPortChanged(int); + void slotOpenFileDialog(KURLRequester *); + + protected: + + virtual void help(); + + private: + + + KURLRequester * kur_root_; + QSpinBox * sb_listenPort_; + QSpinBox * sb_bandwidthLimit_; + QSpinBox * sb_connectionLimit_; + QLineEdit * le_serverName_; + + QWidget * page1_; + QWidget * page2_; + QWidget * page3_; + QWidget * page4_; + QWidget * page5_; + }; +} + +#endif // KPF_SERVER_WIZARD_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/SingleServerConfigDialog.cpp b/kpf/src/SingleServerConfigDialog.cpp new file mode 100644 index 00000000..6fa1792b --- /dev/null +++ b/kpf/src/SingleServerConfigDialog.cpp @@ -0,0 +1,98 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <klocale.h> + +#include "Defines.h" +#include "WebServer.h" +#include "ConfigDialogPage.h" +#include "SingleServerConfigDialog.h" + +namespace KPF +{ + SingleServerConfigDialog::SingleServerConfigDialog + ( + WebServer * server, + QWidget * parent + ) + : KDialogBase + ( + parent, + "KPF::SingleServerConfigDialog", + false, + i18n("Configuring Server %1 - kpf").arg(server->root()), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, + true + ), + server_(server) + { + widget_ = new ConfigDialogPage(server_, this); + + connect + ( + widget_, + SIGNAL(ok(bool)), + SLOT(slotOk(bool)) + ); + + setMainWidget(widget_); + + connect(this, SIGNAL(finished()), SLOT(slotFinished())); + + widget_->checkOk(); + } + + SingleServerConfigDialog::~SingleServerConfigDialog() + { + // Empty. + } + + WebServer * + SingleServerConfigDialog::server() + { + return server_; + } + + void + SingleServerConfigDialog::accept() + { + widget_->save(); + KDialogBase::accept(); + } + + void + SingleServerConfigDialog::slotFinished() + { + emit(dying(this)); + } + + void + SingleServerConfigDialog::slotOk(bool ok) + { + enableButtonOK(ok); + } +} + +#include "SingleServerConfigDialog.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/SingleServerConfigDialog.h b/kpf/src/SingleServerConfigDialog.h new file mode 100644 index 00000000..a6f1c17f --- /dev/null +++ b/kpf/src/SingleServerConfigDialog.h @@ -0,0 +1,68 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_SINGLE_SERVER_CONFIG_DIALOG_H +#define KPF_SINGLE_SERVER_CONFIG_DIALOG_H + +#include <kdialogbase.h> + +namespace KPF +{ + class WebServer; + class ConfigDialogPage; + + /** + * Used to allow configuration of a WebServer object. + */ + class SingleServerConfigDialog : public KDialogBase + { + Q_OBJECT + + public: + + SingleServerConfigDialog(WebServer *, QWidget * parent); + + virtual ~SingleServerConfigDialog(); + + WebServer * server(); + + protected slots: + + void slotFinished(); + void accept(); + void slotOk(bool); + + signals: + + void dying(SingleServerConfigDialog *); + + private: + + WebServer * server_; + + ConfigDialogPage * widget_; + }; +} + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/StartingKPFDialog.cpp b/kpf/src/StartingKPFDialog.cpp new file mode 100644 index 00000000..19b073bd --- /dev/null +++ b/kpf/src/StartingKPFDialog.cpp @@ -0,0 +1,139 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <qlayout.h> +#include <qlabel.h> +#include <qframe.h> +#include <qtimer.h> + +#include <dcopclient.h> +#include <kapplication.h> +#include <klocale.h> + +#include "Defines.h" +#include "StartingKPFDialog.h" + +namespace KPF +{ + class StartingKPFDialog::Private + { + public: + + Private() + { + // Empty. + } + + QTimer timer; + }; + + StartingKPFDialog::StartingKPFDialog(QWidget * parent) + : + KDialogBase + ( + parent, + "StartingKPFDialog", + true, /* modal */ + i18n("Starting KDE public fileserver applet"), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Cancel, + true + ) + { + d = new Private; + + QFrame * mainWidget = makeMainWidget(); + + QLabel * about = + new QLabel + ( + i18n("Starting kpf..."), + mainWidget + ); + + QVBoxLayout * layout = new QVBoxLayout(mainWidget); + + layout->addWidget(about); + + kapp->dcopClient()->setNotifications(true); + + connect + ( + kapp->dcopClient(), + SIGNAL(applicationRegistered(const QCString &)), + SLOT(slotApplicationRegistered(const QCString &)) + ); + + kapp->dcopClient() + ->send("kicker", "default", "addApplet(QString)", "kpfapplet.desktop"); + + connect(&d->timer, SIGNAL(timeout()), SLOT(slotTimeout())); + + enableButtonOK(false); + enableButtonCancel(true); + + d->timer.start(8 * 1000 /* 8 seconds */, true /* single shot */); + } + + StartingKPFDialog::~StartingKPFDialog() + { + delete d; + d = 0; + } + + void + StartingKPFDialog::slotTimeout() + { + enableButtonOK(true); + enableButtonCancel(false); + + if (kpfRunning()) + { + kpfDebug << "slotTimeout: kpf is running now" << endl; + } + else + { + kpfDebug << "slotTimeout: kpf still not running" << endl; + } + } + + bool + StartingKPFDialog::kpfRunning() + { + return kapp->dcopClient()->isApplicationRegistered("kpf"); + } + + void + StartingKPFDialog::slotApplicationRegistered(const QCString & id) + { + if ("kpf" == id) + { + kpfDebug << "kpf just started up" << endl; + enableButtonOK(true); + enableButtonCancel(false); + } + } +} + +#include "StartingKPFDialog.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/StartingKPFDialog.h b/kpf/src/StartingKPFDialog.h new file mode 100644 index 00000000..cbeca549 --- /dev/null +++ b/kpf/src/StartingKPFDialog.h @@ -0,0 +1,62 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_STARTING_KPF_DIALOG_H +#define KPF_STARTING_KPF_DIALOG_H + +#include <kdialogbase.h> + +namespace KPF +{ + /** + * Used to provide a simply display which informs the user that an attempt + * to start the kpf applet is being made. + */ + class StartingKPFDialog : public KDialogBase + { + Q_OBJECT + + public: + + StartingKPFDialog(QWidget * parent); + + virtual ~StartingKPFDialog(); + + protected slots: + + void slotTimeout(); + void slotApplicationRegistered(const QCString &); + + protected: + + bool kpfRunning(); + + private: + + class Private; + Private * d; + }; +} + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/System.cpp b/kpf/src/System.cpp new file mode 100644 index 00000000..f8f2c78b --- /dev/null +++ b/kpf/src/System.cpp @@ -0,0 +1,71 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifdef __unix__ + +// System includes +#include <signal.h> // For signal(2). +#include <unistd.h> // For get?uid. +#include <sys/types.h> // For get?uid. + +namespace kpf +{ + void blockSigPipe() + { + ::signal(SIGPIPE, SIG_IGN); + } + + int userId() + { + return ::getuid(); + } + + int effectiveUserId() + { + return ::geteuid(); + } +} + +#else // non-UNIX + +namespace kpf +{ + void blockSigPipe() + { + return; + } + + int userId() + { + return -1; + } + + int effectiveUserId() + { + return -1; + } + +} + +#endif + diff --git a/kpf/src/System.h b/kpf/src/System.h new file mode 100644 index 00000000..b740e0c7 --- /dev/null +++ b/kpf/src/System.h @@ -0,0 +1,35 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_SYSTEM_H +#define KPF_SYSTEM_H + +namespace kpf +{ + void blockSigPipe(); + + int userId(); + int effectiveUserId(); +} + +#endif diff --git a/kpf/src/Utils.cpp b/kpf/src/Utils.cpp new file mode 100644 index 00000000..a867a998 --- /dev/null +++ b/kpf/src/Utils.cpp @@ -0,0 +1,414 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include <ctime> +#include <clocale> + +#include <qfile.h> +#include <qregexp.h> + +#include <klocale.h> + +#include "Defines.h" +#include "Utils.h" + +namespace KPF +{ + static bool dateInitDone = false; + + static QStringList monthList; + + static const char rfc1123Format[] = "%a, %d %b %Y %H:%M:%S GMT"; + + static time_t qDateTimeToTimeT(const QDateTime & t) + { + struct tm tempTm; + + tempTm.tm_year = t.date().year(); + tempTm.tm_mon = t.date().month(); + tempTm.tm_mday = t.date().day(); + tempTm.tm_hour = t.time().hour(); + tempTm.tm_min = t.time().minute(); + tempTm.tm_sec = t.time().second(); + tempTm.tm_isdst = -1; + + // Fix up differences between QDateTime and tm. + + tempTm.tm_year -= 1900; + + --tempTm.tm_mon; + + return ::mktime(&tempTm); + } + + QDateTime + toGMT(const QDateTime & dt) + { + time_t dtAsTimeT = qDateTimeToTimeT(dt); + + struct tm * dtAsGmTm = ::gmtime(&dtAsTimeT); + + if (0 == dtAsGmTm) + return QDateTime(); + + time_t dtAsGmTimeT = ::mktime(dtAsGmTm); + + QDateTime ret; + + ret.setTime_t(dtAsGmTimeT); + + return ret; + } + + void dateInit() + { + if (dateInitDone) + return; + + dateInitDone = true; + + monthList + << "Jan" + << "Feb" + << "Mar" + << "Apr" + << "May" + << "Jun" + << "Jul" + << "Aug" + << "Sep" + << "Oct" + << "Nov" + << "Dec" + ; + } + + QString dateString() + { + return dateString(QDateTime::currentDateTime()); + } + + + QString dateString(const QDateTime & t) + { + time_t asTimeT = qDateTimeToTimeT(t); + + struct tm * asTm = ::gmtime(&asTimeT); + + if (0 == asTm) + { + kpfDebug << "::gmtime() failed" << endl; + return QString::null; + } + + asTm->tm_isdst = -1; + + const int len = 128; + + char buf[len]; + + // Ensure we use locale "C" for strftime. + + QCString oldLC_ALL = ::strdup(::setlocale(LC_TIME, "C")); + QCString oldLC_TIME = ::strdup(::setlocale(LC_ALL, "C")); + { + ::strftime(static_cast<char *>(buf), len, rfc1123Format, asTm); + } + ::setlocale(LC_TIME, oldLC_TIME.data()); + ::setlocale(LC_ALL, oldLC_ALL.data()); + + return QString::fromUtf8(buf); + } + + bool + parseDate(const QString & s, QDateTime & dt) + { + dateInit(); + +// kpfDebug << "Parsing date `" << s << "'" << endl; + + QStringList l(QStringList::split(' ', s)); + + switch (l.count()) + { + case 4: +// kpfDebug << "Date type is RFC 850" << endl; + return parseDateRFC850(l, dt); + break; + case 5: +// kpfDebug << "Date type is asctime" << endl; + return parseDateAscTime(l, dt); + break; + case 6: +// kpfDebug << "Date type is RFC1123" << endl; + return parseDateRFC1123(l, dt); + break; + default: +// kpfDebug << "Date type is unknown" << endl; + return false; + break; + } + + return false; + } + + bool + parseDateRFC1123(const QStringList & l, QDateTime & dt) + { + if ("GMT" != l[5]) + return false; + + uint day(l[1].toUInt()); + + bool haveMonth = false; + uint month = 0; + + QStringList::ConstIterator it; + + for (it = monthList.begin(); it != monthList.end(); ++it) + { + if (*it == l[2]) + { + haveMonth = true; + break; + } + + ++month; + } + + if (!haveMonth) + return false; + + uint year(l[3].toUInt()); + + QStringList timeTokenList(QStringList::split(':', l[4])); + + if (3 != timeTokenList.count()) + return false; + + uint hours (timeTokenList[0].toUInt()); + uint minutes (timeTokenList[1].toUInt()); + uint seconds (timeTokenList[2].toUInt()); + + dt.setDate(QDate(year, month + 1, day)); + dt.setTime(QTime(hours, minutes, seconds)); + + return dt.isValid(); + } + + bool + parseDateRFC850(const QStringList & l, QDateTime & dt) + { + if ("GMT" != l[3]) + return false; + + QStringList dateTokenList(QStringList::split('-', l[1])); + + if (3 != dateTokenList.count()) + return false; + + uint day(dateTokenList[0].toUInt()); + + bool haveMonth = false; + uint month = 0; + + QStringList::ConstIterator it; + + for (it = monthList.begin(); it != monthList.end(); ++it) + { + if (*it == dateTokenList[1]) + { + haveMonth = true; + break; + } + + ++month; + } + + if (!haveMonth) + return false; + + uint year(dateTokenList[2].toUInt()); + + if (year < 50) + year += 2000; + else if (year < 100) + year += 1900; + + QStringList timeTokenList(QStringList::split(':', l[2])); + + if (3 != timeTokenList.count()) + return false; + + uint hours (timeTokenList[0].toUInt()); + uint minutes (timeTokenList[1].toUInt()); + uint seconds (timeTokenList[2].toUInt()); + + dt.setDate(QDate(year, month + 1, day)); + dt.setTime(QTime(hours, minutes, seconds)); + + return dt.isValid(); + } + + bool + parseDateAscTime(const QStringList & l, QDateTime & dt) + { + bool haveMonth = false; + uint month = 0; + + QStringList::ConstIterator it; + + for (it = monthList.begin(); it != monthList.end(); ++it) + { + if (*it == l[1]) + { + haveMonth = true; + break; + } + + ++month; + } + + if (!haveMonth) + return false; + + uint day(l[2].toUInt()); + + QStringList timeTokenList(QStringList::split(':', l[3])); + + if (3 != timeTokenList.count()) + return false; + + uint hours (timeTokenList[0].toUInt()); + uint minutes (timeTokenList[1].toUInt()); + uint seconds (timeTokenList[2].toUInt()); + + uint year(l[4].toUInt()); + + dt.setDate(QDate(year, month + 1, day)); + dt.setTime(QTime(hours, minutes, seconds)); + + return dt.isValid(); + } + + QString + translatedResponseName(uint code) + { + QString s; + + switch (code) + { + case 200: + s = i18n("OK"); + break; + case 206: + s = i18n("Partial content"); + break; + case 304: + s = i18n("Not modified"); + break; + case 400: + s = i18n("Bad request"); + break; + case 403: + s = i18n("Forbidden"); + break; + case 404: + s = i18n("Not found"); + break; + case 412: + s = i18n("Precondition failed"); + break; + case 416: + s = i18n("Bad range"); + break; + case 500: + s = i18n("Internal error"); + break; + case 501: + s = i18n("Not implemented"); + break; + case 505: + s = i18n("HTTP version not supported"); + break; + default: + s = i18n("Unknown"); + break; + } + + return s; + } + + QString + responseName(uint code) + { + QString s; + + switch (code) + { + case 200: + s = "OK"; + break; + case 206: + s = "Partial content"; + break; + case 304: + s = "Not modified"; + break; + case 400: + s = "Bad request"; + break; + case 403: + s = "Forbidden"; + break; + case 404: + s = "Not found"; + break; + case 412: + s = "Precondition failed"; + break; + case 416: + s = "Bad range"; + break; + case 500: + s = "Internal error"; + break; + case 501: + s = "Not implemented"; + break; + case 505: + s = "HTTP version not supported"; + break; + default: + s = "Unknown"; + break; + } + + return s; + } + + +} // End namespace KPF + + +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/Utils.h b/kpf/src/Utils.h new file mode 100644 index 00000000..6a81dfa8 --- /dev/null +++ b/kpf/src/Utils.h @@ -0,0 +1,113 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_UTILS_H +#define KPF_UTILS_H + +#include <qstringlist.h> +#include <qstring.h> +#include <qdatetime.h> +#include <qfileinfo.h> + +/** + * Used to keep all parts of the kpf application out of the global namespace. + */ +namespace KPF +{ + /** + * Safe version of QMIN + * @return minimum of a and b. + */ + template<class T> T min(T a, T b) { return a < b ? a : b; } + + /** + * Safe version of QMAX + * @return minimum of a and b. + */ + template<class T> T max(T a, T b) { return b < a ? a : b; } + + /** + * @return the current date and time as an HTTP/1.1 compliant date string. + */ + QString dateString(); + + /** + * @return the passed QDateTime as an HTTP/1.1 compliant date string. + */ + QString dateString(const QDateTime & dt); + + /** + * Parse an HTTP/1.1 date to a QDateTime. + * @param ret the QDateTime representation (result of parsing) + * @return true if the date is an an acceptable format. + */ + bool parseDate(const QString &, QDateTime & ret); + + /** + * Parse an RFC 1123 format date to a QDateTime. Usually called by + * @ref parseDate. + */ + bool parseDateRFC1123(const QStringList &, QDateTime &); + + /** + * Parse an RFC 850 format date to a QDateTime. Usually called by + * @ref parseDate. + */ + bool parseDateRFC850(const QStringList &, QDateTime &); + + /** + * Parse an asctime(3) format date to a QDateTime. Usually called by + * @ref parseDate. + */ + bool parseDateAscTime(const QStringList &, QDateTime &); + + /** + * @return i18n(HTTP response message for code) + */ + QString translatedResponseName(uint code); + + /** + * @return HTTP response message for code + */ + QString responseName(uint code); + + /** + * @return the passed QDateTime converted GMT, honouring daylight + * saving time if necessary. + */ + QDateTime toGMT(const QDateTime &); + + /** + * Unquote hex quoted chars in string. + */ + QString unquote(const QString &); + + /** + * Quote naughty chars in %xx format. + */ + QString quote(const QString &); + +} // End namespace KPF + +#endif +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/WebServer.cpp b/kpf/src/WebServer.cpp new file mode 100644 index 00000000..37301f09 --- /dev/null +++ b/kpf/src/WebServer.cpp @@ -0,0 +1,644 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +// System includes +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> + +// Qt includes +#include <qsocket.h> +#include <qdatetime.h> +#include <qtimer.h> + +// KDE includes +#include "config.h" +#include <kconfig.h> +#include <kglobal.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <dnssd/publicservice.h> + +// Local includes +#include "Defines.h" +#include "Defaults.h" +#include "WebServerSocket.h" +#include "WebServer.h" +#include "Server.h" +#include "Utils.h" + +namespace KPF +{ + static const uint SamplesPerSecond = 10; + static const uint MaxBacklog = 1024; + + class WebServer::Private + { + public: + + Private() + : socket (0L), + listenPort (Config::DefaultListenPort), + connectionLimit (Config::DefaultConnectionLimit), + bandwidthLimit (Config::DefaultBandwidthLimit), + lastTotalOutput (0L), + totalOutput (0L), + portContention (true), + paused (false), + followSymlinks (Config::DefaultFollowSymlinks), + customErrorMessages (false) + { + } + + ~Private() + { + delete socket; + delete service; + service = 0; + socket = 0; + } + + WebServerSocket * socket; + uint listenPort; + uint connectionLimit; + QPtrList<Server> serverList; + QString root; + QString serverName; + QTimer writeTimer; + QTimer resetOutputTimer; + QTimer bindTimer; + QTimer backlogTimer; + ulong bandwidthLimit; + ulong lastTotalOutput; + ulong totalOutput; + bool portContention; + bool paused; + bool followSymlinks; + bool customErrorMessages; + QValueList<int> backlog; + DNSSD::PublicService* service; + + }; + + WebServer::WebServer(const QString & root) + : DCOPObject(QCString("WebServer_") + root.utf8()), + QObject() + { + d = new Private; + + d->root = root; + loadConfig(); + publish(); + + connect(&d->bindTimer, SIGNAL(timeout()), SLOT(slotBind())); + connect(&d->writeTimer, SIGNAL(timeout()), SLOT(slotWrite())); + connect(&d->resetOutputTimer, SIGNAL(timeout()), SLOT(slotCheckOutput())); + connect(&d->backlogTimer, SIGNAL(timeout()), SLOT(slotClearBacklog())); + + d->bindTimer .start( 0, true); + d->resetOutputTimer .start(1000 / SamplesPerSecond, false); + } + + + WebServer::WebServer + ( + const QString & root, + uint listenPort, + uint bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + const QString & serverName + ) + : DCOPObject(QCString("WebServer_") + root.utf8()), + QObject() + { + d = new Private; + + d->root = root; + + d->listenPort = listenPort; + d->bandwidthLimit = bandwidthLimit; + d->connectionLimit = connectionLimit; + d->followSymlinks = followSymlinks; + d->serverName = serverName; + + saveConfig(); + publish(); + connect(&d->bindTimer, SIGNAL(timeout()), SLOT(slotBind())); + connect(&d->writeTimer, SIGNAL(timeout()), SLOT(slotWrite())); + connect(&d->resetOutputTimer, SIGNAL(timeout()), SLOT(slotCheckOutput())); + connect(&d->backlogTimer, SIGNAL(timeout()), SLOT(slotClearBacklog())); + + d->bindTimer .start( 0, true); + d->resetOutputTimer .start(1000 / SamplesPerSecond, false); + } + + WebServer::~WebServer() + { + killAllConnections(); + + delete d; + d = 0; + } + + void WebServer::publish() + { + d->service = new DNSSD::PublicService(d->serverName,"_http._tcp",d->listenPort); + connect(d->service,SIGNAL(published(bool)),this,SLOT(wasPublished(bool))); + d->service->publishAsync(); + } + + void WebServer::wasPublished(bool ok) + { + if(ok) { + KMessageBox::information( NULL, i18n("Successfully published this new service to the network (ZeroConf)."), i18n( "Successfully Published the Service" ), "successfullypublished" ); + kpfDebug << "Published to dnssd successfully" << endl; + } + else { + KMessageBox::information( NULL, i18n("Failed to publish this new service to the network (ZeroConf). The server will work fine without this, however."), i18n( "Failed to Publish the Service" ), "failedtopublish" ); + } + } + + void + WebServer::slotBind() + { + if (0 != d->socket) + { + qWarning("Uhhh, socket isn't 0, but I'm told to bind ?"); + return; + } + + d->socket = new WebServerSocket(d->listenPort, d->connectionLimit); + + d->portContention = !d->socket->ok(); + + emit(contentionChange(d->portContention)); + + if (!d->portContention) + connect(d->socket, SIGNAL(connection(int)), SLOT(slotConnection(int))); + + else + { + delete d->socket; + d->socket = 0; + d->bindTimer.start(1000, true); + } + } + + void + WebServer::slotConnection(int fd) + { + if (!d->backlog.isEmpty()) + { + if (d->backlog.count() < MaxBacklog) + { + kpfDebug << "Adding this connection to the backlog." << endl; + d->backlog.append(fd); + } + else + { + kpfDebug << "Backlog full. Ignoring this connection." << endl; + } + return; + } + + if (!handleConnection(fd)) + { + if (d->backlog.count() < MaxBacklog) + { + kpfDebug << "Adding this connection to the backlog." << endl; + d->backlog.append(fd); + d->backlogTimer.start(10, true); + } + else + { + kpfDebug << "Backlog full. Ignoring this connection." << endl; + } + } + } + + bool + WebServer::handleConnection(int fd) + { + if (d->paused) + { + kpfDebug << "Paused." << endl; + return false; + } + + if (d->serverList.count() >= d->connectionLimit) + { +// kpfDebug << "Hit connection limit." << endl; + return false; + } + + int on = 1; + + ::setsockopt + ( + fd, + SOL_SOCKET, + SO_REUSEADDR, + ( char* )&on, + sizeof( on ) ); + + on = 0; + + ::setsockopt + ( + fd, + SOL_SOCKET, + SO_LINGER, + ( char* ) &on, + sizeof( on ) ); + + Server * s = new Server(d->root, d->followSymlinks, fd, this); + + connect + ( + s, + SIGNAL(output(Server *, ulong)), + SLOT(slotOutput(Server *, ulong)) + ); + + connect(s, SIGNAL(finished(Server *)), SLOT(slotFinished(Server *))); + connect(s, SIGNAL(request(Server *)), SIGNAL(request(Server *))); + connect(s, SIGNAL(response(Server *)), SIGNAL(response(Server *))); + + d->serverList.append(s); + + connect + (s, SIGNAL(readyToWrite(Server *)), SLOT(slotReadyToWrite(Server *))); + + emit(connection(s)); + + return true; + } + + void + WebServer::restart() + { + d->bindTimer.stop(); + + killAllConnections(); + delete d->socket; + d->socket = 0; + d->service->setServiceName(d->serverName); + d->service->setPort(d->listenPort); + d->bindTimer.start(0, true); + } + + void + WebServer::killAllConnections() + { + QPtrListIterator<Server> it(d->serverList); + + for (; it.current(); ++it) + it.current()->cancel(); + + } + + void + WebServer::slotOutput(Server * s, ulong l) + { + emit(output(s, l)); + } + + void + WebServer::slotFinished(Server * s) + { + emit(finished(s)); + d->serverList.removeRef(s); + delete s; + s = 0; + } + + void + WebServer::setBandwidthLimit(ulong l) + { + d->bandwidthLimit = l; + saveConfig(); + } + + ulong + WebServer::bandwidthLimit() + { + return d->bandwidthLimit; + } + + void + WebServer::setFollowSymlinks(bool b) + { + d->followSymlinks = b; + saveConfig(); + } + + bool + WebServer::followSymlinks() + { + return d->followSymlinks; + } + + QString + WebServer::root() + { + return d->root; + } + + uint + WebServer::connectionLimit() + { + return d->connectionLimit; + } + + void + WebServer::setConnectionLimit(uint i) + { + d->connectionLimit = i; + saveConfig(); + } + + uint + WebServer::listenPort() + { + return d->listenPort; + } + + void + WebServer::setListenPort(uint i) + { + d->listenPort = i; + saveConfig(); + } + + bool + WebServer::customErrorMessages() + { + return d->customErrorMessages; + } + + void + WebServer::setCustomErrorMessages(bool b) + { + d->customErrorMessages = b; + saveConfig(); + } + + ulong + WebServer::bytesLeft() const + { +#if 0 + // Multiply the bandwidth limit by 10 so we can do 10 checks per second. + + ulong l = + (d->bandwidthLimit * 10240) - (d->totalOutput - d->lastTotalOutput); +#endif + + ulong l = + ulong(d->bandwidthLimit * (1024 / double(SamplesPerSecond))) + - (d->totalOutput - d->lastTotalOutput); + + return l; + } + + ulong + WebServer::bandwidthPerClient() const + { + ulong l = 0L; + + if (!d->serverList.isEmpty()) + { + l = bytesLeft() / d->serverList.count(); + } + + kpfDebug << l << endl; + + return l; + } + + void + WebServer::slotReadyToWrite(Server *) + { + d->writeTimer.stop(); + d->writeTimer.start(0, true); + } + + void + WebServer::slotWrite() + { + if (d->serverList.isEmpty()) + return; + + QPtrListIterator<Server> it(d->serverList); + + for (; it.current(); ++it) + { + if (0 == bytesLeft()) + break; + + Server * s = it.current(); + + if (0 == s->bytesLeft()) + continue; + + ulong bytesAvailable = 0; + + if (0 == bandwidthPerClient()) + bytesAvailable = bytesLeft(); + else + bytesAvailable = min(s->bytesLeft(), bandwidthPerClient()); + + if (0 != bytesAvailable) + d->totalOutput += s->write(bytesAvailable); + } + + d->writeTimer.start(1000 / SamplesPerSecond, true); + } + + void + WebServer::slotCheckOutput() + { + emit(connectionCount(d->serverList.count())); + ulong lastOutput = d->totalOutput - d->lastTotalOutput; + emit(wholeServerOutput(ulong(lastOutput * SamplesPerSecond))); + d->lastTotalOutput = d->totalOutput; + } + + void + WebServer::slotClearBacklog() + { +// kpfDebug << "WebServer::slotClearBacklog" << endl; + + if (!d->backlog.isEmpty()) + { + uint currentBacklogCount = d->backlog.count(); + + for (uint i = 0; i < currentBacklogCount; i++) + { + int fd = d->backlog.first(); + + if (handleConnection(fd)) + { + kpfDebug + << "Ah, we can now handle this connection. Removing from backlog." + << endl; + + d->backlog.remove(d->backlog.begin()); + } + else + { +// kpfDebug +// << "Still can't handle this connection. Leaving in backlog" +// << endl; + + break; + } + } + } + + if (!d->backlog.isEmpty()) + { + d->backlogTimer.start(10, true); + } + } + + uint + WebServer::connectionCount() + { + return d->serverList.count(); + } + + void + WebServer::pause(bool b) + { + if(b == d->paused) return; + + d->paused = b; + if (b) d->service->stop(); + else d->service->publishAsync(); //published() should be already connected + emit pauseChange(d->paused); + saveConfig(); + } + + bool + WebServer::paused() + { + return d->paused; + } + QString + WebServer::serverName() + { + return d->serverName; + } + void + WebServer::setServerName(const QString& serverName) + { + d->serverName=serverName; + } + + bool + WebServer::portContention() + { + return d->portContention; + } + + void + WebServer::set + ( + uint listenPort, + ulong bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + const QString& serverName + ) + { + d->listenPort = listenPort; + d->bandwidthLimit = bandwidthLimit; + d->connectionLimit = connectionLimit; + d->followSymlinks = followSymlinks; + d->serverName = serverName; + + saveConfig(); + } + + void + WebServer::loadConfig() + { + kpfDebug << "WebServer(" << d->root << "): Loading configuration" << endl; + KConfig c(Config::name()); + + c.setGroup(Config::key(Config::GroupPrefix) + d->root); + + d->listenPort = + c.readUnsignedNumEntry + (Config::key(Config::ListenPort), d->listenPort); + + d->bandwidthLimit = + c.readUnsignedNumEntry + (Config::key(Config::BandwidthLimit), d->bandwidthLimit); + + d->connectionLimit = + c.readUnsignedNumEntry + (Config::key(Config::ConnectionLimit), d->connectionLimit); + + d->followSymlinks = + c.readBoolEntry + (Config::key(Config::FollowSymlinks), d->followSymlinks); + + d->customErrorMessages = + c.readBoolEntry + (Config::key(Config::CustomErrors), d->customErrorMessages); + + d->paused = + c.readBoolEntry + (Config::key(Config::Paused), d->paused); + + d->serverName = + c.readEntry + (Config::key(Config::ServerName), d->serverName); + + } + + void + WebServer::saveConfig() + { + kpfDebug << "WebServer(" << d->root << "): Saving configuration" << endl; + KConfig c(Config::name()); + + c.setGroup(Config::key(Config::GroupPrefix) + d->root); + + c.writeEntry(Config::key(Config::ListenPort), d->listenPort); + c.writeEntry(Config::key(Config::BandwidthLimit), d->bandwidthLimit); + c.writeEntry(Config::key(Config::ConnectionLimit), d->connectionLimit); + c.writeEntry(Config::key(Config::FollowSymlinks), d->followSymlinks); + c.writeEntry(Config::key(Config::CustomErrors), d->customErrorMessages); + c.writeEntry(Config::key(Config::Paused), d->paused); + c.writeEntry(Config::key(Config::ServerName), d->serverName); + + c.sync(); + } + +} // End namespace KPF + +#include "WebServer.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/WebServer.h b/kpf/src/WebServer.h new file mode 100644 index 00000000..ae9d9383 --- /dev/null +++ b/kpf/src/WebServer.h @@ -0,0 +1,346 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_WEB_SERVER_H +#define KPF_WEB_SERVER_H + +#include <dcopobject.h> +#include <qserversocket.h> + +#include "Defaults.h" +#include "Request.h" +#include "Response.h" + +namespace KPF +{ + class Server; + + /** + * Listens on a port for incoming connections. + * Creates and manages Server objects. + * Manages bandwidth limit, dealing out bandwidth to Server objects. + * Maintains concurrent connection limit, using a backlog to queue incoming + * connections which cannot be served immediately. + */ + class WebServer : public QObject, virtual public DCOPObject + { + K_DCOP + Q_OBJECT + + public: + + /** + * Only root dir specified - this causes an immediate loadConfig. + * + * @param root Virtual root directory for servers. Passed to all created + * Server objects, which much ensure that only files from the root and + * its child directories are served. + */ + WebServer(const QString & root); + + /** + * @param root Virtual root directory for servers. Passed to all created + * Server objects, which much ensure that only files from the root and + * its child directories are served. + */ + WebServer + ( + const QString & root, + uint listenPort, + uint bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + const QString & serverName + ); + + virtual ~WebServer(); + + /** + * Load the configuration, but do not kill existing connections even if + * listen port is changed. Do not change listen port yet - only when + * asked to restart. + */ + void loadConfig(); + + k_dcop: + + /** + * @return virtual root. + */ + QString root(); + + /** + * @return server name + */ + QString serverName(); + + /** + * @return amount of bytes that may be sent out per second, in total + * (adding the output of all contained Server objects.) + */ + ulong bandwidthLimit(); + + /** + * @return number of concurrent connections that will be served (by + * creating Server objects. More connections may wait in a backlog.) + */ + uint connectionLimit(); + + /** + * @return port on which to listen for incoming connections. + */ + uint listenPort(); + + /** + * @return true if requests may include symbolic links in their path. + */ + bool followSymlinks(); + + /** + * @return true if custom error messages (set by the user) should be + * sent. + */ + bool customErrorMessages(); + + /** + * Set the maximum amount of bytes that may be sent out per second, in + * total (adding the output of all contained Server objects.) + */ + void setBandwidthLimit (ulong); + + /** + * Set the number of concurrent connections that will be served (by + * creating Server objects. More connections may wait in a backlog.) + */ + void setConnectionLimit (uint); + + /** + * Set the port on which to listen for incoming connections. Does not + * take effect until restart() is called. + */ + void setListenPort (uint); + + /** + * Set server name + */ + void setServerName (const QString&); + + /** + * Set whether requests may include symbolic links in their path. + */ + void setFollowSymlinks (bool); + + /** + * Set whether custom error messages (set by the user) should be + * sent. + */ + void setCustomErrorMessages (bool); + + /** + * Convenience method for setting many attributes at once. + */ + void set + ( + uint listenPort, + ulong bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + const QString& serverName + ); + + /** + * Kill all connections, stop listening on port, and start listening on + * (possibly different) port. + */ + void restart(); + + /** + * Start / stop accepting new connections. + */ + void pause(bool); + + /** + * @return true if no new connections are accepted. + */ + bool paused(); + + /** + * @return true if this WebServer is unable to listen on the requested + * port. + */ + bool portContention(); + + /** + * @return number of Server objects serving requests. + */ + uint connectionCount(); + + protected slots: + + /** + * Called repeatedly by a timer when this WebServer is in contention for + * its listen port. + */ + void slotBind (); + + /** + * Called by contained socket object when a new incoming connection is + * made. + */ + void slotConnection (int); + + /** + * Called by a Server when it has finished all transactions and is ready + * to die. + */ + void slotFinished (Server *); + + /** + * Called by a Server when it has sent data to the remote client. + */ + void slotOutput (Server *, ulong); + + /** + * Called by a Server when it wishes to send data to the remote client. + */ + void slotReadyToWrite (Server *); + + /** + * Called regularly by a timer to start output allocation (to Server + * objects.) + */ + void slotWrite (); + + /** + * Called regularly by a timer to check output for current time slice. + */ + void slotCheckOutput (); + + /** + * Called regularly by a timer to handle connections queued in the + * backlog. + */ + void slotClearBacklog (); + + /** + * Called when this succesfully publishes via zeroconf, or there was an + * error doing so. + */ + void wasPublished(bool ok); + + protected: + + /** + * Attempt to create a Server to handle a new connection. + * @param fd file descriptor. + */ + bool handleConnection(int fd); + + void saveConfig(); + + signals: + + /** + * @param bytes number of bytes sent by this server during the last + * time slice. + */ + void wholeServerOutput (ulong bytes); + + /** + * Emitted when a Server object has received a request from its remote + * client. + */ + void request (Server *); + + /** + * Emitted when a Server object has created a response which it wishes + * to send to its remote client. + */ + void response (Server *); + + /** + * Emitted when a Server object when data has been send to remote client. + * @param bytes number of bytes sent to remote client. + */ + void output (Server *, ulong bytes); + + /** + * Emitted when a new Server has been created. + */ + void connection (Server *); + + /** + * Emitted when a Server has finished all transactions. + */ + void finished (Server *); + + /** + * Emitted when this WebServer is now contending, or has stopped + * contending, its listen port. + */ + void contentionChange (bool); + + /** + * Emitted when this WebServer is now accepting, or has stopped + * accepting, incoming connections. + */ + void pauseChange (bool); + + /** + * Emitted when the number active Server objects (served connections) + * changes. + */ + void connectionCount (uint); + + private: + + /** + * @return number of bytes that may be sent out. Changes over time, + * depending on bandwidth limit. + */ + ulong bytesLeft() const; + + /** + * @return number of bytes that may be sent out by each Server, with the + * total split between the existing Server objects. + */ + ulong bandwidthPerClient() const; + + + /** publish this to dns-sd (zeroconf) + */ + void publish(); + /** + * Cause all existing Server objects to close their connections by + * calling Server::cancel. + */ + void killAllConnections(); + + class Private; + Private * d; + }; + +} // End namespace KPF + +#endif // WEB_SERVER_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/WebServerManager.cpp b/kpf/src/WebServerManager.cpp new file mode 100644 index 00000000..feec661d --- /dev/null +++ b/kpf/src/WebServerManager.cpp @@ -0,0 +1,295 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +// KDE includes +#include <kconfig.h> +#include <kglobal.h> +#include <kapplication.h> + +// Local includes +#include "Defines.h" +#include "WebServerManager.h" +#include "WebServer.h" +#include "WebServer_stub.h" + +namespace KPF +{ + WebServerManager * WebServerManager::instance_ = 0L; + + WebServerManager * + WebServerManager::instance() + { + if (0 == instance_) + instance_ = new WebServerManager; + + return instance_; + } + + void + WebServerManager::shutdown() + { + delete instance_; + instance_ = 0; + } + + WebServerManager::WebServerManager() + : DCOPObject("WebServerManager"), + QObject() + { + serverList_.setAutoDelete(true); + } + + WebServerManager::~WebServerManager() + { + // Empty. + } + + QPtrList<WebServer> + WebServerManager::serverListLocal() + { + return serverList_; + } + + WebServer * + WebServerManager::createServerLocal + ( + const QString & root, + uint listenPort, + uint bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + const QString & serverName + ) + { + if (0 != server(root)) + return 0; + if ( listenPort == 0) + listenPort = nextFreePort(); + WebServer * server = + new WebServer + ( + root, + listenPort, + bandwidthLimit, + connectionLimit, + followSymlinks, + serverName + ); + + serverList_.append(server); + + saveConfig(); + + emit(serverCreated(server)); + + return server; + } + + void + WebServerManager::loadConfig() + { + KConfig config(Config::name()); + + config.setGroup("General"); + + QStringList serverRootList = config.readListEntry("ServerRootList"); + + QStringList::ConstIterator it; + + for (it = serverRootList.begin(); it != serverRootList.end(); ++it) + { + WebServer * s = new WebServer(*it); + serverList_.append(s); + s->loadConfig(); + emit(serverCreated(s)); + } + } + + void + WebServerManager::saveConfig() const + { + KConfig config(Config::name()); + + config.setGroup("General"); + + QPtrListIterator<WebServer> it(serverList_); + + QStringList serverRootList; + + for (; it.current(); ++it) + serverRootList << it.current()->root(); + + config.writeEntry("ServerRootList", serverRootList); + + config.sync(); + } + + WebServer * + WebServerManager::server(const QString & root) + { + QPtrListIterator<WebServer> it(serverList_); + + for (; it.current(); ++it) + { + kpfDebug << "WebServerManager::server(): found root of " << + "\"" << it.current()->root() << "\"" << endl; + + if (it.current()->root() == root) + { + kpfDebug + << "WebServerManager::server(" << root << "): found" << endl; + return it.current(); + } + } + + kpfDebug + << "WebServerManager::server(" << root << "): not found" << endl; + return 0; + } + + bool + WebServerManager::disableServer(const QString & root) + { + WebServer * existing = server(root); + + if (0 == existing) + { + return false; + } + else + { + emit(serverDisabled(existing)); + // Auto-deleted by list. + serverList_.removeRef(existing); + saveConfig(); + return true; + } + } + + QValueList<DCOPRef> + WebServerManager::serverList() + { + QValueList<DCOPRef> l; + + QPtrListIterator<WebServer> it(serverList_); + + for (; it.current(); ++it) + l << DCOPRef(it.current()); + + return l; + } + + DCOPRef + WebServerManager::createServer + ( + QString root, + uint listenPort, + uint bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + QString serverName + ) + { + WebServer * server = createServerLocal + (root, listenPort, bandwidthLimit, connectionLimit, followSymlinks, serverName); + + if (0 == server) + return DCOPRef(); + else + return DCOPRef(server); + } + + void + WebServerManager::disableServer(DCOPRef serverRef) + { + if (serverRef.isNull()) + return; + + WebServer_stub webServer + (serverRef.app(), serverRef.object()); + + QString root = webServer.root(); + + if (DCOPStub::CallFailed == webServer.status()) + { + kpfDebug << "Real shitty mess here" << endl; + return; + } + + bool ok = disableServer(root); + + if (!ok) + { + kpfDebug << "Definitely a real shitty mess here" << endl; + return; + } + } + + void + WebServerManager::quit() + { +// kapp->quit(); + } + + bool + WebServerManager::hasServer(const QString & s) + { + QString root(s); + + if ('/' == root.at(root.length() - 1)) + { + root.truncate(root.length() - 1); + } + + return (0 != server(root) || 0 != server(root + "/")); + } + + uint WebServerManager::nextFreePort() const + { + for (uint port = Config::DefaultListenPort; port < 65536; ++port) + { + bool ok = true; + + for (QPtrListIterator<WebServer> it(serverList_); it.current(); ++it) + { + if (it.current()->listenPort() == port) + { + ok = false; + break; + } + } + + if (ok) + { + return port; + } + } + + // Not much we can do. + return Config::DefaultListenPort; + } + +} // End namespace KPF + +#include "WebServerManager.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/WebServerManager.h b/kpf/src/WebServerManager.h new file mode 100644 index 00000000..6faa8a30 --- /dev/null +++ b/kpf/src/WebServerManager.h @@ -0,0 +1,173 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef KPF_WEB_SERVER_MANAGER_H +#define KPF_WEB_SERVER_MANAGER_H + +#include <dcopobject.h> +#include <dcopref.h> + +#include "Defaults.h" + +#include <qptrlist.h> + +namespace KPF +{ + class WebServer; + + /** + * Singleton, encapsulating a set of WebServer objects. Handles + * creating WebServer objects at startup (based on settings) and + * on demand. Destroys WebServer objects on demand. + */ + class WebServerManager : public QObject, virtual public DCOPObject + { + Q_OBJECT + K_DCOP + + public: + + static WebServerManager * instance(); + + /** + * Calls delete(this). + */ + void shutdown(); + + /** + * @return a list of pointers to WebServer objects managed + * by this object. + */ + QPtrList<WebServer> serverListLocal(); + + /** + * @return a pointer to a new WebServer object, with the root + * as specified, or 0 if creation was impossible. Updates + * the configuration. + */ + WebServer * createServerLocal + ( + const QString & root, + uint listenPort, + uint bandwidthLimit = Config::DefaultBandwidthLimit, + uint connectionLimit = Config::DefaultConnectionLimit, + bool followSymlinks = Config::DefaultFollowSymlinks, + const QString & serverName = QString::null + ); + + /** + * Disables a WebServer and updates the configuration. + */ + bool disableServer(const QString & root); + + /** + * Loads the configuration. + * Creates WebServer objects to match the configuration and + * ensures each object loads its configuration. + */ + void loadConfig(); + + /** + * Saves the configuration. + * Also ensures each WebServer object saves its configuration. + */ + void saveConfig() const; + + /** + * Find a WebServer or return 0. + */ + WebServer * server(const QString & root); + + /** + * Ask a server to re-read its configuration. + */ + bool reconfigureServer(const QString & root); + + /** + * Pause/unpause a server. + */ + bool pauseServer(const QString & root, bool); + + /** + * @return whether the server is paused. + */ + bool serverPaused(const QString & root); + + /** + * Restart a server. + */ + bool restartServer(const QString & root); + + /** + * @return if a Server object with the specified root exists. Handles + * the two possible variations of trailing slash, i.e. existing and not + * existing. + */ + bool hasServer(const QString & root); + + uint nextFreePort() const; + + k_dcop: + + QValueList<DCOPRef> serverList(); + + DCOPRef createServer + ( + QString root, + uint listenPort, + uint bandwidthLimit, + uint connectionLimit, + bool followSymlinks, + QString serverName + ); + + void disableServer(DCOPRef); + + void quit(); + + protected: + + /** + * Not used, as this is a singleton. + */ + WebServerManager(); + + virtual ~WebServerManager(); + + signals: + + void serverCreated(WebServer *); + void serverDisabled(WebServer *); + + private: + + static WebServerManager * instance_; + + void load(); + QPtrList<WebServer> serverList_; + }; + +} // End namespace KPF + +#endif // WEB_SERVER_MANAGER_H +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/WebServerSocket.cpp b/kpf/src/WebServerSocket.cpp new file mode 100644 index 00000000..4dfa6626 --- /dev/null +++ b/kpf/src/WebServerSocket.cpp @@ -0,0 +1,44 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#include "Defines.h" +#include "WebServerSocket.h" + +namespace KPF +{ + WebServerSocket::WebServerSocket(Q_UINT16 port, uint maxconn) + : QServerSocket(port, maxconn, 0L) + { + // Empty. + } + + void + WebServerSocket::newConnection(int fd) + { + emit(connection(fd)); + } + +} // End namespace KPF + +#include "WebServerSocket.moc" +// vim:ts=2:sw=2:tw=78:et diff --git a/kpf/src/WebServerSocket.h b/kpf/src/WebServerSocket.h new file mode 100644 index 00000000..f45e0d70 --- /dev/null +++ b/kpf/src/WebServerSocket.h @@ -0,0 +1,51 @@ +/* + KPF - Public fileserver for KDE + + Copyright 2001 Rik Hemsley (rikkus) <rik@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, 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. +*/ + +#ifndef WEB_SERVER_SOCKET_H +#define WEB_SERVER_SOCKET_H + +#include <qserversocket.h> + +namespace KPF +{ + /** + * Overridden to emit a signal on a new connection. + */ + class WebServerSocket : public QServerSocket + { + Q_OBJECT + + public: + + WebServerSocket(Q_UINT16 port, uint maxconn); + virtual void newConnection(int fd); + + signals: + + void connection(int); + }; + +} // End namespace KPF + +#endif // WEB_SERVER_SOCKET_H +// vim:ts=2:sw=2:tw=78:et |