#include "Socket.h" #include "Action.h" #include "HttpServ.h" #include "Buffer.h" #include "ReadJob.h" #include "config.h" String endhl = "\r\n", endnl = "\n"; HttpServ::HttpServ(int port, const String & nname) throw (GeneralException) : name(nname), localport(port) { bool r = true; r = Listener.SetLocal("", port); if (r) { r = Listener.Listen(); } if (!r) { throw GeneralException("Initialisation of the Mini HTTP-Server failed."); } cerr << "Mini HTTP-Server '" << name << "' ready and listening for port " << port << endl; } void HttpServ::MainLoop(Action * p) { while (1) { ProcessRequest(p, Listener.Accept()); } } void HttpServ::ProcessRequest(Action * p, Socket s) { String file, domain, t; Buffer b; Task * c = new ReadJob(&s, &b); Action * f; int len; s.SetNonBlock(); c->Run(); delete c; bad = false; if (!s.IsConnected()) return; cerr << "Got a request\n----\n"; bool post = ParseUri(file, domain, &b); len = -1; do { b >> t; cerr << 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(); } } while (t.strlen()); 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) { bad = true; Vars = new Variables(0); } else { cerr << "Got a POST request. Parsing variables. (len = " << len << ")\n"; // Les variables seront initialisées ici. ParseVars(&s, len); } } else { Vars = new Variables(0); } 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; } else { // L'url sans domaine ni fichier est valide. (cela arrive sur certains navigateurs...) bad = (file != ""); } } if (bad) { ShowError(&s); } else { if (((domain == "") || (domain == "/")) && (file == "")) { // Si le navigateur a demandé l'URL '/', alors on renvoie une notification // de redirection. SendRedirect(&s); } else if (domain == "/bin") { // Le domaine 'bin' est réservé aux actions. On cherche donc l'action à effectuer. if ((f = p->Look4URL(file))) { SendHeads(&s, "text/html"); f->Do(Vars, &s); } else { ShowError(&s); } } 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 { Input i(String("datas/") + file); SendHeads(&s, GetMime(file)); s.ReadFile(i); cerr << "File found, dumped.\n"; } catch (IOGeneral e) { ShowError(&s); cerr << "File not found, error showed.\n"; } } } delete Vars; cerr << "----\n"; } void HttpServ::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 HttpServ::ParseUri(String & file, String & domain, Handle * s) { String t, Uri; bool post = false; const char * p = NULL; ssize_t sppos; *s >> t; cerr << 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; break; } case 'G': /* GET? */ if (t.extract(1, 3) == "ET ") { p = t.to_charp(4); break; } default: 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) { Uri = Uri.to_charp(posslash); } else { 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 HttpServ::SendRedirect(Handle * s) { *s << "HTTP/1.1 301 Moved Permanently" << endhl << "Server: " << name << endhl << "Location: http://127.0.0.1:" << localport << "/bin/start" << endhl << "Cache-Control: no-cache" << endhl << "Connection-Type: 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 HttpServ::SendHeads(Handle * s, const String & mime) { *s << "HTTP/1.1 200 OK" << endhl << "Server: " << name << endhl << "Cache-Control: no-cache" << endhl << "Connection-Type: closed" << endhl << "Content-Type: " << mime << endhl << endhl; } /* * Affichage d'une erreur 404. */ void HttpServ::ShowError(Handle * s) { *s << "HTTP/1.1 404 Not Found" << endhl << "Server: " << name << endhl << "Cache-Control: no-cache" << endhl << "Connection-Type: 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 HttpServ::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"; } return "text/plain"; }