summaryrefslogtreecommitdiff
path: root/lib/HttpServ.cc
diff options
context:
space:
mode:
authorPixel <Pixel>2001-09-20 23:27:01 +0000
committerPixel <Pixel>2001-09-20 23:27:01 +0000
commit8346d0774d2d1e076038db27f65f1d082a460f16 (patch)
tree132f84cf1ef45d5006a2b1d52d4d40b1e8e51abc /lib/HttpServ.cc
Initial revision
Diffstat (limited to 'lib/HttpServ.cc')
-rw-r--r--lib/HttpServ.cc297
1 files changed, 297 insertions, 0 deletions
diff --git a/lib/HttpServ.cc b/lib/HttpServ.cc
new file mode 100644
index 0000000..dfead28
--- /dev/null
+++ b/lib/HttpServ.cc
@@ -0,0 +1,297 @@
+#include "Action.h"
+#include "HttpServ.h"
+#include "Socket.h"
+#include "config.h"
+
+String endhl = "\r\n", endnl = "\n";
+
+HttpServ::HttpServ(int port, const String & nname) : name(nname), localport(port) {
+ Listener.SetLocal("", port);
+ Listener.Listen();
+ cerr << "Mini HTTP-Server '" << name << "' ready and listening for port " << port << endl;
+}
+
+void HttpServ::MainLoop(Action * p) {
+ while (1) {
+ ProcessRequest(p);
+ }
+}
+
+void HttpServ::ProcessRequest(Action * p) {
+ Socket s(Listener.Accept());
+ String file, domain, t;
+ Action * f;
+ int len;
+
+ bad = false;
+
+ if (!s.IsConnected()) return;
+
+ cerr << "Got a request\n----\n";
+
+ bool post = ParseUri(file, domain, s);
+
+
+ len = -1;
+ do {
+ s >> t;
+ cerr << t << endl;
+ if ((t.strstr("Content-Length: ") == 0) || (t.strstr("Content-length: ") == 0)) {
+ cerr << "Saw 'Content-Lenght:', reading length from '" << t.to_charp(16) << "'\n";
+ len = String(t.to_charp(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(Socket & 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, Socket & s) {
+ String t, Uri;
+ bool post = false;
+ char * p;
+ 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? */
+ p = t.to_charp(1, 4);
+ if (!strcmp(p, "OST ")) {
+ p = t.to_charp(5);
+ post = true;
+ break;
+ }
+ case 'G': /* GET? */
+ p = t.to_charp(1, 3);
+ if (!strcmp(p, "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(Socket & 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 <<
+ "<HTML><HEAD><TITLE>301 - Moved Permanently</TITLE></HEAD>" << endnl <<
+ "<BODY><center><b><h2>You should be redirected to the " << endnl << endnl <<
+ "<a href=\"http://localhost/bin/start\">start page</a></h2></b></center>" << endnl <<
+ "</BODY></HTML>" << endnl;
+}
+
+/*
+ * Nous envoyons les entetes de réponse HTTP.
+ */
+
+void HttpServ::SendHeads(Socket & 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(Socket & 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 <<
+ "<HTML><HEAD><TITLE>404 - Error</TITLE></HEAD>" << endnl <<
+ "<BODY><center><b><h2>The server was unable to process your query</h2></b></center>" << endnl <<
+ "Click <A HREF=\"/\">here</A> to go the main page." <<
+ "</BODY></HTML>" << 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.to_charp(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";
+}