#include "Socket.h" #include "Action.h" #include "HttpServ.h" #include "Buffer.h" #include "ReadJob.h" #include "CopyJob.h" #include "Task.h" #include "config.h" String endhl = "\r\n", endnl = "\n"; class ProcessRequest : public Task { public: ProcessRequest(Action *, const Socket &, const String &, int); virtual ~ProcessRequest() {} virtual String GetName(); protected: virtual int Do(); private: String GetMime(const String &); bool ParseUri(String &, String &, Handle *); void ParseVars(Handle *, int); void ShowError(Handle *); void SendHeads(Handle *, const String &, const String & = "", time_t = -1); void SendRedirect(Handle *); String file, domain, t; Buffer b; Task * c, * a; Action * f; int len, localport; Action * p; Socket s; String name, host; Variables * Vars, * Heads; bool bad, hasvars, post; }; ProcessRequest::ProcessRequest(Action * ap, const Socket & as, const String & aname, int aport) : localport(aport), p(ap), s(as), name(aname) { SetBurst(); } String ProcessRequest::GetName() { return "Processing HTTP request"; } int ProcessRequest::Do() { switch (current) { case 0: if (!s.IsConnected()) return TASK_DONE; c = new ReadJob(&s, &b); WaitFor(c); current = 1; Suspend(TASK_ON_HOLD); case 1: delete c; bad = false; // cerr << "---- Got a request from handle " << s.GetHandle() << " \n"; post = ParseUri(file, domain, &b); Heads = new Variables(); len = -1; do { int p; b >> t; // cerr << "Read Request (n): " << t << endl; if ((t.strstr("Content-Length: ") == 0) || (t.strstr("Content-length: ") == 0)) { // cerr << "Saw 'Content-Lenght:', reading length from '" << t.extract(16) << "'\n"; len = t.extract(16).to_int(); } if (t.strstr("Host: ") == 0) { host = t.extract(6); } if ((p = t.strchr(':')) >= 0) { String s = t.extract(0, p - 1); s += '='; s += t.extract(p + 2); Heads->Add(s); } } while (t.strlen()); // cerr << "---- Processing it.\n"; hasvars = false; if (post) { // On a pas eu de ligne 'Content-Length' mais on a eu une méthode POST. // Cela est une erreur. if (len == -1) { // cerr << "Error: method POST but no Content-Length\n"; bad = true; Vars = new Variables(); } else { // cerr << "Got a POST request. Parsing variables. (len = " << len << ")\n"; // Les variables seront initialisées ici. hasvars = true; } } else { Vars = new Variables(); } current = 2; if (hasvars && (len)) { c = new CopyJob(&s, &b, len); WaitFor(c); Suspend(); } else { c = 0; } case 2: if (hasvars) { if (c) delete c; ParseVars(&b, len); } // cerr << " Domain = '" << domain << "' - File = '" << file << "'\n"; if (!bad) { // Nous vérifions le domaine. if (domain != "") { bad = true; // Les domaines valides sont '/', '/bin' et '/image'. if (domain == "/image") bad = false; if (domain == "/bin") bad = false; if (domain == "/") bad = false; if (bad) { // cerr << "Error: bad domain.\n"; } } else { // L'url sans domaine ni fichier est valide. (cela arrive sur certains navigateurs...) bad = (file != ""); } } a = 0; if (bad) { ShowError(&b); } else { if (((domain == "") || (domain == "/")) && (file == "")) { // Si le navigateur a demandé l'URL '/', alors on renvoie une notification // de redirection. SendRedirect(&b); } else if (domain == "/bin") { // Le domaine 'bin' est réservé aux actions. On cherche donc l'action à effectuer. if ((f = p->Look4URL(file))) { SendHeads(&b, "text/html"); a = f->Do(Vars, Heads, &s); } else { ShowError(&b); } } else { // Dans tous les autres cas de domaine, on cherche le fichier dans le répertoire datas. // On utilise try au cas où le fichier n'existe pas et donc que le constructeur // d'input renvoie une erreur. try { Handle * i = new Input(String("datas/") + file); SendHeads(&b, GetMime(file), String("Accept-Ranges: bytes") + endhl + "Content-Length: " + (unsigned long long int) i->GetSize() + endhl, i->GetModif()); i->SetNonBlock(); a = new CopyJob(i, &s); // cerr << "File found, dumping.\n"; } catch (IOGeneral e) { ShowError(&b); // cerr << "File not found, error showed.\n"; } } } if (a) a->Stop(); delete Vars; delete Heads; // cerr << "---- Sending header buffer.\n"; c = new CopyJob(&b, &s, -1, false); WaitFor(c); current = 3; Suspend(); case 3: delete c; if (a) { // cerr << "---- Sending contents.\n"; a->Restart(); WaitFor(a); current = 4; Suspend(); } case 4: if (a) delete a; // cerr << "---- End of Request.\n"; } return TASK_DONE; } void ProcessRequest::ParseVars(Handle * s, int len) { String t, v; char conv[3], l; int hconv, nbvars; ssize_t pos = 0, next; t = ""; for (int i = 0; i < len; i++) { s->read(&l, 1); t += l; } // cerr << "Post variables line: '" << t << "'\n"; // Les variables sont sous la forme 'var1=val1&var2=val2&val3=var3'. Donc le nombre d'occurences // du caractère '=' indique le nombre de variables. nbvars = t.strchrcnt('='); Vars = new Variables(nbvars); for (int i = 0; i < nbvars; i++) { // Les variables sont sous la forme 'var1=val1&var2=val2&val3=var3'. Donc on cherche le caractère // & dans la chaine POST. next = t.strchr('&', pos); if (next < 0) next = t.strlen(); v = ""; while (pos != next) { switch (t[pos]) { // Le navigateur encode les caractères spéciaux à l'aide du format %XX où XX indique // la valeur hexadécimale du caractère. Nous encodons surtout les caractères // ' ', '=', '%', et '/' avec cette technique. case '%': pos++; conv[0] = t[pos++]; conv[1] = t[pos++]; conv[2] = '\0'; sscanf(conv, "%x", &hconv); v += ((char) hconv); break; // Certains navigateurs utilisent '+' pour indiquer ' ' (qui est illégal) au lieu // d'utiliser %20. case '+': v += ' '; pos++; break; default: v += t[pos++]; } Vars->SetTo(i, v); } pos++; } } /* * Cette fonction renverra true si la méthode est une méthode POST. * Les Strings domain et file seront modifiées afin de renvoyer le domaine * et le fichier lut. La string s doit donner la première ligne de la requète, * c'est à dire la méthode demandée par le client. */ bool ProcessRequest::ParseUri(String & file, String & domain, Handle * s) { String t, Uri; bool post = false; const char * p = 0; ssize_t sppos; *s >> t; // cerr << "Read Request (1): " << t << endl; bad = false; // p nous indiquera la position de la chaîne URL. switch (t[0]) { case 'P': /* POST? */ if (t.extract(1, 4) == "OST ") { p = t.to_charp(5); post = true; } else { // cerr << "Error: unknow request.\n"; bad = true; } break; case 'G': /* GET? */ if (t.extract(1, 3) == "ET ") { p = t.to_charp(4); } else { // cerr << "Error: unknow request.\n"; bad = true; } break; default: // cerr << "Error: unknow request.\n"; bad = true; } if (!bad) { ssize_t poshttp, posslash; Uri = p; sppos = Uri.strrchr(' '); p = Uri.to_charp(0, sppos - 1); Uri = p; // On enlève tout le host spécifié éventuellement dans la requete. if ((poshttp = Uri.strstr("http://")) > 0) { Uri = Uri.to_charp(poshttp + 7); posslash = Uri.strchr('/'); // Certains navigateurs indiqueront uniquement http://host comme URL. if (posslash >= 0) { host = Uri.extract(0, posslash - 1); Uri = Uri.to_charp(posslash); } else { host = Uri; Uri = ""; } } posslash = Uri.strrchr('/'); file = Uri.to_charp(posslash + 1); if (posslash > 0) { domain = Uri.to_charp(0, posslash - 1); } else { domain = ""; } } return post; } /* * Ceci sert à rediriger le navigateur vers l'url de démarrage. */ void ProcessRequest::SendRedirect(Handle * s) { *s << "HTTP/1.1 301 Moved Permanently" << endhl << "Server: " << name << endhl << "Location: http://" << host << "/bin/start" << endhl << "Cache-Control: no-cache" << endhl << "Connection: closed" << endhl << "Content-Type: text/html" << endhl << endhl << "301 - Moved Permanently" << endnl << "

You should be redirected to the " << endnl << endnl << "start page

" << endnl << "" << endnl; } /* * Nous envoyons les entetes de réponse HTTP. */ void ProcessRequest::SendHeads(Handle * s, const String & mime, const String & extra, time_t lm) { time_t t = time(NULL); struct tm * ft = gmtime(&t); char buf[1024]; strftime(buf, 1024, "%a, %d %b %Y %H:%M:%S GMT", ft); *s << "HTTP/1.1 200 OK" << endhl << "Date: " << buf << endhl << "Server: " << name << endhl; if (lm >=0) { ft = gmtime(&lm); strftime(buf, 1024, "%a, %d %b %Y %H:%M:%S GMT", ft); } *s << "Last-Modified: " << buf << endhl << extra << "Connection: closed" << endhl << "Content-Type: " << mime << endhl << endhl; } /* * Affichage d'une erreur 404. */ void ProcessRequest::ShowError(Handle * s) { *s << "HTTP/1.1 404 Not Found" << endhl << "Server: " << name << endhl << "Cache-Control: no-cache" << endhl << "Connection: closed" << endhl << "Content-Type: text/html" << endhl << endhl << "404 - Error" << endnl << "

The server was unable to process your query

" << endnl << "Click here to go the main page." << "" << endnl; } /* * Sert à déterminer le type mime à partir de l'extension du fichier. * Par défaut, nous mettons "text/plain". */ String ProcessRequest::GetMime(const String & f) { String ext; size_t ppos; ppos = f.strrchr('.'); if (ppos >= 0) { ext = f.extract(ppos + 1); if (ext == "jpg") return "image/jpeg"; if (ext == "jpeg") return "image/jpeg"; if (ext == "htm") return "text/html"; if (ext == "html") return "text/html"; if (ext == "gif") return "image/gif"; if (ext == "png") return "image/png"; if (ext == "class") return "application/octet-stream"; } return "text/plain"; } HttpServ::HttpServ(Action * ap, int port, const String & nname) throw (GeneralException) : p(ap), name(nname), localport(port) { bool r = true; // cerr << "Initialising Mini HTTP-Server on port " << localport << endl; r = Listener.SetLocal("", port); if (!r) { throw GeneralException("Initialisation of the Mini HTTP-Server failed: can't bind"); } r = Listener.Listen(); if (!r) { throw GeneralException("Initialisation of the Mini HTTP-Server failed: can't listen"); } Listener.SetNonBlock(); WaitFor(&Listener, W4_STICKY | W4_READING); // cerr << "Mini HTTP-Server '" << name << "' ready and listening for port " << port << endl; } HttpServ::~HttpServ(void) { Listener.close(); } int HttpServ::Do() { try { Socket s = Listener.Accept(); s.SetNonBlock(); new ProcessRequest(p, s, name, localport); } catch (GeneralException) { } return TASK_ON_HOLD; } String HttpServ::GetName() { return String("Mini HTTP-Server '") + name + "'"; }