#include "cbook-main.h"
char *rasprintf(const char *f, ...) {
char *r = NULL;
va_list a;
va_start(a, f);
vasprintf(&r, f, a);
va_end(a);
return r;
}
char *htmlEscape(char *s) {
char *r = malloc(32);
/*
l = original string length
i = current byte of original string
j = current byte of new string
b = allocated bytes for new string
*/
long l = strlen(s), i, j, b = 32;
for (i = 0, j = 0; i < l; i++) {
if (j == b - 7) // we could need up to 6 chars
r = realloc(r, b *= 2);
if (s[i] == '&') {
strcpy(&r[j], "&");
j += 5;
} else if (s[i] == '<') {
strcpy(&r[j], "<");
j += 4;
} else if (s[i] == '>') {
strcpy(&r[j], ">");
j += 4;
} else if (s[i] == '"') {
strcpy(&r[j], """);
j += 6;
} else if (s[i] == '\'') {
strcpy(&r[j], "'");
j += 6;
} else r[j++] = s[i];
}
r[j] = 0;
return r;
}
void printHtml(char *s) {
char *escaped = htmlEscape(s);
fputs(escaped, stdout);
free(escaped);
}
void parseConf() {
cfg_opt_t opts[] = {
CFG_SIMPLE_STR("mysqlHostname", &config.mysqlHostname),
CFG_SIMPLE_STR("mysqlUsername", &config.mysqlUsername),
CFG_SIMPLE_STR("mysqlPassword", &config.mysqlPassword),
CFG_SIMPLE_STR("mysqlDatabase", &config.mysqlDatabase),
CFG_SIMPLE_STR("siteName", &config.siteName),
CFG_END()
};
cfg_t *cfg = cfg_init(opts, 0);
cfg_parse(cfg, CBOOK_CONFFILE);
}
void myerror() {
printf("error: mysql: %u: %s\n", mysql_errno(mysql), mysql_error(mysql));
exit(1);
}
char *myescape(char *q) {
unsigned long l = strlen(q);
char *r = malloc(l * 2 + 1);
unsigned long rl = mysql_real_escape_string(mysql, r, q, l);
r = realloc(r, rl + 1);
return r;
}
void fail(const char *s) {
puts(s);
exit(1);
}
void myquery(const char *f, ...) {
va_list a;
va_start(a, f);
char *q = NULL;
vasprintf(&q, f, a);
va_end(a);
if (!q) {
puts("error: query: vasprintf returned null");
exit(1);
}
if (mysql_query(mysql, q)) myerror();
free(q);
}
void createDatabase() {
myquery("create database %s character set utf8 collate utf8_general_ci", config.mysqlDatabase);
if (mysql_select_db(mysql, config.mysqlDatabase)) myerror();
myquery("create table bookings (\n\
id bigint not null auto_increment primary key,\n\
item bigint not null,\n\
year bigint not null,\n\
month bigint not null,\n\
date bigint not null,\n\
session bigint not null\n\
)");
myquery("create table sessions (\n\
id bigint not null auto_increment primary key,\n\
name longtext not null\n\
)");
}
void pageHeader(char *title) {
puts("Content-type: text/html\n");
puts("<!DOCTYPE html>");
puts("<html>");
puts("\t<head>");
puts("\t\t<meta charset=\"utf-8\">");
printf("\t\t<title>cbook: ");
printHtml(config.siteName);
printf(": ");
printHtml(title);
puts("</title>");
puts("\t\t<style>");
puts("\t\t\tbody { font-family: sans-serif; font-size: 0.75em; }");
puts("\t\t\ttable { border-collapse: collapse; margin: 1em 0em; }");
puts("\t\t\ttd, th { border: 1px solid black; padding: 0.5em; }");
puts("\t\t</style>");
puts("\t</head>");
puts("\t<body>");
printf("\t\t<h1>cbook: <a href=\"?\">");
printHtml(config.siteName);
puts("</a></h1>");
}
void pageFooter() {
puts("\t\t<pre>");
printQueryHash();
puts("\t\t</pre>");
puts("\t</body>");
puts("</html>");
}
void pageHome() {
pageHeader("home");
long i;
MYSQL_RES *res;
MYSQL_ROW row;
puts("\t\t<table>");
puts("\t\t\t<tr>");
puts("\t\t\t\t<th>");
for (i = 0; i < 7; i++)
printf("\t\t\t\t<th>%s\n", constDays[i]);
myquery("select * from sessions");
res = mysql_store_result(mysql);
long hasSessions = 0;
while (row = mysql_fetch_row(res)) {
hasSessions = 1;
puts("\t\t\t<tr>");
printf("\t\t\t\t<th>");
printHtml(row[1]);
printf("\n");
for (i = 0; i < 7; i++)
puts("\t\t\t\t<td>");
}
if (!hasSessions) {
puts("\t\t\t<tr>");
puts("\t\t\t\t<td colspan=\"8\">No sessions available. <a href=\"?p=sessions\">Add some.</a>");
}
puts("\t\t</table>");
mysql_free_result(res);
pageFooter();
}
void pageSessions() {
pageHeader("sessions");
MYSQL_RES *res;
MYSQL_ROW row;
puts("\t\t<h2>sessions</h2>");
puts("\t\t<form>");
puts("\t\t\t<input type=\"hidden\" name=\"p\" value=\"session_add\">");
puts("\t\t\t<input name=\"name\">");
puts("\t\t\t<input type=\"submit\" value=\"Add session\">");
puts("\t\t</form>");
puts("\t\t<script>");
puts("\t\t\tdocument.getElementsByName('name')[0].focus();");
puts("\t\t</script>");
puts("\t\t<table>");
puts("\t\t\t<tr>");
puts("\t\t\t\t<th>id");
puts("\t\t\t\t<th>session name");
myquery("select * from sessions");
res = mysql_store_result(mysql);
while (row = mysql_fetch_row(res)) {
puts("\t\t\t<tr>");
printf("\t\t\t\t<td>");
printHtml(row[0]);
printf("\t\t\t\t<td>");
printHtml(row[1]);
printf("\n");
}
puts("\t\t</table>");
pageFooter();
}
void pageSessionAdd() {
char *v = g_hash_table_lookup(queryHash, "name");
v = myescape(v);
myquery("insert into sessions (name) values ('%s')", v);
free(v);
puts("Location: ?p=sessions\n");
}
void parseQueryString() {
queryHash = g_hash_table_new(g_str_hash, g_str_equal);
char *s = getenv("QUERY_STRING");
long state = DQUERY_STATE_KEY, i, j = 0, k = 0, l = strlen(s);
for (i = 0; i <= l; i++) {
switch (state) {
case DQUERY_STATE_KEY:
if (s[i] == '&' || s[i] == 0) {
g_hash_table_insert(queryHash, urlDecode(strndup(&s[j], i - j)), NULL);
j = i + 1;
} else if (s[i] == '=') {
k = i + 1;
state = DQUERY_STATE_VALUE;
}
break;
case DQUERY_STATE_VALUE:
if (s[i] == '&' || s[i] == 0) {
g_hash_table_insert(queryHash, urlDecode(strndup(&s[j], k - j - 1)), urlDecode(strndup(&s[k], i - k)));
j = i + 1;
state = DQUERY_STATE_KEY;
}
break;
default:
fail("error: parseQueryString: invalid state");
}
}
}
void printQueryHash() {
printf("query: '%s'\n", getenv("QUERY_STRING"));
printf("query hash size: %u\n", g_hash_table_size(queryHash));
g_hash_table_foreach(queryHash, printQueryHashHelper, NULL);
}
void printQueryHashHelper(gpointer key, gpointer value, gpointer user) {
printf("'%s' = '%s'\n", (char *) key, (char *) value);
}
char *urlDecode(char *s) {
long i, l = strlen(s), state = 0, offset = 0, byte = 0;
for (i = 0; i <= l; i++) {
switch (state) {
case DURI_STATE_WAIT:
if (s[i + offset] == '%')
state = DURI_STATE_FIRST;
else if (s[i + offset] == '+')
s[i] = ' ';
else
s[i] = s[i + offset];
break;
case DURI_STATE_FIRST:
if (s[i + offset] == 0)
fail("error: urlDecode: string ended waiting for first byte of percent escape");
if (s[i + offset] != '0' && s[i + offset] != '1' && s[i + offset] != '2' && s[i + offset] != '3' &&
s[i + offset] != '4' && s[i + offset] != '5' && s[i + offset] != '6' && s[i + offset] != '7' &&
s[i + offset] != '8' && s[i + offset] != '9' && s[i + offset] != 'A' && s[i + offset] != 'B' &&
s[i + offset] != 'C' && s[i + offset] != 'D' && s[i + offset] != 'E' && s[i + offset] != 'F' &&
s[i + offset] != 'a' && s[i + offset] != 'b' && s[i + offset] != 'c' && s[i + offset] != 'd' &&
s[i + offset] != 'e' && s[i + offset] != 'f') fail("error: urlDecode: expecting first byte to be [0-9A-Fa-f]");
byte = (s[i + offset] >= '0' && s[i + offset] <= '9') ? ((s[i + offset] - '0') << 4) :
(s[i + offset] >= 'A' && s[i + offset] <= 'F') ? ((10 + s[i + offset] - 'A') << 4):
(s[i + offset] >= 'a' && s[i + offset] <= 'f') ? ((10 + s[i + offset] - 'a') << 4) : 0;
state = DURI_STATE_SECOND;
break;
case DURI_STATE_SECOND:
if (s[i + offset] == 0)
fail("error: urlDecode: string ended waiting for second byte of percent escape");
if (s[i + offset] != '0' && s[i + offset] != '1' && s[i + offset] != '2' && s[i + offset] != '3' &&
s[i + offset] != '4' && s[i + offset] != '5' && s[i + offset] != '6' && s[i + offset] != '7' &&
s[i + offset] != '8' && s[i + offset] != '9' && s[i + offset] != 'A' && s[i + offset] != 'B' &&
s[i + offset] != 'C' && s[i + offset] != 'D' && s[i + offset] != 'E' && s[i + offset] != 'F' &&
s[i + offset] != 'a' && s[i + offset] != 'b' && s[i + offset] != 'c' && s[i + offset] != 'd' &&
s[i + offset] != 'e' && s[i + offset] != 'f') fail("error: urlDecode: expecting second byte to be [0-9A-Fa-f]");
byte |= (s[i + offset] >= '0' && s[i + offset] <= '9') ? (s[i + offset] - '0') :
(s[i + offset] >= 'A' && s[i + offset] <= 'F') ? (10 + s[i + offset] - 'A'):
(s[i + offset] >= 'a' && s[i + offset] <= 'f') ? (10 + s[i + offset] - 'a') : 0;
i -= 2;
offset += 2;
s[i] = byte;
state = DURI_STATE_WAIT;
break;
default:
fail("error: urlDecode: invalid state");
}
}
return s;
}
int main(int argc, char **argv) {
parseConf();
parseQueryString();
mysql = mysql_init(NULL);
if (!mysql) myerror();
if (!mysql_real_connect(mysql, config.mysqlHostname, config.mysqlUsername, config.mysqlPassword, NULL, 0, NULL, 0)) myerror();
if (mysql_select_db(mysql, config.mysqlDatabase)) createDatabase();
char *page = g_hash_table_lookup(queryHash, "p");
if (page)
if (!strcmp(page, "sessions")) pageSessions();
else if (!strcmp(page, "session_add")) pageSessionAdd();
else pageHome();
else pageHome();
mysql_close(mysql);
return 0;
}