summaryrefslogtreecommitdiffstats
path: root/kpf/src/Server.cpp
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commitbcb704366cb5e333a626c18c308c7e0448a8e69f (patch)
treef0d6ab7d78ecdd9207cf46536376b44b91a1ca71 /kpf/src/Server.cpp
downloadtdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.tar.gz
tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kpf/src/Server.cpp')
-rw-r--r--kpf/src/Server.cpp1137
1 files changed, 1137 insertions, 0 deletions
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