// Smersh // // Douglas Thrift // // $Id$ #include #include #include "Daemon.hpp" string Daemon::crlf("\r\n"); void Daemon::serve(int port, bool fork, Daemon* self) { api::TcpSocket server; server.Create(); server.SetAddress(api::InternetAddress(api::InternetAddress::Any, port)); if (fork) { switch (pid_t pid = ::fork()) { case -1: cerr << program << ": fork()\n"; exit(1); case 0: break; default: cout << pid << '\n'; return; } } server.Listen(50); while (true) { Client* client (new Client); server.Accept(client->socket, &client->ip); api::Thread thread(etl::BindAll(&Daemon::handle, self, client)); } } Daemon::Status Daemon::request(istream& sin, Environment& env, ostream& post, ostream& log) { string line; Matcher request("^([A-Z]+) .*?(\\?.+)? HTTP/(\\d+)\\.(\\d+)$"); getline(sin, line); log << '"' << line << "\" "; env.put("HTTP_REFERER=-"); env.put("HTTP_USER_AGENT=-"); if (line == request) { if (lexical_cast(request[3]) > 1) return version; Matcher method("^GET|HEAD|POST$"); if (request[1] != method) return notImplemented; env.set("REQUEST_METHOD", method); if (!request[2].empty()) env.set("QUERY_STRING", request[2].substr(1)); headers(sin, env); if (method[0] == "POST") return message(sin, env, post); return ok; } return notFound; } void Daemon::response(ostream& sout, Status status) { sout << "HTTP/1.1 " << status << ' ' << reason(status) << crlf << "Date: " << date() << crlf << "Server: " << server() << crlf << "Connection: close" << crlf; } string Daemon::server() { utsname system; uname(&system); return string("Smersh/0.9 (") + system.sysname + ')'; } streamsize Daemon::error(ostream& sout, Status status) { string reason(this->reason(status)); ostringstream error; error << "" << status << ' ' << reason << "

" << reason << "

Mistakes were made, deal with t" << "hem.


" << server() + "
\n"; sout << "Content-Length: " << error.str().length() << crlf << "Content-Type: text/html; charset=UTF-8\r\n\r\n" << error.str(); return error.str().size(); } string Daemon::date(bool log) { time_t now(time(NULL)); tm time; if (log) localtime_r(&now, &time); else gmtime_r(&now, &time); const char* format(log ? "[%m/%d/%Y:%T %z]" : "%a, %d %b %Y %T GMT"); char when[log ? 29 : 30]; strftime(when, log ? 29 : 30, format, &time); return when; } int Daemon::handle(Client* client) { ios::InputOutputStreamBufAdapter adapter(client->socket); iostream sio(&adapter); Environment env; stringstream post; ostringstream log; Status code(request(sio, env, post, log)); response(sio, code); bool head(env.get("REQUEST_METHOD") == "HEAD"); streamsize sent(0); if (code == ok && !head) { ostringstream output; Smersh smersh(post, output, env); string content(output.str().substr(40)); sio << "Content-Length: " << content.length() << crlf << "Content-Type: text/html; charset=UTF-8\r\n\r\n"; sio.write(content.data(), content.size()); sent = content.size(); } else if (head) sio << "Content-Type: text/html; charset=UTF-8\r\n\r\n"; else sent = error(sio, code); log << code << ' ' << (sent > 0 ? lexical_cast(sent) : string("0")) << " \"" << env.get("HTTP_REFERER") << "\" \"" << env.get("HTTP_USER_AGENT") << '"'; ofstream fout(this->log.c_str(), ios_base::app); fout << inet_ntoa(client->ip->sin_addr) << " - - " << date(true) << ' ' << log.str() << '\n'; sio << flush; client->socket.ShutdownWrite(); delete client; } void Daemon::headers(istream& sin, Environment& env) { do { string line; getline(sin, line); if (line.empty()) break; istringstream input(line); string name, value; std::getline(input, name, ':'); getline(input, value); for (char next(sin.peek()); next == ' ' || next == '\t'; next = sin.peek()) { getline(sin, line); value += ' ' + line; } Matcher matcher("^\\s*(.+)\\s*$"); if (name == "Content-Length") { if (value == matcher) env.set("CONTENT_LENGTH", matcher[1]); } else if (name == "Content-Type") { if (value == matcher) env.set("CONTENT_TYPE", matcher[1]); } else if (name == "Referer") { if (value == matcher) env.set("HTTP_REFERER", matcher[1]); } else if (name == "User-Agent") { if (value == matcher) env.set("HTTP_USER_AGENT", matcher[1]); } } while (sin.good()); } Daemon::Status Daemon::message(istream& sin, Environment& env, ostream& post) { string contentLength(env.get("CONTENT_LENGTH")); if (env.get("CONTENT_TYPE") != "application/x-www-form-urlencoded") return mediaType; if (contentLength.empty()) return lengthRequired; streamsize length(lexical_cast(contentLength)); char* content(new char[length]); sin.read(content, length); post.write(content, length); delete [] content; return ok; } string Daemon::reason(Status status) { ostringstream sout; switch (status) { case ok: sout << "OK"; break; case found: sout << "Found"; break; case seeOther: sout << "See Other"; break; case bad: sout << "Bad Request"; break; case notFound: sout << "Not Found"; break; case lengthRequired: sout << "Length Required"; break; case mediaType: sout << "Unsupported Media Type"; break; case serverError: sout << "Internal Server Error"; break; case notImplemented: sout << "Not Implemented"; break; case version: sout << "HTTP Version not supported"; } return sout.str(); }