diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Action.cc | 87 | ||||
-rw-r--r-- | lib/Confirm.cc | 26 | ||||
-rw-r--r-- | lib/Exceptions.cc | 88 | ||||
-rw-r--r-- | lib/Form.cc | 56 | ||||
-rw-r--r-- | lib/Handle.cc | 110 | ||||
-rw-r--r-- | lib/HttpServ.cc | 297 | ||||
-rw-r--r-- | lib/Input.cc | 32 | ||||
-rw-r--r-- | lib/Makefile.am | 12 | ||||
-rw-r--r-- | lib/Menu.cc | 26 | ||||
-rw-r--r-- | lib/Message.cc | 21 | ||||
-rw-r--r-- | lib/Output.cc | 32 | ||||
-rw-r--r-- | lib/Socket.cc | 140 | ||||
-rw-r--r-- | lib/String.cc | 291 | ||||
-rw-r--r-- | lib/Table.cc | 41 | ||||
-rw-r--r-- | lib/Variables.cc | 55 | ||||
-rw-r--r-- | lib/checkargs.c | 85 | ||||
-rw-r--r-- | lib/datecalc.c | 84 |
17 files changed, 1483 insertions, 0 deletions
diff --git a/lib/Action.cc b/lib/Action.cc new file mode 100644 index 0000000..292e669 --- /dev/null +++ b/lib/Action.cc @@ -0,0 +1,87 @@ +#include "String.h" +#include "Action.h" +#include "HttpServ.h" +#include "config.h" + +Action * Action::start = 0; + +static int counter = 0; + +static String genurl(const String & u) { + if (u.strlen()) { + return u; + } else { + // Si l'url passée en paramètre est vide, on génère une URL + // sous la forme TmpXXXX où XXXX est une valeur qui s'incrémente. + return String("Tmp") + (counter++); + } +} + +Action::Action(const String & u) : next(start), prev(0), URL(genurl(u)), hastoclean(false), accessed(false) { + start = this; + if (next) next->prev = this; +} + +Action::~Action() { + if (start == this) { + start = next; + } + if (next) next->prev = prev; + if (prev) prev->next = next; +} + +Action * Action::Look4URL(const String & URL) { + Action * p; + + for (p = start; p; p = p->next) { + if (!p->GetURL().strlen()) { + // Si l'action que l'on vient de tester n'a pas d'URL, elle ne sert à rien. + // On l'efface donc de notre liste. GetURL renvoie une chaine vide si l'action + // était déclarée en mode CleanUp, puis a été accédée. + delete p; + } else { + if (URL == p->GetURL()) return p; + } + } + + return 0; +} + +/* + * Voici la skin principale. Elle nécessite un fichier 'grain.png' dans le répertoire datas + * et un fichier 'style.css' (fournis) + */ + +void Action::SendHead(Handle * h) { + (*h) << "<HTML><HEAD><TITLE>" << GetTitle() << "</TITLE>" << endnl << + "<META http-equiv=\"Content-Style-Type\" content=\"text/css\">" << endnl << + "<LINK REL=STYLESHEET HREF=\"/image/style.css\" TYPE=\"text/css\">" << endnl << + "</HEAD><BODY BGCOLOR=\"#aaaaaa\" BACKGROUND=\"/image/grain.png\">" << endnl << + "<center><TABLE WIDTH=\"50%\" BORDER=3 cellspacing=1 BGCOLOR=\"#ffffff\"><TR><TD>" << endnl << + "<center><b><h1>" << GetTitle() << "</h1></b></center></TD></TR></TABLE><P>" << endnl << + "<TABLE WIDTH=\"80%\" BORDER=2 cellspacing=1 BGCOLOR=\"#ffffff\"><TR><TD>" << endnl; +} + +void Action::SendFoot(Handle * h) { + (*h) << "</TABLE></TD></TR></BODY></HTML>" << endnl; +} + +String Action::GetURL(void) { + // Comme décrit plus haut, il faut renvoyer la chaîne vide si l'action est en + // mode CleanUp et a été lue. + return (hastoclean && accessed) ? "" : URL; +} + +void Action::CleanUp(void) { + hastoclean = true; +} + +void Action::Accessed(void) { + accessed = true; +} + +void Action::ShowButton(Handle * h, const String & l, const String & u) { + (*h) << "<CENTER><FORM METHOD=\"POST\" ACTION=\"/bin/" << u << "\">" << endnl << + "<INPUT TYPE=\"SUBMIT\" VALUE=\"" << l << "\">" << endnl + << "</FORM></CENTER>" << endnl; +} diff --git a/lib/Confirm.cc b/lib/Confirm.cc new file mode 100644 index 0000000..5177f8b --- /dev/null +++ b/lib/Confirm.cc @@ -0,0 +1,26 @@ +#include "HttpServ.h" +#include "Confirm.h" +#include "config.h" + +Confirm::Confirm(const String & t, const String & m, const String & U, Action * y, Action * n) : + Action(U), tit(t), msg(m), NYes(y), NNo(n) { } + +void Confirm::Do(Variables * v, Handle * h) { + SendHead(h); + (*h) << msg << "<CENTER><TABLE BORDER=0><tr><td>" << endnl << + "<FORM METHOD=\"POST\" ACTION=\"/bin/" << (NYes ? NYes->GetURL() : "start") << "\">" << endnl << + "<INPUT TYPE=\"SUBMIT\" VALUE=\" Oui \">" << endnl; + v->Dump(h); + (*h) << "</FORM></td><td>" << endnl << + "<FORM METHOD=\"POST\" ACTION=\"/bin/" << (NNo ? NNo->GetURL() : "start") << "\">" << endnl << + "<INPUT TYPE=\"SUBMIT\" VALUE=\" Non \">" << endnl; + v->Dump(h); + (*h) << "</FORM></td></tr></TABLE></CENTER>" << endnl; + SendFoot(h); + + Accessed(); +} + +String Confirm::GetTitle(void) { + return tit; +} diff --git a/lib/Exceptions.cc b/lib/Exceptions.cc new file mode 100644 index 0000000..b5a33bd --- /dev/null +++ b/lib/Exceptions.cc @@ -0,0 +1,88 @@ +#include <malloc.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stddef.h> +#include "config.h" +#include "String.h" +#include "Exceptions.h" + +char GeneralException::t[BUFSIZ]; + +char * Base::strdup(const char * s) const { + return xstrdup(s); +} + +void * Base::malloc(ssize_t s) const { + return xmalloc(s); +} + +/* +void * Base::operator new(size_t s) { + return memset(xmalloc(s), 0, s); +} + +void * Base::operator new(size_t s, void * & p) { + return memset(p = xmalloc(s), 0, s); +} +*/ + +GeneralException::GeneralException(String emsg) : msg(strdup(emsg.to_charp())) { } +GeneralException::GeneralException() : msg(0) { } +GeneralException::GeneralException(const GeneralException & e) : msg(strdup(e.msg)) { } + +GeneralException::~GeneralException() { + free(msg); +} + +char * GeneralException::GetMsg() { + return msg; +} + +MemoryException::MemoryException(ssize_t s) { + sprintf(t, _("Failed allocating %lld bytes."), s); + msg = strdup(t); +} + +IOException::IOException(String fn, op_t op, ssize_t s) { + sprintf(t, _("An error has occured while %s %lld bytes from %s: %s"), op == IO_WRITE ? _("writing") : _("reading"), + s, fn.to_charp(), strerror(errno)); + msg = strdup(t); +} + +IOInternal::IOInternal(String fn, op_t op) { + sprintf(t, _("Internal error: has occured while %s from %s: open for %s."), op == IO_WRITE ? _("writing") : _("reading"), + fn.to_charp(), op == IO_WRITE ? _("reading") : _("writing")); + msg = strdup(t); +} + +IOGeneral::IOGeneral(String fn) : GeneralException(fn) { } + +char * xstrdup(const char * s) throw (MemoryException) { + char * r; + + if (!(r = ::strdup(s))) { + throw MemoryException(strlen(s + 1)); + } + + return r; +} + +void * xmalloc(ssize_t s) throw (MemoryException) { + void * r; + + if (!(r = ::malloc(s))) { + throw MemoryException(s); + } + + return r; +} + +#undef free + +void xfree(void *& p) { + if (p) { + ::free(p); + p = 0; + } +} diff --git a/lib/Form.cc b/lib/Form.cc new file mode 100644 index 0000000..5fb885a --- /dev/null +++ b/lib/Form.cc @@ -0,0 +1,56 @@ +#include "Form.h" +#include "HttpServ.h" + +Form::Form(const String & titre, const String & url, const String & inv, String * names, String * invs, + String * defaults, String ** lists, String ** descs, int nb, Action * na) : + Action(url), tit(titre), iv(inv), nms(names), ivs(invs), defs(defaults), lsts(lists), dscs(descs), n(nb), Next(na) { } + +String Form::GetTitle(void) { + return tit; +} + +void Form::Do(Variables * v, Handle * h) { + SendHead(h); + + (*h) << +"<center>" << endnl << +"<form ACTION=\"/bin/" << (Next ? Next->GetURL() : "start") << "\" METHOD=POST>" << endnl; + + v->Dump(h); + + (*h) << +"<table BORDER=0 CELLSPACING=0 BGCOLOR=\"#000000\"><tr><td>" << endnl << +"<table BORDER=0 CELLSPACING=0 CELLPADDING=3 WIDTH=\"300\" BGCOLOR=\"#FFFFCC\">" << endnl << +"<tr><td ALIGN=CENTER WIDTH=\"100%\" BGCOLOR=\"#000000\"><b><font FACE=\"arial,helvetica\" COLOR=\"#FFFFFF\">" << endnl << +iv << endnl << +"</font></b></td></tr>" << endnl; + + for (int i = 0; i < n; i++) { + (*h) << +"<tr><td>" << ivs[i] << "</td></tr>" << endnl; + if (lsts[i]) { + String * s, * t; + (*h) << +"<tr><td><select NAME=\"" << nms[i] << "\">" << endnl; + for (s = lsts[i], t = dscs[i]; s->strlen(); s++, t++) { + (*h) << +"<option VALUE=\"" << *s << "\">" << *t << "</option>" << endnl; + } + (*h) << +"</select></td></tr>" << endnl; + } else { + (*h) << +"<tr><td><input TYPE=\"text\" NAME=\"" << nms[i] << "\" VALUE=\"" << defs[i] << "\" size=40></tr></td>" << endnl; + } + } + + (*h) << + +"<tr><td ALIGN=\"center\"><input TYPE=\"submit\" VALUE=\" Ok \"></td></tr>" << endnl << +"</table></td></tr></table></form></center>" << endnl; + + SendFoot(h); + + Accessed(); +} + diff --git a/lib/Handle.cc b/lib/Handle.cc new file mode 100644 index 0000000..8108285 --- /dev/null +++ b/lib/Handle.cc @@ -0,0 +1,110 @@ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include "Handle.h" +#include "config.h" + +Handle::Handle(const Handle & nh) : h(dup(nh.h)), closed(false) { } + +Handle::~Handle() { + if (h != -1) { + ::close(h); + } +} + +Handle::Handle(int nh) : h(nh), closed(false) { } + +int Handle::GetHandle() { + return h; +} + +ssize_t Handle::write(const void *buf, size_t count) throw (IOException) { + ssize_t r, tr = 0; + bool done, full = false; + + do { + done = true; + if ((r = ::write(h, buf, count)) < 0) { + if ((!errno) || (errno = EAGAIN)) { + // Avant de déclarer une erreur, on vérifie si ce n'est pas un + // problème lié au fait qu'il n'y a plus de place libre. Cela peut + // arriver si l'on agit sur un pipe ou un handle. Nous + // attendons encore une fois avant de déclarer l'erreur, + // grace au drapeau full. + if (full) { + throw IOException(GetName(), IO_WRITE, count); + } else { + sleep(1); + done = false; + full = true; + tr += r; + } + } else { + throw IOException(GetName(), IO_WRITE, count); + } + } else if (r != count) { + full = done = false; + ((char *)buf) += r; + tr += r; + } + } while (!done); + + return r + tr; +} + +ssize_t Handle::read(void *buf, size_t count) throw (IOException) { + ssize_t r; + + if ((r = ::read(h, buf, count)) < 0) { + if ((!errno) || (errno = EAGAIN)) { + // Avant de déclarer une erreur, on vérifie si ce n'est pas un + // problème lié au fait qu'il n'y a plus d'octets. + return 0; + } else { + throw IOException(GetName(), IO_READ, count); + } + } + + if (!r) { + closed = true; + } + + return r; +} + +bool Handle::IsClosed(void) { + return closed; +} + +Handle & operator<<(Handle & h, const String & s) { + char * p; + + p = s.to_charp(); + h.write(p, strlen(p)); + + return h; +} + +Handle & operator>>(Handle & h, String & s) { + char t[BUFSIZ]; + int i = 0, r; + + while ((r = h.read(&(t[i]), 1)) && (i != (BUFSIZ - 1))) { + // Il y a souvent des \r\n dans les sockets par exemple. + // On ignore le \r pour ne garder que le \n, standard sous + // Unix. + if (t[i] == '\r') { + continue; + } + if (t[i] == '\n') { + break; + } else { + i++; + } + } + + t[i] = '\0'; + s.set("%s", t); + return h; +} 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"; +} diff --git a/lib/Input.cc b/lib/Input.cc new file mode 100644 index 0000000..f4e2602 --- /dev/null +++ b/lib/Input.cc @@ -0,0 +1,32 @@ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "Input.h" +#include "Exceptions.h" +#include "config.h" + +Input::Input(String no) throw (IOGeneral) : + Handle(no.strlen() ? open(no.to_charp(), O_RDONLY) : 0), + n(no) { + if (GetHandle() < 0) { + throw IOGeneral(String("Error opening file") + no + " for reading: " + strerror(errno)); + } +} + +bool Input::CanWrite() { + return 0; +} + +bool Input::CanRead() { + return 1; +} + +String Input::GetName() { + return n; +} + diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..5149387 --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,12 @@ +localedir = $(datadir)/locale +DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ +AM_CFLAGS = -O3 -Wall -Wstrict-prototypes $(CFLAGS) +AM_CXXFLAGS = -O3 -Wall -Wstrict-prototypes $(CXXFLAGS) +INCLUDES = -I. -I.. -I$(includedir) -I../include +lib_LTLIBRARIES = libClasses.la + +libClasses_la_SOURCES = Exceptions.cc Handle.cc Output.cc String.cc\ + Socket.cc Input.cc HttpServ.cc Variables.cc Action.cc Menu.cc Message.cc\ + Form.cc Confirm.cc Table.cc checkargs.c datecalc.c + +libClasses_la_LDFLAGS = -version-info $(Classes_VERSION_INFO) diff --git a/lib/Menu.cc b/lib/Menu.cc new file mode 100644 index 0000000..7488d1d --- /dev/null +++ b/lib/Menu.cc @@ -0,0 +1,26 @@ +#include "Menu.h" +#include "HttpServ.h" + +Menu::Menu(const String & t, const String & U, String * ts, Action ** as, int nb) : + Action(U), tit(t), lt(ts), la(as), nba(nb) { } + +void Menu::Do(Variables * v, Handle * h) { + int i, f = 0; + + SendHead(h); + (*h) << "<center><TABLE BORDER=0>" << endnl; + for (i = 0; i < nba; i++) { + (*h) << "<TR BGCOLOR=\"#" << (f ? "dddddd" : "cccccc") << "\"><TD ALIGN=\"center\">" << (i + 1) << "</TD><TD>" + "<A HREF=\"/bin/" << (la[i] ? la[i]->GetURL() : "start") << "\">" + << lt[i] << "</A></TD></TR>" << endnl; + f = f ? 0 : 1; + } + (*h) << "</TABLE></center>" << endnl; + SendFoot(h); + + Accessed(); +} + +String Menu::GetTitle(void) { + return tit; +} diff --git a/lib/Message.cc b/lib/Message.cc new file mode 100644 index 0000000..1ede8fb --- /dev/null +++ b/lib/Message.cc @@ -0,0 +1,21 @@ +#include "HttpServ.h" +#include "Message.h" +#include "config.h" + +Message::Message(const String & t, const String & m, const String & U, Action * n) : + Action(U), tit(t), msg(m), Next(n) { } + +void Message::Do(Variables * v, Handle * h) { + SendHead(h); + (*h) << msg << "<CENTER><FORM METHOD=\"POST\" ACTION=\"/bin/" << (Next ? Next->GetURL() : "start") << "\">" << endnl << + "<INPUT TYPE=\"SUBMIT\" VALUE=\" Ok \">" << endnl; + v->Dump(h); + (*h) << "</FORM></CENTER>" << endnl; + SendFoot(h); + + Accessed(); +} + +String Message::GetTitle(void) { + return tit; +} diff --git a/lib/Output.cc b/lib/Output.cc new file mode 100644 index 0000000..75bb437 --- /dev/null +++ b/lib/Output.cc @@ -0,0 +1,32 @@ +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "Output.h" +#include "Exceptions.h" +#include "config.h" + +Output::Output(String no, int trunc = 1) throw (IOGeneral) : + Handle(no.strlen() ? open(no.to_charp(), O_WRONLY | O_CREAT | (trunc ? O_TRUNC : O_APPEND)) : 1), + n(no) { + if (GetHandle() < 0) { + throw IOGeneral(String("Error opening file") + no + " for writing: " + strerror(errno)); + } +} + +bool Output::CanWrite() { + return 1; +} + +bool Output::CanRead() { + return 0; +} + +String Output::GetName() { + return n; +} + diff --git a/lib/Socket.cc b/lib/Socket.cc new file mode 100644 index 0000000..100d434 --- /dev/null +++ b/lib/Socket.cc @@ -0,0 +1,140 @@ +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include "String.h" +#include "Socket.h" +#include "Exceptions.h" +#include "config.h" +#include "Input.h" +#include "Output.h" + +Socket::Socket() throw (GeneralException) : Handle(socket(AF_INET, SOCK_STREAM, 0)), connected(false), listening(false) { + if (GetHandle() < 0) { + throw GeneralException("Error creating socket."); + } +} + +Socket::Socket(int h) : Handle(h), connected(true), listening(false) { } + +String Socket::GetName(void) { + return String("socket"); +} + +bool Socket::IsConnected(void) { + return connected; +} + +bool Socket::IsListening(void) { + return listening; +} + +bool Socket::CanRead(void) { + return connected; +} + +bool Socket::CanWrite(void) { + return connected; +} + +size_t Socket::WriteFile(Output & o) { + char c; + size_t s = 0; + + while (!IsClosed()) { + s += read(&c, 1); + o.write(&c, 1); + } + return s; +} + +size_t Socket::ReadFile(Input & i) { + char c; + size_t s = 0; + + while (!i.IsClosed()) { + s += i.read(&c, 1); + write(&c, 1); + } + return s; +} + + + /***********************************************\ + * Toute la suite n'est pas à décrire. Consulter * + * plutôt un document décrivant les sockets. * + \***********************************************/ + + +bool Socket::SetLocal(String vhost, int port) { + struct hostent * localhostent; + struct in_addr localhostaddr; + struct sockaddr_in localsocketaddr; + + + memset((void *)&localhostaddr, 0, sizeof(localhostaddr)); + + if (vhost.strlen() != 0) { + if ((localhostent = gethostbyname(vhost.to_charp()))) { + memcpy((void *)&localhostaddr, localhostent->h_addr, sizeof(localhostaddr)); + } else { + return false; + } + } else { + localhostaddr.s_addr = htonl(INADDR_ANY); + } + + memset(&localsocketaddr, 0, sizeof(struct sockaddr_in)); + localsocketaddr.sin_family = AF_INET; + localsocketaddr.sin_addr = localhostaddr; + localsocketaddr.sin_port = htons(port); + + if (bind(GetHandle(), (struct sockaddr *) &localsocketaddr, sizeof(localsocketaddr)) < 0) { + return false; + } else { + return true; + } +} + +bool Socket::Connect(String host, int port) { + struct hostent * remotehostent; + struct sockaddr_in remotesocketaddr; + + if (!listening && !connected) { + if (!(remotehostent = gethostbyname(host.to_charp()))) { + return false; + } + + remotesocketaddr.sin_family = AF_INET; + remotesocketaddr.sin_port = htons(port); + bcopy(remotehostent->h_addr, &remotesocketaddr.sin_addr, remotehostent->h_length); + if(connect(GetHandle(), (struct sockaddr *)&remotesocketaddr, sizeof(remotesocketaddr)) < 0) { + connected = true; + } + } + return connected; +} + +bool Socket::Listen(void) { + if (!listening && !connected) { + if (listen(GetHandle(), 10)) { + listening = true; + } + } + return listening; +} + +Socket Socket::Accept(void) { + struct sockaddr inaddr; + socklen_t inlen = sizeof(inaddr); + int h; + + if ((h = accept(GetHandle(), &inaddr, &inlen)) < 0) { + return Socket(); + } else { + return Socket(h); + } +} diff --git a/lib/String.cc b/lib/String.cc new file mode 100644 index 0000000..16c7db3 --- /dev/null +++ b/lib/String.cc @@ -0,0 +1,291 @@ +#include <iostream.h> +#include <string.h> +#include <stdarg.h> +#include "String.h" +#include "Exceptions.h" +#include "config.h" + +extern "C" { + double dateCalc(char *, char *); + int isDateArgument(char *); +} + +char String::t[BUFSIZ]; + +String::String(const String & s) : str(strdup(s.str)) { } + +String::String(char c) { + char t[2]; + + sprintf(t, "%c", c); + str = strdup(t); +} + +String::String(const char * s) : str(s ? strdup(s) : strdup("")) { } + +String::String(int i) { + char t[20]; + + sprintf(t, "%i", i); + str = strdup(t); +} + +String::String(double d) { + char t[30]; + + sprintf(t, "%g", d); + str = strdup(t); +} + +String::~String() { + free(str); +} + +char * String::set(char * s, ...) { + va_list ap; + + va_start(ap, s); + vsprintf(t, s, ap); + free(str); + str = strdup(t); + va_end(ap); + return t; +} + +char * String::to_charp(size_t from, ssize_t to) const { + if (to < 0) { + strcpy(t, &(str[from])); + } else { + if (to >= strlen()) { + to = strlen() - 1; + } + if (to >= from) { + int i; + for (i = 0; i <= to - from; i++) { + t[i] = str[i + from]; + } + t[i] = '\0'; + } else { + t[0] = '\0'; + } + } + return t; +} + +int String::to_int(void) const { + int r; + + sscanf(str, "%i", &r); + return r; +} + +double String::to_double(void) const { + double r; + + sscanf(str, "%lf", &r); + return r; +} + +String & String::operator=(const String & s) { + if (str != s.str) { + // On évite l'autodestruction... + free(str); + str = strdup(s.str); + } + return *this; +} + +String String::operator+(const String & s) const { + strcpy(t, str); + strcat(t, s.str); + return String(t); +} + +String & String::operator+=(const String & s) { + strcpy(t, str); + strcat(t, s.str); + free(str); + str = strdup(t); + return (*this); +} + +ostream & operator<<(ostream & os, const String & s) { + return (os << s.to_charp()); +} + +istream & operator>>(istream & is, String & s) { + char c; + + s.set(""); + + while (!is.eof()) { + is >> c; + s += c; + } + + return is; +} + +bool String::operator!=(const String & s) const { + return (strcmp(str, s.str) != 0); +} + +bool String::operator==(const String & s) const { + return (strcmp(str, s.str) == 0); +} + +bool String::operator<=(const String & s) const { + return (strcmp(str, s.str) <= 0); +} + +bool String::operator>=(const String & s) const { + return (strcmp(str, s.str) >= 0); +} + +bool String::operator<(const String & s) const { + return (strcmp(str, s.str) < 0); +} + +bool String::operator>(const String & s) const { + return (strcmp(str, s.str) > 0); +} + +size_t String::strlen() const { + return (str ? ::strlen(str) : 0); +} + +char String::operator[](size_t i) const { + if (i >= strlen()) { + return 0; + } else { + return str[i]; + } +} + +ssize_t String::strchr(char c, size_t from) const { + size_t s = strlen(); + + for (size_t i = from; i < s; i++) { + if (str[i] == c) return i; + } + + return -1; +} + +ssize_t String::strrchr(char c) const { + size_t s = strlen(); + + for (size_t i = s - 1; i >= 0; i--) { + if (str[i] == c) return i; + } + + return -1; +} + +ssize_t String::strstr(const String & s) const { + char * p = ::strstr(str, s.str); + + if (p) { + return p - str; + } else { + return -1; + } +} + +int String::strchrcnt(char c) const { + size_t i, cnt = 0; + size_t s = strlen(); + + for (i = 0; i < s; i++) { + if (str[i] == c) cnt++; + } + + return cnt; +} + +String String::to_sqldate(void) const { +/* DD/MM/YYYY ==> YYYYMMMDD */ + return (is_date() ? String(to_charp(6, 9)) + to_charp(3, 4) + to_charp(0, 1) : ""); +} + +String String::to_sqltime(void) const { +/* h:m ==> h * 60 + m */ + int p = strchr(':'); + return (is_time() ? String(String(to_charp(0, p - 1)).to_int() * 60 + String(to_charp(p + 1)).to_int()) : ""); +} + +String String::from_sqldate(void) const { +/* YYYYMMDD ==> DD/MM/YYYY */ + return ((strlen() == 8) && is_number() ? String(to_charp(6, 7)) + '/' + to_charp(4, 5) + '/' + to_charp(0, 3) : ""); +} + +String String::from_sqltime(void) const { +/* t ==> (t / 60):(t % 60) */ + int t = to_int(); + return (is_number() ? String((int) (t / 60)) + ':' + (t % 60) : ""); +} + +bool String::is_date(void) const { +/* 'DD/MM/YYYY' + 0123456789 */ + + if (strlen() != 10) return false; + if ((str[2] != '/') || (str[5] != '/') || + (!String(to_charp(0, 1)).is_number()) || + (!String(to_charp(3, 4)).is_number()) || + (!String(to_charp(6, 9)).is_number())) { + return (isDateArgument(to_sqldate().str)); + } + + return true; +} + +double String::datedif(const String & s) const { + double r; + if (is_date() && s.is_date()) { + r = dateCalc(str, s.str); + return r < 0 ? -r : r; + } + + return -1; +} + +bool String::is_number(void) const { + size_t s = strlen(); + + + for (size_t i = ((str[i] == '-') ? 1 : 0); i < s; i++) { + if ((str[i] > '9') || (str[i] < '0')) return false; + } + return true; +} + +bool String::is_float(void) const { + size_t s = strlen(); + bool seendot = false; + + for (size_t i = ((str[i] == '-') ? 1 : 0); i < s; i++) { + if ((str[i] > '9') || (str[i] < '0')) { + if ((str[i] == '.') && !seendot) { + seendot = true; + } else { + return false; + } + } + } + + return true; +} + +bool String::is_time(void) const { + int p = strchr(':'); + + if (p == -1) return false; + + // On accepte les heures sous le format xxxxxx:yy pour pouvoir indiquer des durées. + + if ((!String(to_charp(0, p - 1)).is_number()) || (!String(to_charp(p + 1)).is_number())) + return false; + + return (String(to_charp(p + 1)).to_int() < 60) ? true : false; +} diff --git a/lib/Table.cc b/lib/Table.cc new file mode 100644 index 0000000..776caae --- /dev/null +++ b/lib/Table.cc @@ -0,0 +1,41 @@ +#include "Table.h" +#include "HttpServ.h" + +Table::Table(const String & titre, const String & url, String * heads, String * cells, int nbc, int nbl, Action * na) : + Action(url), tit(titre), hds(heads), cls(cells), nc(nbc), nl(nbl), Next(na) { } + +String Table::GetTitle(void) { + return tit; +} + +void Table::Do(Variables * v, Handle * h) { + SendHead(h); + + (*h) << "<center><TABLE BORDER=0>" << endnl; + + if (hds) { + (*h) << "<TR>" << endnl; + for (int i = 0; i < nc; i++) { + (*h) << "<TH BGCOLOR=\"#bbbbbb\">" << hds[i] << "</TH>" << endnl; + } + (*h) << "</TR>" << endnl; + } + + for (int l = 0; l < nl; l++) { + (*h) << "<TR>" << endnl; + for (int c = 0; c < nc; c++) { + (*h) << "<TD BGCOLOR=\"#" << (l % 2 ? "cccccc" : "dddddd") << "\">" << cls[l * nc + c] << "</TD>" << endnl; + } + (*h) << "</TR>" << endnl; + } + + (*h) << "</TABLE>" + +"<FORM METHOD=\"POST\" ACTION=\"/bin/" << (Next ? Next->GetURL() : "start") << "\">" << endnl << +"<INPUT TYPE=\"SUBMIT\" VALUE=\" Ok \">" << endnl; +(*h) << "</FORM></CENTER>" << endnl; + + + SendFoot(h); + Accessed(); +} diff --git a/lib/Variables.cc b/lib/Variables.cc new file mode 100644 index 0000000..15de09d --- /dev/null +++ b/lib/Variables.cc @@ -0,0 +1,55 @@ + +#include "Variables.h" +#include "HttpServ.h" +#include "String.h" +#include "config.h" + +Variables::Variables(int nb) : Vars(nb ? new String[nb] : 0), nbvars(nb) { } + +Variables::~Variables() { + if (Vars) { + delete[] Vars; + } +} + +void Variables::SetTo(int i, const String & s) { + Vars[i] = s; +} + +String Variables::operator[](const String & name) { + int i; + String r; + + for (i = 0; i < nbvars; i++) { + if (Vars[i].strstr(name) == 0) break; + } + + if (i == nbvars) { + r = ""; + } else { + r = Vars[i].to_charp(Vars[i].strchr('=') + 1); + } + + return r; +} + +String Variables::operator[](int i) { + return Vars[i]; +} + +int Variables::GetNb(void) { + return nbvars; +} + +void Variables::Dump(Handle * h) { + int i, eqp; + String Vn, Vv; + + for (i = 0; i < nbvars; i++) { + eqp = Vars[i].strchr('='); + Vn = Vars[i].to_charp(0, eqp - 1); + Vv = Vars[i].to_charp(eqp + 1); + (*h) << "<INPUT TYPE=\"HIDDEN\" NAME=\"" << Vn << "\" VALUE=\"" << Vv << "\">" << endnl; + } +} + diff --git a/lib/checkargs.c b/lib/checkargs.c new file mode 100644 index 0000000..e1c97be --- /dev/null +++ b/lib/checkargs.c @@ -0,0 +1,85 @@ +/* datedif - calculates the difference in days between two dates
+ * Copyright (C) 2000 Micael Widell contact: xeniac@linux.nu
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdlib.h> +#include <string.h> +
+int isDateArgument(char* dateString) {
+
+ const int MONTHS[] = {31,0,31,30,31,30,31,31,30,31,30,31};
+ int i, day, month, year;
+ char buffer[5];
+
+ /* 'today' is a valid date */
+ if (strcmp(dateString, "today") == 0)
+ return 1;
+
+ /* Numeric dates must be eight characters long */
+ if (8 != strlen(dateString))
+ return 0;
+
+ /* Check that the date is entirely formed of numbers */
+ for (i = 0; i < 8; i++) {
+ if (dateString[i] < '0' || dateString[i] > '9')
+ return 0;
+ }
+
+ /* Check that the date exists */
+ memset(buffer, 0, 5);
+ strncpy(buffer, dateString + 6, 2);
+ day = atoi(buffer);
+ strncpy(buffer, dateString + 4, 2);
+ month = atoi(buffer);
+ month -= 1;
+ strncpy(buffer, dateString, 4);
+ year = atoi(buffer);
+
+ /* Validate month */
+ if (month < 0 || month > 11)
+ return 0;
+
+ /* Validating dates is simple when the date does not fall into February */
+ if (1 != month) {
+ if (day < 1 || day > MONTHS[month])
+ return 0;
+ } else {
+ int feb = 28;
+
+ /* Februarys are a bit tougher issue */
+ if (0 == year % 4) {
+ if (0 == year % 100) {
+ if (0 == year % 400) {
+ feb = 29;
+ } else {
+ feb = 28;
+ }
+ } else {
+ feb = 29;
+ }
+ }
+ if (day < 1 || day > feb)
+ return 0;
+ }
+
+ /* Avoid user from using dates before 16000301, since those will result in
+ incorrect output */
+ if(16000301 > atoi(dateString))
+ return 0;
+
+ return 1;
+}
diff --git a/lib/datecalc.c b/lib/datecalc.c new file mode 100644 index 0000000..46e7179 --- /dev/null +++ b/lib/datecalc.c @@ -0,0 +1,84 @@ +/* datedif - calculates the difference in days between two dates + * Copyright (C) 2000 Micael Widell contact: xeniac@linux.nu + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <time.h> +#include <stdlib.h> +#include <string.h> + + +/* Gaus's formula - days since 1.3.1600 (Gregorian calendar) */ +int days(register int n, register int m, register int y) +{ + register int cy; + if((m -= 2) <= 0){ + m += 12; y--; + } + y -= 1600; cy = y/100; + return 365*y+y/4-cy+cy/4+367*m/12+n-31; +} + + +double dateCalc(char date1[], char date2[]){ + + /* Declare the needed variables */ + char* date[2] = { date1, date2 }; + struct tm *date_tm[2]; + time_t date_time_t[2]; + double dateDifference; + int isToday[2] = { 0, 0 }; + char buffer[5]; + int day[2], month[2], year[2], i; + + + /* If any of the arguments are "today", then include today's date in the + right variables */ + for(i = 0; i < 2; i++){ + if(!strcmp(date[i], "today")){ + time(&date_time_t[i]); + date_tm[i] = localtime(&date_time_t[i]); + day[i] = (*date_tm[i]).tm_mday; + month[i] = (*date_tm[i]).tm_mon + 1; + year[i] = (*date_tm[i]).tm_year + 1900; + isToday[i] = 1; + } + } + + /* Cut out the year, month and day from 8-digit datestrings */ + for (i = 0; i < 2; i++){ + if(!isToday[i]){ + memset(buffer, 0, 5); + strncpy(buffer, &date[i][6], 2); + day[i] = atoi(buffer); + strncpy(buffer, &date[i][4], 2); + month[i] = atoi(buffer); + strncpy(buffer, date[i], 4); + year[i] = atoi(buffer); + } + } + + /* Calculate the difference */ + dateDifference = days(day[1], month[1], year[1]) - days(day[0], month[0], year[0]); + + return dateDifference; +} + + + + + + |