/* KPF - Public fileserver for KDE Copyright 2001 Rik Hemsley (rikkus) 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 TQString & dir, bool followSymlinks, int socket, WebServer * parent ) : TQObject(parent, "Server") { d = new Private; kpfDebug << "New server: " << d->id << endl; d->dir = dir; d->followSymlinks = followSymlinks; d->birth = TQDateTime::currentDateTime(); d->socket.setSocket(socket); connect(&(d->socket), TQT_SIGNAL(readyRead()), this, TQT_SLOT(slotReadyRead())); connect ( &(d->socket), TQT_SIGNAL(bytesWritten(int)), TQT_SLOT(slotBytesWritten(int)) ); connect ( &(d->socket), TQT_SIGNAL(connectionClosed()), TQT_SLOT(slotConnectionClosed()) ); connect ( &(d->idleTimer), TQT_SIGNAL(timeout()), TQT_SLOT(slotTimeout()) ); connect ( &(d->readTimer), TQT_SIGNAL(timeout()), TQT_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(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; TQString 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 TQString & line) { ++d->requestCount; #ifdef KPF_TRAFFIC_DEBUG kpfDebug << d->id << ": (request #" << d->requestCount << ") readRequest: `" << line << "'" << endl; #endif TQStringList l(TQStringList::split(' ', line)); // A request usually looks like TQT_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] : TQString()); // Before we check the request, say we received it. emit(request(this)); return checkRequest(); } bool Server::checkRequest() { // We only handle TQT_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 TQDir/TQFile/TQFileInfo // 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 TQValueQueue. // Basically we 'dequeue' the first line from incomingHeaderBuffer. TQString 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. TQString 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()) { TQString line = "Content-Range: bytes "; line += TQString::number(d->request.range().first()); line += '-'; if (d->request.range().haveLast()) line += TQString::number(d->request.range().last()); else line += TQString::number(d->resource.size() - 1); line += '/'; line += TQString::number(d->resource.size()); writeLine(line); } writeLine("Content-Length: " + TQString::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 TQString & line) { // Fill a buffer. We're not allowed to send anything out until our // controller calls write(). TQCString 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. TQCString 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 = TQDateTime::currentDateTime(); emit(finished(this)); } TQHostAddress 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 (TQSocket::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); // // Using -1 to signal an error is 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. // 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()); TQByteArray 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: `" << TQCString(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 TQSocket 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; } TQDateTime Server::birth() const { return d->birth; } TQDateTime 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