summaryrefslogtreecommitdiffstats
path: root/src/web
diff options
context:
space:
mode:
Diffstat (limited to 'src/web')
-rw-r--r--src/web/Makefile9
-rw-r--r--src/web/csv.vala75
-rw-r--r--src/web/main.vala35
-rw-r--r--src/web/template.vala104
-rw-r--r--src/web/web.vala858
-rw-r--r--src/web/websession.vala142
6 files changed, 1223 insertions, 0 deletions
diff --git a/src/web/Makefile b/src/web/Makefile
new file mode 100644
index 0000000..cc9d77e
--- /dev/null
+++ b/src/web/Makefile
@@ -0,0 +1,9 @@
+all: web
+
+web: main.vala web.vala websession.vala csv.vala template.vala ../database/db-interface.vala ../pgp/pgp-interface.vala ../price.vapi
+ valac -o $@ --vapidir=../../vapi --pkg gee-1.0 --pkg gio-2.0 --pkg libsoup-2.4 --pkg posix --pkg libarchive --pkg gpgme $^
+
+clean:
+ rm -rf web
+
+.PHONY: all clean
diff --git a/src/web/csv.vala b/src/web/csv.vala
new file mode 100644
index 0000000..4e32a0a
--- /dev/null
+++ b/src/web/csv.vala
@@ -0,0 +1,75 @@
+/* Copyright 2012, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+public class CSVMemberFile {
+ private UserInfo[] members;
+
+ public Gee.List<int> missing_unblocked_members() {
+ var result = new Gee.ArrayList<int>();
+ var dbusers = db.get_member_ids();
+
+ foreach(var u in dbusers) {
+ bool found=false;
+ foreach(var m in members) {
+ if(u == m.id) {
+ found=true;
+ break;
+ }
+ }
+
+ if(!found) {
+ if(!db.user_is_disabled(u))
+ result.add(u);
+ }
+ }
+
+ return result;
+ }
+
+ private string[] csv_split(string line) {
+ return /;(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/.split(line);
+ }
+
+ private string csv_value(string value) {
+ if(value[0] == '"' && value[value.length-1] == '"')
+ return value.substring(1,value.length-2);
+ else
+ return value;
+ }
+
+ public CSVMemberFile(string data) {
+ foreach(var line in data.split("\n")) {
+ var linedata = csv_split(line);
+ if(linedata.length >= 9) {
+ var m = UserInfo();
+ m.id = int.parse(csv_value(linedata[0]));
+ m.email = csv_value(linedata[1]);
+ m.firstname = csv_value(linedata[2]);
+ m.lastname = csv_value(linedata[3]);
+ m.street = csv_value(linedata[4]);
+ m.postcode = int.parse(csv_value(linedata[5]));
+ m.city = csv_value(linedata[6]);
+ m.gender = csv_value(linedata[7]) == "m" ? "masculinum" : csv_value(linedata[7]) == "w" ? "femininum" : "unknown";
+ m.pgp = csv_value(linedata[8]);
+ if(csv_value(linedata[0]) != "EXTERNEMITGLIEDSNUMMER")
+ members += m;
+ }
+ }
+ }
+
+ public UserInfo[] get_members() {
+ return members;
+ }
+}
diff --git a/src/web/main.vala b/src/web/main.vala
new file mode 100644
index 0000000..07dbb03
--- /dev/null
+++ b/src/web/main.vala
@@ -0,0 +1,35 @@
+/* Copyright 2013, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+Database db;
+public CSVMemberFile csvimport;
+public PGP pgp;
+
+public static int main(string[] args) {
+ try {
+ db = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.Database", "/io/mainframe/shopsystem/database");
+ pgp = Bus.get_proxy_sync(BusType.SESSION, "io.mainframe.shopsystem.PGP", "/io/mainframe/shopsystem/pgp");
+ } catch(IOError e) {
+ error("IOError: %s\n", e.message);
+ }
+
+ /* attach WebServer to MainLoop */
+ new WebServer();
+
+ /* start MainLoop */
+ new MainLoop().run();
+
+ return 0;
+}
diff --git a/src/web/template.vala b/src/web/template.vala
new file mode 100644
index 0000000..8fe56d0
--- /dev/null
+++ b/src/web/template.vala
@@ -0,0 +1,104 @@
+/* Copyright 2012, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+public errordomain TemplateError {
+ NOT_FOUND,
+ NOT_LOADABLE,
+ NOT_ALLOWED,
+}
+
+public class WebTemplate {
+ private string template;
+ public uint8[] data { get { return template.data; } }
+
+ public WebTemplate(string file, WebSession login) throws TemplateError {
+ var b = File.new_for_path("templates/base.html");
+ var m = File.new_for_path("templates/menu.html");
+ var f = File.new_for_path("templates/"+file);
+ File fauth;
+
+ if(login.logged_in)
+ fauth = File.new_for_path("templates/menu_logout.html");
+ else
+ fauth = File.new_for_path("templates/menu_login.html");
+
+ uint8[] basis, menu, template, auth;
+
+ if(!b.query_exists())
+ throw new TemplateError.NOT_FOUND("templates/base.html not found!");
+
+ if(!m.query_exists())
+ throw new TemplateError.NOT_FOUND("templates/menu.html not found!");
+
+ if(!fauth.query_exists())
+ throw new TemplateError.NOT_FOUND(fauth.get_path()+" not found!");
+
+ if(!f.query_exists())
+ throw new TemplateError.NOT_FOUND("templates/"+file+" not found!");
+
+ try {
+ if(!b.load_contents(null, out basis, null))
+ throw new TemplateError.NOT_LOADABLE("templates/base.html could not be loaded!");
+ if(!m.load_contents(null, out menu, null))
+ throw new TemplateError.NOT_LOADABLE("templates/menu.html could not be loaded!");
+ if(!fauth.load_contents(null, out auth, null))
+ throw new TemplateError.NOT_LOADABLE(fauth.get_path()+" could not be loaded!");
+ if(!f.load_contents(null, out template, null))
+ throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!");
+ } catch(Error e) {
+ throw new TemplateError.NOT_LOADABLE("could not load templates!");
+ }
+
+ this.template = ((string) basis).replace("{{{NAVBAR}}}", ((string) menu));
+ this.template = this.template.replace("{{{AUTH}}}", ((string) auth));
+ this.template = this.template.replace("{{{CONTENT}}}", ((string) template));
+ this.template = this.template.replace("{{{USERNAME}}}", login.name);
+ this.template = this.template.replace("{{{USERID}}}", "%d".printf(login.user));
+ this.template = this.template.replace("{{{SUPERUSER}}}", login.superuser ? "" : "hidden");
+ }
+
+ public WebTemplate.DATA(string file) throws TemplateError {
+ var f = File.new_for_path("templates/"+file);
+ uint8[] template;
+
+ if(!f.query_exists())
+ throw new TemplateError.NOT_FOUND("templates/"+file+" not found!");
+
+ try {
+ if(!f.load_contents(null, out template, null))
+ throw new TemplateError.NOT_LOADABLE("templates/"+file+" could not be loaded!");
+ } catch(Error e) {
+ throw new TemplateError.NOT_LOADABLE("could not load templates!");
+ }
+
+ this.template = (string) template;
+ }
+
+ public void replace(string key, string value) {
+ template = template.replace("{{{"+key+"}}}", value);
+ }
+
+ public void menu_set_active(string key) {
+ try {
+ var regex_active = new Regex("{{{MENU\\."+key+"}}}");
+ var regex_other = new Regex("{{{MENU\\..*}}}");
+
+ template = regex_active.replace(template, -1, 0, "active");
+ template = regex_other.replace(template, -1, 0, "");
+ } catch(RegexError e) {
+ warning ("%s", e.message);
+ }
+ }
+}
diff --git a/src/web/web.vala b/src/web/web.vala
new file mode 100644
index 0000000..9dfb1ae
--- /dev/null
+++ b/src/web/web.vala
@@ -0,0 +1,858 @@
+/* Copyright 2012, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+public class WebServer {
+ private Soup.Server srv;
+
+ void handler_default(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("index.html", l);
+ t.replace("TITLE", "KtT Shop System");
+ t.menu_set_active("home");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_logout(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ l.logout();
+ var t = new WebTemplate("logout.html", l);
+ t.replace("TITLE", "KtT Shop System");
+ t.menu_set_active("home");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_users(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ string[] pathparts = path.split("/");
+
+ if(pathparts.length <= 2) {
+ handler_user_list(server, msg, path, query, client);
+ } else {
+ int id = int.parse(pathparts[2]);
+
+ if(pathparts.length <= 3) {
+ handler_user_entry(server, msg, path, query, client, id);
+ } else {
+ switch(pathparts[3]) {
+ case "invoice":
+ uint16 selectedyear = (pathparts.length >= 5 && pathparts[4] != "") ? (uint16) int.parse(pathparts[4]) : (uint16) (new DateTime.now_local()).get_year();
+ uint8 selectedmonth = (pathparts.length >= 6 && pathparts[5] != "") ? (uint8) int.parse(pathparts[5]) : (uint8) (new DateTime.now_local()).get_month();
+ uint8 selectedday = (pathparts.length >= 7 && pathparts[6] != "") ? (uint8) int.parse(pathparts[6]) : (uint8) (new DateTime.now_local()).get_day_of_month();
+ handler_user_invoice(server, msg, path, query, client, id, selectedyear, selectedmonth, selectedday);
+ break;
+ case "stats":
+ handler_todo(server, msg, path, query, client);
+ break;
+ default:
+ handler_404(server, msg, path, query, client);
+ break;
+ }
+ }
+ }
+ }
+
+ void handler_user_list(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ if(!session.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+
+ var t = new WebTemplate("users/index.html", session);
+ t.replace("TITLE", "KtT Shop System: User");
+ t.menu_set_active("users");
+ var data = "";
+ foreach(var m in db.get_member_ids()) {
+ try {
+ var name = db.get_username(m);
+ data += @"<tr><td>$m</td><td><a href=\"/users/$m\">$name</a></td></tr>";
+ } catch(DatabaseError e) {
+ /* TODO: write error to log */
+ }
+ }
+ t.replace("DATA", data);
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_user_pgp_import(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ if(!session.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+
+ var t = new WebTemplate("users/import-pgp.html", session);
+ t.replace("TITLE", "KtT Shop System: PGP Key Import");
+ t.menu_set_active("users");
+
+ Soup.Buffer filedata;
+ var postdata = Soup.Form.decode_multipart(msg, "file", null, null, out filedata);
+
+ if(postdata == null || !postdata.contains("step")) {
+ t.replace("DATA", "");
+ t.replace("STEP1", "block");
+ t.replace("STEP2", "none");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ return;
+ } else {
+ var keylist = pgp.import_archive(filedata.data);
+ string keylisttemplate;
+
+ if(keylist.length > 0) {
+ keylisttemplate = "<ul>\n";
+ foreach(string s in keylist) {
+ keylisttemplate += "<li>"+s+"</li>\n";
+ }
+ keylisttemplate += "</ul>\n";
+ } else {
+ keylisttemplate = "<p><b>No new keys!</b></p>";
+ }
+
+ t.replace("DATA", keylisttemplate);
+ t.replace("STEP1", "none");
+ t.replace("STEP2", "block");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ return;
+ }
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_user_import(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ if(!session.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+ var t = new WebTemplate("users/import.html", session);
+ t.replace("TITLE", "KtT Shop System: User Import");
+ t.menu_set_active("users");
+
+ Soup.Buffer filedata;
+ var postdata = Soup.Form.decode_multipart(msg, "file", null, null, out filedata);
+ if(postdata == null || !postdata.contains("step")) {
+ t.replace("DATA1", "");
+ t.replace("DATA2", "");
+ t.replace("STEP1", "block");
+ t.replace("STEP2", "none");
+ t.replace("STEP23", "none");
+ t.replace("STEP3", "none");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ return;
+ } else {
+ if(filedata != null) {
+ string text = (string) filedata.data;
+ text = text.substring(0,(long) filedata.length-1);
+ csvimport = new CSVMemberFile(text);
+ }
+
+ if(csvimport == null) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+
+ /* new & changed users */
+ string data1 = "";
+ foreach(var member in csvimport.get_members()) {
+ if(db.user_exists(member.id) && db.user_equals(member)) {
+ var dbmember = db.get_user_info(member.id);
+ data1 += @"<tr class=\"error\"><td><i class=\"icon-minus-sign\"></i><td>$(dbmember.id)</td><td>$(dbmember.firstname)</td><td>$(dbmember.lastname)</td><td>$(dbmember.email)</td><td>$(dbmember.gender)</td><td>$(dbmember.street)</td><td>$(dbmember.postcode)</td><td>$(dbmember.city)</td><td>$(dbmember.pgp)</td></tr>";
+ }
+ if(!db.user_exists(member.id) || !db.user_equals(member)) {
+ data1 += @"<tr class=\"success\"><td><i class=\"icon-plus-sign\"></td><td>$(member.id)</td><td>$(member.firstname)</td><td>$(member.lastname)</td><td>$(member.email)</td><td>$(member.gender)</td><td>$(member.street)</td><td>$(member.postcode)</td><td>$(member.city)</td><td>$(member.pgp)</td></tr>";
+ }
+ }
+ t.replace("DATA1", data1);
+
+ /* removed users */
+ Gee.List<int> blockedusers = csvimport.missing_unblocked_members();
+ if(blockedusers.size > 0) {
+ string data2 = "<b>Disabling the following users</b>, because they are no longer found in the member CSV: <ul>";
+
+ foreach(var member in blockedusers) {
+ try {
+ string name = db.get_username(member);
+ data2 += @"<li>$name ($member)</li>";
+ } catch(Error e) {}
+ }
+
+ data2 += "</ul>";
+ t.replace("DATA2", data2);
+ } else {
+ t.replace("DATA2", "");
+ }
+
+ /* show correct blocks */
+ t.replace("STEP1", "none");
+ t.replace("STEP23", "block");
+ if(postdata["step"] == "1") {
+ t.replace("STEP2", "block");
+ t.replace("STEP3", "none");
+ } else {
+ t.replace("STEP2", "none");
+ t.replace("STEP3", "block");
+ }
+
+ if(postdata["step"] == "2") {
+ /* disable users */
+ foreach(var member in csvimport.missing_unblocked_members()) {
+ db.user_disable(member, true);
+ }
+
+ /* update users */
+ foreach(var member in csvimport.get_members()) {
+ db.user_replace(member);
+ }
+
+ csvimport = null;
+ }
+ }
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_user_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ if(id != session.user && !session.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+ var t = new WebTemplate("users/entry.html", session);
+ t.replace("TITLE", "KtT Shop System: User Info %llu".printf(id));
+ t.menu_set_active("users");
+
+ var userinfo = db.get_user_info(id);
+
+ t.replace("UID", "%d".printf(userinfo.id));
+ t.replace("FIRSTNAME", userinfo.firstname);
+ t.replace("LASTNAME", userinfo.lastname);
+ t.replace("EMAIL", userinfo.email);
+ t.replace("GENDER", userinfo.gender);
+ t.replace("STREET", userinfo.street);
+ t.replace("POSTALCODE", "%d".printf(userinfo.postcode));
+ t.replace("CITY", userinfo.city);
+ t.replace("PGPKEYID", userinfo.pgp);
+
+ var userauth = db.get_user_auth(id);
+ t.replace("DISABLED", userauth.disabled ? "true" : "false");
+ t.replace("ISSUPERUSER", userauth.superuser ? "true" : "false");
+
+ var postdata = Soup.Form.decode_multipart(msg, null, null, null, null);
+ if(postdata != null && postdata.contains("password1") && postdata.contains("password2")) {
+ if(postdata["password1"] != postdata["password2"]) {
+ t.replace("MESSAGE", "<div class=\"alert alert-error\">Error! Passwords do not match!</div>");
+ } else if(postdata["password1"] == "") {
+ t.replace("MESSAGE", "<div class=\"alert alert-error\">Error! Empty Password not allowed!</div>");
+ } else {
+ db.set_user_password(id, postdata["password1"]);
+ t.replace("MESSAGE", "<div class=\"alert alert-success\">Password Changed!</div>");
+ }
+ } else {
+ t.replace("MESSAGE", "");
+ }
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_user_invoice(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, int id, uint16 selectedyear, uint8 selectedmonth, uint8 selectedday) {
+ DateTime start, stop;
+
+ DateYear y = (DateYear) selectedyear;
+ if(!y.valid() || y > 8000) {
+ selectedyear = (uint16) new DateTime.now_local().get_year();
+ y = (DateYear) selectedyear;
+ }
+
+ DateMonth m = (DateMonth) selectedmonth;
+ if(selectedmonth != 0 && !m.valid()) {
+ selectedmonth = (uint8) new DateTime.now_local().get_month();
+ m = (DateMonth) selectedmonth;
+ }
+
+ DateDay d = (DateDay) selectedday;
+ if(selectedday != 0 && !d.valid()) {
+ selectedday = (uint8) new DateTime.now_local().get_day_of_month();
+ d = (DateDay) selectedday;
+ }
+
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ if(id != l.user && !l.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+ var t = new WebTemplate("users/invoice.html", l);
+ t.replace("TITLE", "KtT Shop System: User Invoice %llu".printf(id));
+ t.menu_set_active("users");
+
+ /* years, in which something has been purchased by the user */
+ var first = new DateTime.from_unix_local(db.get_first_purchase(id));
+ var last = new DateTime.from_unix_local(db.get_last_purchase(id));
+ string years = "";
+ for(int i=first.get_year(); i <= last.get_year(); i++) {
+ years += @"<li><a href=\"/users/$id/invoice/$i/$selectedmonth/$selectedday\">$i</a></li>";
+ }
+ t.replace("YEARS", years);
+ t.replace("SELECTEDYEAR", @"$selectedyear");
+
+ /* months, in which something has been purchased by the user */
+ string[] monthnames = { "All Months", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
+ string months = @"<li><a href=\"/users/$id/invoice/$selectedyear/0/0\">All Months</a></li>";
+ for(int i=1; i<monthnames.length; i++) {
+ if(first.get_year() == selectedyear && i > 0 && i < first.get_month())
+ months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>";
+ else if(selectedyear < first.get_year())
+ months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>";
+ else if(last.get_year() == selectedyear && i > last.get_month())
+ months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>";
+ else if(selectedyear > last.get_year())
+ months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\" class=\"disabled\"\">$(monthnames[i])</a></li>";
+ else
+ months += @"<li><a href=\"/users/$id/invoice/$selectedyear/$i/$selectedday\">$(monthnames[i])</a></li>";
+ }
+ t.replace("MONTHS", months);
+ t.replace("SELECTEDMONTH", @"$(monthnames[selectedmonth])");
+
+ int dim = m.valid() ? Date.get_days_in_month(m, y) : 0;
+ string days = @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/0\">All Days</a></li>";
+ for(int i=1; i<=dim; i++) {
+ if(first.get_year() == selectedyear && first.get_month() == selectedmonth && i < first.get_day_of_month())
+ days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>";
+ else if(selectedyear < first.get_year() || (selectedyear == first.get_year() && selectedmonth < first.get_month()))
+ days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>";
+ else if(last.get_year() == selectedyear && last.get_month() == selectedmonth && i > last.get_day_of_month())
+ days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>";
+ else if(selectedyear > last.get_year() || (selectedyear == last.get_year() && selectedmonth > last.get_month()))
+ days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\" class=\"disabled\">$i</a></li>";
+ else
+ days += @"<li><a href=\"/users/$id/invoice/$selectedyear/$selectedmonth/$i\">$i</a></li>";
+ }
+ t.replace("DAYS", days);
+ if(selectedday > 0)
+ t.replace("SELECTEDDAY", @"$selectedday");
+ else
+ t.replace("SELECTEDDAY", "All Days");
+
+ if(selectedday != 0) {
+ start = new DateTime.local(selectedyear, selectedmonth, selectedday, 8, 0, 0);
+ stop = start.add_days(1);
+ } else if(selectedmonth != 0) {
+ start = new DateTime.local(selectedyear, selectedmonth, 1, 0, 0, 0);
+ stop = start.add_months(1);
+ } else {
+ start = new DateTime.local(selectedyear, 1, 1, 0, 0, 0);
+ stop = start.add_years(1);
+ }
+
+ string table = "";
+ Price sum = 0;
+ foreach(var e in db.get_invoice(id, start.to_unix(), stop.to_unix())) {
+ var timestamp = new DateTime.from_unix_utc(e.timestamp);
+ var date = timestamp.format("%d.%m.%Y");
+ var time = timestamp.format("%H:%M:%S");
+ var product = e.product.name;
+ var price = e.price;
+ table += @"<tr><td>$date</td><td>$time</td><td>$product</td><td>$price€</td></tr>";
+ sum += e.price;
+ }
+
+ t.replace("DATA", table);
+ t.replace("SUM", @"$sum €");
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_products(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ string[] pathparts = path.split("/");
+
+ if(pathparts.length <= 2 || pathparts[2] == "") {
+ handler_product_list(server, msg, path, query, client);
+ } else {
+ uint64 id = uint64.parse(pathparts[2]);
+
+ if(pathparts.length <= 3) {
+ handler_product_entry(server, msg, path, query, client, id);
+ } else {
+ switch(pathparts[3]) {
+ case "restock":
+ handler_product_restock(server, msg, path, query, client, id);
+ break;
+ case "newprice":
+ handler_product_newprice(server, msg, path, query, client, id);
+ break;
+ default:
+ handler_product_entry(server, msg, path, query, client, id);
+ break;
+ }
+ }
+ }
+ }
+
+ void handler_product_list(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("products/index.html", l);
+ t.replace("TITLE", "KtT Shop System: Product List");
+ t.menu_set_active("products");
+
+ string table = "";
+ foreach(var e in db.get_stock()) {
+ table += "<tr><td><a href=\"/products/%s\">%s</a></td><td><a href=\"/products/%s\">%s</a></td><td>%d</td><td>%s€</td><td>%s€</td></tr>".printf(
+ e.id, e.id, e.id, e.name, e.amount, e.memberprice, e.guestprice
+ );
+ }
+
+ t.replace("DATA", table);
+
+ if(l.superuser)
+ t.replace("NEWPRODUCT", "block");
+ else
+ t.replace("NEWPRODUCT", "none");
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_product_entry(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client, uint64 id) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("products/entry.html", l);
+ t.replace("TITLE", "KtT Shop System: Product %llu".printf(id));
+ t.menu_set_active("products");
+
+ /* ean */
+ t.replace("EAN", "%llu".printf(id));
+
+ /* name */
+ string name = db.get_product_name(id);
+ t.replace("NAME", name);
+
+ /* amount */
+ t.replace("AMOUNT", "%d".printf(db.get_product_amount(id)));
+
+ if(l.superuser)
+ t.replace("ISADMIN", "block");
+ else
+ t.replace("ISADMIN", "none");
+
+ /* prices */
+ string prices = "";
+ foreach(var e in db.get_prices(id)) {
+ var time = new DateTime.from_unix_local(e.valid_from);
+ prices += @"<tr><td>%s</td><td>$(e.memberprice)€</td><td>$(e.guestprice)€</td></tr>".printf(
+ time.format("%Y-%m-%d %H:%M")
+ );
+ }
+ t.replace("PRICES", prices);
+
+ /* restocks */
+ string restocks = "";
+ foreach(var e in db.get_restocks(id)) {
+ var time = new DateTime.from_unix_local(e.timestamp);
+ var supplier = db.get_supplier(e.supplier).name;
+ if(supplier == "Unknown")
+ supplier = "";
+ string bbd;
+ if(e.best_before_date > 0)
+ bbd = (new DateTime.from_unix_local(e.best_before_date)).format("%Y-%m-%d");
+ else
+ bbd = "";
+ restocks += "<tr><td>%s</td><td>%d</td><td>%s€</td><td>%s</td><td>%s</td></tr>".printf(
+ time.format("%Y-%m-%d %H:%M"), e.amount, e.price, supplier, bbd
+ );
+ }
+ t.replace("RESTOCKS", restocks);
+
+ /* suppliers */
+ string suppliers = "<option value=\"0\">Unknown</option>";
+ foreach(var e in db.get_supplier_list()) {
+ suppliers += "<option value=\"%lld\">%s</option>".printf(e.id, e.name);
+ }
+ t.replace("SUPPLIERS", suppliers);
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_products_new(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ var template = new WebTemplate("products/new.html", session);
+ template.replace("TITLE", "KtT Shop System: New Product");
+ template.menu_set_active("products");
+
+ if(!session.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+
+ if(query != null && query.contains("name") && query.contains("id") && query.contains("memberprice") && query.contains("guestprice")) {
+ var name = query["name"];
+ var ean = uint64.parse(query["id"]);
+ Price memberprice = Price.parse(query["memberprice"]);
+ Price guestprice = Price.parse(query["guestprice"]);
+
+ if(ean > 0 && memberprice > 0 && guestprice > 0 && db.new_product(ean, name, memberprice, guestprice)) {
+ template.replace("NAME", name);
+ template.replace("EAN", @"$ean");
+ template.replace("MEMBERPRICE", @"$memberprice€");
+ template.replace("GUESTPRICE", @"$guestprice€");
+ template.replace("NEW.OK", "block");
+ template.replace("NEW.FAIL", "none");
+ } else {
+ template.replace("NAME", "...");
+ template.replace("NEW.OK", "none");
+ template.replace("NEW.FAIL", "block");
+ }
+ } else {
+ template.replace("NAME", "...");
+ template.replace("NEW.OK", "none");
+ template.replace("NEW.FAIL", "block");
+ }
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, template.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_product_restock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client, uint64 id) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+
+ if(!session.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+
+ var template = new WebTemplate("products/restock.html", session);
+ template.replace("TITLE", "KtT Shop System: Restock Product %llu".printf(id));
+ template.replace("NAME", db.get_product_name(id));
+ template.menu_set_active("products");
+
+ if(query != null && query.contains("amount") && query.contains("price")) {
+ int amount = int.parse(query["amount"]);
+ int supplier = int.parse(query["supplier"]);
+ string best_before_date = query["best_before_date"];
+ Price price = Price.parse(query["price"]);
+ DateTime bbd;
+
+ var dateparts = best_before_date.split("-");
+ if(dateparts.length == 3) {
+ bbd = new DateTime.local(int.parse(dateparts[0]), int.parse(dateparts[1]), int.parse(dateparts[2]), 0, 0, 0);
+ } else {
+ bbd = new DateTime.from_unix_local(0);
+ }
+
+ if(amount >= 1 && price >= 1) {
+ if(db.restock(session.user, id, amount, price, supplier, bbd.to_unix())) {
+ template.replace("AMOUNT", @"$amount");
+ template.replace("PRICE", @"$price");
+ template.replace("BESTBEFORE", bbd.format("%Y-%m-%d"));
+ template.replace("SUPPLIER", db.get_supplier(supplier).name);
+ template.replace("RESTOCK.OK", "block");
+ template.replace("RESTOCK.FAIL", "none");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, template.data);
+ return;
+ }
+ }
+ }
+
+ template.replace("RESTOCK.OK", "none");
+ template.replace("RESTOCK.FAIL", "block");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, template.data);
+ return;
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_product_newprice(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client, uint64 id) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ int64 timestamp = (new DateTime.now_utc()).to_unix();
+
+ if(!session.superuser) {
+ handler_403(server, msg, path, query, client);
+ return;
+ }
+
+ var template = new WebTemplate("products/newprice.html", session);
+ template.replace("TITLE", "KtT Shop System: New Price for Product %llu".printf(id));
+ template.replace("NAME", db.get_product_name(id));
+ template.menu_set_active("products");
+
+ if(query != null && query.contains("guest") && query.contains("member")) {
+ Price member = Price.parse(query["member"]);
+ Price guest = Price.parse(query["guest"]);
+
+ if(guest >= 1 && member >= 1) {
+ if(db.new_price(id, timestamp, member, guest)) {
+ template.replace("GUEST", @"$guest");
+ template.replace("MEMBER", @"$member");
+ template.replace("NEWPRICE.OK", "block");
+ template.replace("NEWPRICE.FAIL", "none");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, template.data);
+ return;
+ }
+ }
+ }
+
+ template.replace("NEWPRICE.OK", "none");
+ template.replace("NEWPRICE.FAIL", "block");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, template.data);
+ return;
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+#if 0
+ void handler_stats(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("stats/index.html", l);
+ t.replace("TITLE", "KtT Shop System: Statistics");
+ t.menu_set_active("stats");
+
+ var stats = db.get_stats_info();
+
+ t.replace("NUMBER_OF_ARTICLES", @"$(stats.count_articles)");
+ t.replace("NUMBER_OF_USERS", @"$(stats.count_users)");
+ t.replace("STOCK_VALUE", @"$(stats.stock_value)€");
+ t.replace("TOTAL_SALES", @"$(stats.sales_total)€");
+ t.replace("TOTAL_PROFIT", @"$(stats.profit_total)€");
+ t.replace("SALES_TODAY", @"$(stats.sales_today)€");
+ t.replace("PROFIT_TODAY", @"$(stats.profit_today)€");
+ t.replace("SALES_THIS_MONTH", @"$(stats.sales_this_month)€");
+ t.replace("PROFIT_THIS_MONTH", @"$(stats.profit_this_month)€");
+ t.replace("SALES_PER_DAY", @"$(stats.sales_per_day)€");
+ t.replace("PROFIT_PER_DAY", @"$(stats.profit_per_day)€");
+ t.replace("SALES_PER_MONTH", @"$(stats.sales_per_month)€");
+ t.replace("PROFIT_PER_MONTH", @"$(stats.profit_per_month)€");
+
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_stats_stock(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("stats/stock.html", l);
+ string data = db.get_stats_stock().json;
+ t.replace("DATA", data);
+ t.replace("TITLE", "KtT Shop System: Statistics: Stock");
+ t.menu_set_active("stats");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_stats_profit_per_day(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("stats/profit_per_day.html", l);
+ string data = db.get_stats_profit_per_day().json;
+ t.replace("DATA", data);
+ t.replace("TITLE", "KtT Shop System: Statistics: Profit");
+ t.menu_set_active("stats");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_stats_profit_per_weekday(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("stats/profit_per_weekday.html", l);
+ string data = db.get_stats_profit_per_weekday().json;
+ t.replace("DATA", data);
+ t.replace("TITLE", "KtT Shop System: Statistics: Profit/Weekday");
+ t.menu_set_active("stats");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_stats_profit_per_product(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var l = new WebSession(server, msg, path, query, client);
+ var t = new WebTemplate("stats/profit_per_product.html", l);
+ string data = db.get_stats_profit_per_products().json;
+ t.replace("DATA", data);
+ t.replace("TITLE", "KtT Shop System: Statistics: Profit/Product");
+ t.menu_set_active("stats");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+#endif
+
+ void handler_js(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var t = new WebTemplate.DATA(path);
+ msg.set_response("text/javascript", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_css(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var t = new WebTemplate.DATA(path);
+ msg.set_response("text/css", Soup.MemoryUse.COPY, t.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ void handler_img(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var f = File.new_for_path("templates/"+path);
+ uint8[] data = null;
+
+ if(f.query_exists() && f.load_contents(null, out data, null)) {
+ msg.set_response("image/png", Soup.MemoryUse.COPY, data);
+ return;
+ }
+ } catch(Error e) {
+ error("there has been some error: %s!\n", e.message);
+ }
+
+ handler_404(server, msg, path, query, client);
+ return;
+ }
+
+ void handler_404(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ string result = "Page not Found\n";
+ msg.set_status(404);
+ msg.set_response("text/plain", Soup.MemoryUse.COPY, result.data);
+ }
+
+ void handler_403(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ var template = new WebTemplate("errors/403.html", session);
+ template.replace("TITLE", "Access Denied");
+ template.menu_set_active("");
+ msg.set_status(403);
+ msg.set_response("text/html", Soup.MemoryUse.COPY, template.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ } }
+
+ void handler_todo(Soup.Server server, Soup.Message msg, string path, GLib.HashTable? query, Soup.ClientContext client) {
+ try {
+ var session = new WebSession(server, msg, path, query, client);
+ var template = new WebTemplate("errors/todo.html", session);
+ template.replace("TITLE", "KtT Shop System: ToDo");
+ template.menu_set_active("");
+ msg.set_response("text/html", Soup.MemoryUse.COPY, template.data);
+ } catch(TemplateError e) {
+ stderr.printf(e.message+"\n");
+ handler_404(server, msg, path, query, client);
+ }
+ }
+
+ public WebServer(int port = 8080) {
+ srv = new Soup.Server(Soup.SERVER_PORT, port);
+
+ /* index */
+ srv.add_handler("/", handler_default);
+
+ /* logout */
+ srv.add_handler("/logout", handler_logout);
+
+ /* data (js, css, img) */
+ srv.add_handler("/js", handler_js);
+ srv.add_handler("/css", handler_css);
+ srv.add_handler("/img", handler_img);
+
+ /* products */
+ srv.add_handler("/products", handler_products);
+ srv.add_handler("/products/new", handler_products_new);
+
+#if 0
+ /* stats */
+ srv.add_handler("/stats", handler_stats);
+ srv.add_handler("/stats/stock", handler_stats_stock);
+ srv.add_handler("/stats/profit_per_day", handler_stats_profit_per_day);
+ srv.add_handler("/stats/profit_per_weekday", handler_stats_profit_per_weekday);
+ srv.add_handler("/stats/profit_per_product", handler_stats_profit_per_product);
+#endif
+
+ /* users */
+ srv.add_handler("/users", handler_users);
+ srv.add_handler("/users/import", handler_user_import);
+ srv.add_handler("/users/import-pgp", handler_user_pgp_import);
+
+ srv.run_async();
+ }
+}
diff --git a/src/web/websession.vala b/src/web/websession.vala
new file mode 100644
index 0000000..5c562c6
--- /dev/null
+++ b/src/web/websession.vala
@@ -0,0 +1,142 @@
+/* Copyright 2012, Sebastian Reichel <sre@ring0.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+public errordomain WebSessionError {
+ SESSION_NOT_FOUND,
+ USER_NOT_FOUND
+}
+
+public class WebSession {
+ public int user {
+ get;
+ private set;
+ default = 0;
+ }
+ public string name {
+ get;
+ private set;
+ default = "Guest";
+ }
+ public bool failed {
+ get;
+ private set;
+ default = false;
+ }
+ public bool logged_in {
+ get;
+ private set;
+ default = false;
+ }
+ public bool superuser {
+ get;
+ private set;
+ default = false;
+ }
+ public bool disabled {
+ get;
+ private set;
+ default = false;
+ }
+
+ private string generate_session_id(int user) {
+ const string charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
+ string result = "";
+
+ Random.set_seed((uint32) time_t() + (uint32) Posix.getpid() + (uint32) user);
+
+ for(int i=0; i<19; i++) {
+ int character_position = Random.int_range(0,charset.length);
+ string character = charset[character_position].to_string();
+ result += character;
+ }
+
+ /* TODO: make sure, that session id is unique */
+
+ return result;
+ }
+
+ private void setup_auth(int user) {
+ var auth = db.get_user_auth(user);
+ this.disabled = auth.disabled;
+ this.superuser = auth.superuser;
+ this.logged_in = true;
+ }
+
+ public void logout() {
+ if(logged_in) {
+ db.set_sessionid(user, "");
+ superuser = false;
+ logged_in = false;
+ }
+ }
+
+ public WebSession(Soup.Server server, Soup.Message msg, string path, GLib.HashTable<string,string>? query, Soup.ClientContext client) {
+ var cookies = Soup.cookies_from_request(msg);
+
+ /* Check for existing session */
+ foreach(var cookie in cookies) {
+ if(cookie.name == "session") {
+ var sessionid = cookie.value;
+
+ try {
+ user = db.get_user_by_sessionid(sessionid);
+ name = db.get_username(user);
+ setup_auth(user);
+ return;
+ } catch(WebSessionError e) {
+ /* invalid session, ignore */
+ }
+ }
+ }
+
+ /* check for login query */
+ if(query == null || !query.contains("user") || !query.contains("password"))
+ return;
+
+ /* get credentials */
+ var userid = int.parse(query["user"]);
+ var password = query["password"];
+
+ /* check credentials */
+ if(db.check_user_password(userid, password)) {
+ /* generate session */
+ var sessionid = generate_session_id(userid);
+
+ /* set session in database */
+ db.set_sessionid(userid, sessionid);
+
+ /* set session in reply cookie */
+ cookies = new SList<Soup.Cookie>();
+ var sessioncookie = new Soup.Cookie("session", sessionid, "", "/", -1);
+ sessioncookie.domain = null;
+ cookies.append(sessioncookie);
+ Soup.cookies_to_response(cookies, msg);
+
+ /* login successful */
+ user = userid;
+ try {
+ name = db.get_username(user);
+ } catch(WebSessionError e) {
+ name = "Unknown User";
+ }
+
+ setup_auth(user);
+ } else {
+ /* login failed */
+ failed=true;
+ }
+ }
+}
+