Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
dblg/dblg.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2086 lines (1858 sloc)
48.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* $Id$ */ | |
/* | |
* Copyright (c) 2016--2017 Kristaps Dzonsons <kristaps@bsd.lv> | |
* | |
* Permission to use, copy, modify, and 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. | |
*/ | |
#include <assert.h> | |
#include <errno.h> | |
#include <inttypes.h> | |
#include <math.h> | |
#include <stdarg.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
#include <unistd.h> | |
#include <kcgi.h> | |
#include <kcgihtml.h> | |
#include <kcgijson.h> | |
#include <kcgixml.h> | |
#include <ksql.h> | |
/* | |
* Index values for atom feed XML elements. | |
*/ | |
enum xml { | |
XML_AUTHOR, | |
XML_EMAIL, | |
XML_ENTRY, | |
XML_FEED, | |
XML_ID, | |
XML_LINK, | |
XML_NAME, | |
XML_PUBLISHED, | |
XML_TITLE, | |
XML_UPDATED, | |
XML_URI, | |
XML__MAX | |
}; | |
/* | |
* The elements in our HTML template file. | |
*/ | |
enum templ { | |
TEMPL_ASIDE, | |
TEMPL_AUTHOR_LINK, | |
TEMPL_AUTHOR_NAME, | |
TEMPL_CANON, | |
TEMPL_CANON_QUERY, | |
TEMPL_CLASSES, | |
TEMPL_CONTENT, | |
TEMPL_COORD_LAT_DECIMAL, | |
TEMPL_COORD_LNG_DECIMAL, | |
TEMPL_CTIME, | |
TEMPL_CTIME_ISO_8601, | |
TEMPL_IMAGE, | |
TEMPL_MTIME, | |
TEMPL_MTIME_ISO_8601, | |
TEMPL_TITLE, | |
TEMPL__MAX | |
}; | |
/* | |
* Page (URL) designations. | |
*/ | |
enum page { | |
PAGE_ADD_USER, | |
PAGE_ATOM, | |
PAGE_INDEX, | |
PAGE_LOGIN, | |
PAGE_LOGOUT, | |
PAGE_MOD_CLOUD, | |
PAGE_MOD_EMAIL, | |
PAGE_MOD_ENABLE, | |
PAGE_MOD_LANG, | |
PAGE_MOD_LINK, | |
PAGE_MOD_META_TEMPLATE, | |
PAGE_MOD_META_TITLE, | |
PAGE_MOD_NAME, | |
PAGE_MOD_PASS, | |
PAGE_PUBLIC, | |
PAGE_REMOVE, | |
PAGE_SUBMIT, | |
PAGE__MAX | |
}; | |
/* | |
* Form input names. | |
*/ | |
enum key { | |
KEY_ADMIN, | |
KEY_ASIDE, | |
KEY_CLOUDKEY, | |
KEY_CLOUDNAME, | |
KEY_CLOUDPATH, | |
KEY_CLOUDSECRET, | |
KEY_EMAIL, | |
KEY_ENABLE, | |
KEY_ENTRYID, | |
KEY_IMAGE, | |
KEY_LANG, | |
KEY_LATITUDE, | |
KEY_LONGITUDE, | |
KEY_LIMIT, | |
KEY_LINK, | |
KEY_MARKDOWN, | |
KEY_META_TEMPLATE, | |
KEY_META_TITLE, | |
KEY_NAME, | |
KEY_ORDER, | |
KEY_PASS, | |
KEY_SAVE, | |
KEY_SESSCOOKIE, | |
KEY_SESSID, | |
KEY_TITLE, | |
KEY_USERID, | |
KEY__MAX | |
}; | |
/* | |
* SQL statements. | |
*/ | |
enum stmt { | |
STMT_ENTRY_DELETE, | |
STMT_ENTRY_GET, | |
STMT_ENTRY_GET_PUBLIC, | |
STMT_ENTRY_LIST_PENDING, | |
STMT_ENTRY_LIST_PUBLIC, | |
STMT_ENTRY_LIST_PUBLIC_LANG, | |
STMT_ENTRY_LIST_PUBLIC_LANG_LIMIT, | |
STMT_ENTRY_LIST_PUBLIC_LIMIT, | |
STMT_ENTRY_LIST_PUBLIC_MTIME, | |
STMT_ENTRY_LIST_PUBLIC_MTIME_LANG, | |
STMT_ENTRY_LIST_PUBLIC_MTIME_LANG_LIMIT, | |
STMT_ENTRY_LIST_PUBLIC_MTIME_LIMIT, | |
STMT_ENTRY_MODIFY, | |
STMT_ENTRY_NEW, | |
STMT_META_GET, | |
STMT_META_MOD_TEMPLATE, | |
STMT_META_MOD_TITLE, | |
STMT_META_NEW, | |
STMT_META_UPDATE, | |
STMT_SESS_DEL, | |
STMT_SESS_GET, | |
STMT_SESS_NEW, | |
STMT_USER_ADD, | |
STMT_USER_GET, | |
STMT_USER_LIST, | |
STMT_USER_LOOKUP, | |
STMT_USER_MOD_CLOUD, | |
STMT_USER_MOD_DISABLE, | |
STMT_USER_MOD_EMAIL, | |
STMT_USER_MOD_ENABLE, | |
STMT_USER_MOD_HASH, | |
STMT_USER_MOD_LANG, | |
STMT_USER_MOD_LINK, | |
STMT_USER_MOD_NAME, | |
STMT__MAX | |
}; | |
struct meta { | |
int64_t mtime; /* last modified time */ | |
char *title; /* Atom title (or NULL) */ | |
char *template; /* HTML template (or NULL) */ | |
}; | |
struct cloud { | |
char *key; /* API key */ | |
char *secret; /* API secret */ | |
char *path; /* media directory */ | |
char *name; /* name of storage site */ | |
int set; /* whether set or not */ | |
}; | |
struct user { | |
struct cloud cloud; /* cloud settings */ | |
char *email; /* e-mail (and identifier) */ | |
char *link; /* link to user's website (or NULL) */ | |
char *name; /* user's public name */ | |
char *lang; /* default IETF Language (or NULL) */ | |
int64_t flags; | |
#define USER_ADMIN 0x01 /* administrator */ | |
#define USER_DISABLED 0x02 /* disabled */ | |
int64_t id; | |
}; | |
struct entry { | |
char *image; /* image URL (or NULL) */ | |
char *aside; /* non-binary aside (or NULL) */ | |
char *content; /* non-binary content */ | |
char *title; /* non-binary title */ | |
char *lang; /* IETF language (or NULL) */ | |
time_t ctime; /* created */ | |
time_t mtime; /* last modified */ | |
int coords; /* whether coords set */ | |
double lat; /* decimal degree latitude */ | |
double lng; /* decimal degree longitude */ | |
int64_t flags; | |
#define ENTRY_PENDING 0x01 /* pending (private) */ | |
int64_t id; | |
}; | |
struct htmldata { | |
const struct user *u; /* owner (or NULL) */ | |
struct entry entry; /* blog entry */ | |
struct user user; /* blog user */ | |
struct khtmlreq *req; /* HTML handle */ | |
}; | |
/* | |
* Element names of atom feed XML. | |
*/ | |
static const char *xmls[XML__MAX] = { | |
"author", /* XML_AUTHOR */ | |
"email", /* XML_EMAIL */ | |
"entry", /* XML_ENTRY */ | |
"feed", /* XML_FEED */ | |
"id", /* XML_ID */ | |
"link", /* XML_LINK */ | |
"name", /* XML_NAME */ | |
"published", /* XML_PUBLISHED */ | |
"title", /* XML_TITLE */ | |
"updated", /* XML_UPDATED */ | |
"uri", /* XML_URI */ | |
}; | |
/* | |
* HTML template key names. | |
*/ | |
static const char *const templs[TEMPL__MAX] = { | |
"dblg-aside", /* TEMPL_ASIDE */ | |
"dblg-author-link", /* TEMPL_AUTHOR_LINK */ | |
"dblg-author-name", /* TEMPL_AUTHOR_NAME */ | |
"dblg-canon", /* TEMPL_CANON */ | |
"dblg-canon-query", /* TEMPL_CANON_QUERY */ | |
"dblg-classes", /* TEMPL_CLASSES */ | |
"dblg-content", /* TEMPL_CONTENT */ | |
"dblg-coord-lat-decimal", /* TEMPL_COORD_LAT_DECIMAL */ | |
"dblg-coord-lng-decimal", /* TEMPL_COORD_LNG_DECIMAL */ | |
"dblg-ctime", /* TEMPL_CTIME */ | |
"dblg-ctime-iso8601", /* TEMPL_CTIME_ISO_8601 */ | |
"dblg-image", /* TEMPL_IMAGE */ | |
"dblg-mtime", /* TEMPL_MTIME */ | |
"dblg-mtime-iso8601", /* TEMPL_MTIME_ISO_8601 */ | |
"dblg-title", /* TEMPL_TITLE */ | |
}; | |
#define USER "user.id,user.email,user.name,user.link," \ | |
"user.cloudkey,user.cloudsecret,user.cloudname," \ | |
"user.cloudpath,user.flags,user.lang" | |
#define ENTRY "entry.contents,entry.ctime,entry.id,entry.title," \ | |
"entry.latitude,entry.longitude,entry.mtime," \ | |
"entry.flags,entry.lang,entry.aside,entry.image" | |
/* Convenience. */ | |
#define USER_ENTRY \ | |
"SELECT " USER "," ENTRY " FROM entry " \ | |
"INNER JOIN user ON user.id=entry.userid " \ | |
"WHERE entry.flags=0 " | |
#define META "meta.mtime,meta.title,meta.template" | |
static const char *const stmts[STMT__MAX] = { | |
/* STMT_ENTRY_DELETE */ | |
"DELETE FROM entry WHERE id=? AND entry.userid=?", | |
/* STMT_ENTRY_GET */ | |
"SELECT " USER "," ENTRY " FROM entry " | |
"INNER JOIN user ON user.id=entry.userid " | |
"WHERE entry.id=?", | |
/* STMT_ENTRY_GET_PUBLIC */ | |
"SELECT " USER "," ENTRY " FROM entry " | |
"INNER JOIN user ON user.id=entry.userid " | |
"WHERE entry.id=? AND entry.flags=0", | |
/* STMT_ENTRY_LIST_PENDING */ | |
"SELECT " ENTRY " FROM entry " | |
"WHERE entry.flags=1 AND entry.userid=? " | |
"ORDER BY entry.mtime DESC", | |
/* STMT_ENTRY_LIST_PUBLIC */ | |
USER_ENTRY "ORDER BY entry.ctime DESC", | |
/* STMT_ENTRY_LIST_PUBLIC_LANG */ | |
USER_ENTRY "AND entry.lang=? " | |
"ORDER BY entry.ctime DESC", | |
/* STMT_ENTRY_LIST_PUBLIC_LANG_LIMIT */ | |
USER_ENTRY "AND entry.lang=? " | |
"ORDER BY entry.ctime DESC LIMIT ?", | |
/* STMT_ENTRY_LIST_PUBLIC_LIMIT */ | |
USER_ENTRY "ORDER BY entry.ctime DESC LIMIT ?", | |
/* STMT_ENTRY_LIST_PUBLIC_MTIME */ | |
USER_ENTRY "ORDER BY entry.mtime DESC", | |
/* STMT_ENTRY_LIST_PUBLIC_MTIME_LANG */ | |
USER_ENTRY "AND entry.lang=? " | |
"ORDER BY entry.mtime DESC", | |
/* STMT_ENTRY_LIST_PUBLIC_MTIME_LANG_LIMIT */ | |
USER_ENTRY "AND entry.lang=? " | |
"ORDER BY entry.mtime DESC LIMIT ?", | |
/* STMT_ENTRY_LIST_PUBLIC_MTIME_LIMIT */ | |
USER_ENTRY "ORDER BY entry.mtime DESC LIMIT ?", | |
/* STMT_ENTRY_MODIFY */ | |
"UPDATE entry SET contents=?,title=?,latitude=?," | |
"longitude=?,mtime=?,flags=?,lang=?,aside=?," | |
"image=? WHERE userid=? AND id=?", | |
/* STMT_ENTRY_NEW */ | |
"INSERT INTO entry (contents,title,userid,latitude," | |
"longitude,flags,lang,aside,image) " | |
"VALUES (?,?,?,?,?,?,?,?,?)", | |
/* STMT_META_GET */ | |
"SELECT " META " FROM meta LIMIT 1", | |
/* STMT_META_MOD_TEMPLATE */ | |
"UPDATE meta SET mtime=?,template=?", | |
/* STMT_META_MOD_TITLE */ | |
"UPDATE meta SET mtime=?,title=?", | |
/* STMT_META_NEW */ | |
"INSERT INTO meta (mtime) VALUES (?)", | |
/* STMT_META_UPDATE */ | |
"UPDATE meta SET mtime=?", | |
/* STMT_SESS_DEL */ | |
"DELETE FROM sess WHERE id=? AND cookie=?", | |
/* STMT_SESS_GET */ | |
"SELECT " USER " FROM sess " | |
"INNER JOIN user ON user.id=sess.userid " | |
"WHERE sess.id=? AND sess.cookie=?", | |
/* STMT_SESS_NEW */ | |
"INSERT INTO sess (cookie,userid) VALUES (?,?)", | |
/* STMT_USER_ADD */ | |
"INSERT INTO user (name,email,hash,flags) " | |
"VALUES (?,?,?,?)", | |
/* STMT_USER_GET */ | |
"SELECT " USER " FROM user WHERE id=?", | |
/* STMT_USER_LIST */ | |
"SELECT " USER " FROM user", | |
/* STMT_USER_LOOKUP */ | |
"SELECT " USER ",hash FROM user WHERE email=?", | |
/* STMT_USER_MOD_CLOUD */ | |
"UPDATE user SET cloudkey=?,cloudsecret=?," | |
"cloudname=?,cloudpath=? WHERE id=?", | |
/* STMT_USER_MOD_DISABLE */ | |
"UPDATE user SET flags=flags | ? WHERE id=?", | |
/* STMT_USER_MOD_EMAIL */ | |
"UPDATE user SET email=? WHERE id=?", | |
/* STMT_USER_MOD_ENABLE */ | |
"UPDATE user SET flags=flags & ~? WHERE id=?", | |
/* STMT_USER_MOD_HASH */ | |
"UPDATE user SET hash=? WHERE id=?", | |
/* STMT_USER_MOD_LANG */ | |
"UPDATE user SET lang=? WHERE id=?", | |
/* STMT_USER_MOD_LINK */ | |
"UPDATE user SET link=? WHERE id=?", | |
/* STMT_USER_MOD_NAME */ | |
"UPDATE user SET name=? WHERE id=?", | |
}; | |
static const struct kvalid keys[KEY__MAX] = { | |
{ NULL, "admin" }, /* KEY_ADMIN */ | |
{ kvalid_string, "aside" }, /* KEY_ASIDE */ | |
{ kvalid_string, "cloudkey" }, /* KEY_CLOUDKEY */ | |
{ kvalid_string, "cloudname" }, /* KEY_CLOUDNAME */ | |
{ kvalid_string, "cloudpath" }, /* KEY_CLOUDPATH */ | |
{ kvalid_string, "cloudsecret" }, /* KEY_CLOUDSECRET */ | |
{ kvalid_email, "email" }, /* KEY_EMAIL */ | |
{ kvalid_uint, "enable" }, /* KEY_ENABLE */ | |
{ kvalid_int, "entryid" }, /* KEY_ENTRYID */ | |
{ kvalid_string, "image" }, /* KEY_IMAGE */ | |
{ kvalid_string, "lang" }, /* KEY_LANG */ | |
{ kvalid_double, "latitude" }, /* KEY_LATITUDE */ | |
{ kvalid_double, "longitude" }, /* KEY_LONGITUDE */ | |
{ kvalid_uint, "limit" }, /* KEY_LIMIT */ | |
{ kvalid_string, "link" }, /* KEY_LINK */ | |
{ kvalid_stringne, "markdown" }, /* KEY_MARKDOWN */ | |
{ kvalid_string, "mtemplate" }, /* KEY_META_TEMPLATE */ | |
{ kvalid_string, "mtitle" }, /* KEY_META_TITLE */ | |
{ kvalid_stringne, "name" }, /* KEY_NAME */ | |
{ kvalid_stringne, "order" }, /* KEY_ORDER */ | |
{ kvalid_stringne, "pass" }, /* KEY_PASS */ | |
{ NULL, "save" }, /* KEY_SAVE */ | |
{ kvalid_uint, "sesscookie" }, /* KEY_SESSCOOKIE */ | |
{ kvalid_int, "sessid" }, /* KEY_SESSID */ | |
{ kvalid_stringne, "title" }, /* KEY_TITLE */ | |
{ kvalid_int, "userid" }, /* KEY_USERID */ | |
}; | |
static const char *const pages[PAGE__MAX] = { | |
"adduser", /* PAGE_ADD_USER */ | |
"atom", /* PAGE_ATOM */ | |
"index", /* PAGE_INDEX */ | |
"login", /* PAGE_LOGIN */ | |
"logout", /* PAGE_LOGOUT */ | |
"modcloud", /* PAGE_MOD_CLOUD */ | |
"modemail", /* PAGE_MOD_EMAIL */ | |
"modenable", /* PAGE_MOD_ENABLE */ | |
"modlang", /* PAGE_MOD_LANG */ | |
"modlink", /* PAGE_MOD_LINK */ | |
"modmetatemplate", /* PAGE_MOD_META_TEMPLATE */ | |
"modmetatitle", /* PAGE_MOD_META_TITLE */ | |
"modname", /* PAGE_MOD_NAME */ | |
"modpass", /* PAGE_MOD_PASS */ | |
"public", /* PAGE_PUBLIC */ | |
"remove", /* PAGE_REMOVE */ | |
"submit", /* PAGE_SUBMIT */ | |
}; | |
static void | |
sendhttphead(struct kreq *r, enum khttp code) | |
{ | |
khttp_head(r, kresps[KRESP_STATUS], | |
"%s", khttps[code]); | |
khttp_head(r, kresps[KRESP_CONTENT_TYPE], | |
"%s", kmimetypes[r->mime]); | |
khttp_head(r, "X-Content-Type-Options", "nosniff"); | |
khttp_head(r, "X-Frame-Options", "DENY"); | |
khttp_head(r, "X-XSS-Protection", "1; mode=block"); | |
} | |
static void | |
sendhttp(struct kreq *r, enum khttp code) | |
{ | |
sendhttphead(r, code); | |
khttp_body(r); | |
} | |
static void | |
db_user_unfill(struct user *p) | |
{ | |
if (NULL == p) | |
return; | |
free(p->email); | |
free(p->link); | |
free(p->lang); | |
free(p->name); | |
free(p->cloud.key); | |
free(p->cloud.secret); | |
free(p->cloud.path); | |
free(p->cloud.name); | |
} | |
static void | |
db_user_free(struct user *p) | |
{ | |
db_user_unfill(p); | |
free(p); | |
} | |
static void | |
col_if_not_null(char **p, struct ksqlstmt *stmt, size_t pos) | |
{ | |
*p = NULL; | |
if ( ! ksql_stmt_isnull(stmt, pos)) | |
*p = kstrdup(ksql_stmt_str(stmt, pos)); | |
} | |
static void | |
bind_if_not_null(struct ksqlstmt *stmt, size_t pos, const char *v) | |
{ | |
if ('\0' == v[0]) | |
ksql_bind_null(stmt, pos); | |
else | |
ksql_bind_str(stmt, pos, v); | |
} | |
static void | |
db_user_fill(struct user *p, struct ksqlstmt *stmt, size_t *pos) | |
{ | |
size_t i = 0; | |
if (NULL == pos) | |
pos = &i; | |
memset(p, 0, sizeof(struct user)); | |
p->id = ksql_stmt_int(stmt, (*pos)++); | |
p->email = kstrdup(ksql_stmt_str(stmt, (*pos)++)); | |
p->name = kstrdup(ksql_stmt_str(stmt, (*pos)++)); | |
if ( ! ksql_stmt_isnull(stmt, *pos)) | |
p->link = kstrdup(ksql_stmt_str(stmt, *pos)); | |
(*pos)++; | |
if ( ! ksql_stmt_isnull(stmt, *pos) && | |
! ksql_stmt_isnull(stmt, *pos + 1) && | |
! ksql_stmt_isnull(stmt, *pos + 2) && | |
! ksql_stmt_isnull(stmt, *pos + 3)) | |
p->cloud.set = 1; | |
col_if_not_null(&p->cloud.key, stmt, (*pos)++); | |
col_if_not_null(&p->cloud.secret, stmt, (*pos)++); | |
col_if_not_null(&p->cloud.name, stmt, (*pos)++); | |
col_if_not_null(&p->cloud.path, stmt, (*pos)++); | |
p->flags = ksql_stmt_int(stmt, (*pos)++); | |
col_if_not_null(&p->lang, stmt, (*pos)++); | |
} | |
static void | |
db_meta_unfill(struct meta *p) | |
{ | |
if (NULL == p) | |
return; | |
free(p->title); | |
free(p->template); | |
} | |
static void | |
db_meta_fill(struct meta *p, struct ksqlstmt *stmt, size_t *pos) | |
{ | |
size_t i = 0; | |
if (NULL == pos) | |
pos = &i; | |
memset(p, 0, sizeof(struct meta)); | |
p->mtime = ksql_stmt_int(stmt, (*pos)++); | |
col_if_not_null(&p->title, stmt, (*pos)++); | |
col_if_not_null(&p->template, stmt, (*pos)++); | |
} | |
static void | |
db_entry_unfill(struct entry *p) | |
{ | |
if (NULL == p) | |
return; | |
free(p->aside); | |
free(p->image); | |
free(p->content); | |
free(p->title); | |
free(p->lang); | |
} | |
static void | |
db_entry_fill(struct entry *p, struct ksqlstmt *stmt, size_t *pos) | |
{ | |
size_t i = 0; | |
if (NULL == pos) | |
pos = &i; | |
memset(p, 0, sizeof(struct entry)); | |
p->content = kstrdup(ksql_stmt_str(stmt, (*pos)++)); | |
p->ctime = ksql_stmt_int(stmt, (*pos)++); | |
p->id = ksql_stmt_int(stmt, (*pos)++); | |
p->title = kstrdup(ksql_stmt_str(stmt, (*pos)++)); | |
if ( ! ksql_stmt_isnull(stmt, *pos) && | |
! ksql_stmt_isnull(stmt, *pos + 1)) { | |
p->coords = 1; | |
p->lat = ksql_stmt_double(stmt, *pos); | |
p->lng = ksql_stmt_double(stmt, *pos + 1); | |
} else | |
p->coords = 0; | |
(*pos) += 2; | |
p->mtime = ksql_stmt_int(stmt, (*pos)++); | |
p->flags = ksql_stmt_int(stmt, (*pos)++); | |
col_if_not_null(&p->lang, stmt, (*pos)++); | |
col_if_not_null(&p->aside, stmt, (*pos)++); | |
col_if_not_null(&p->image, stmt, (*pos)++); | |
} | |
/* | |
* Get our meta-data (title, mtime, etc.). | |
* This will always succeed, and always return valid data even if we had | |
* to create it in the process. | |
*/ | |
static void | |
db_meta_get(struct kreq *r, struct meta *p) | |
{ | |
struct ksqlstmt *stmt; | |
int64_t mtime; | |
enum ksqlc c; | |
/* Begin by fetching the global last modifier. */ | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_META_GET], | |
STMT_META_GET); | |
if (KSQL_ROW != ksql_stmt_step(stmt)) { | |
ksql_stmt_free(stmt); | |
kutil_info(r, NULL, "creating meta row"); | |
mtime = time(NULL); | |
/* | |
* We might have another creation in the meanwhile, so | |
* make sure we catch the constraint. | |
*/ | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_META_NEW], | |
STMT_META_NEW); | |
ksql_bind_int(stmt, 0, mtime); | |
ksql_stmt_cstep(stmt); | |
ksql_stmt_free(stmt); | |
/* Re-fetch. */ | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_META_GET], | |
STMT_META_GET); | |
c = ksql_stmt_step(stmt); | |
assert(KSQL_ROW == c); | |
db_meta_fill(p, stmt, NULL); | |
ksql_stmt_free(stmt); | |
} else { | |
db_meta_fill(p, stmt, NULL); | |
ksql_stmt_free(stmt); | |
} | |
} | |
static void | |
db_meta_update(struct kreq *r) | |
{ | |
struct ksqlstmt *stmt; | |
struct meta p; | |
/* Make sure record exists. */ | |
db_meta_get(r, &p); | |
db_meta_unfill(&p); | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_META_UPDATE], | |
STMT_META_UPDATE); | |
ksql_bind_int(stmt, 0, time(NULL)); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
} | |
static int64_t | |
db_entry_modify(struct kreq *r, const struct user *user, | |
const char *title, const char *text, | |
int64_t id, double lat, double lng, int save, | |
const char *lang, const char *aside, const char *image) | |
{ | |
struct ksqlstmt *stmt; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_MODIFY], | |
STMT_ENTRY_MODIFY); | |
ksql_bind_str(stmt, 0, text); | |
ksql_bind_str(stmt, 1, title); | |
if ((0.0 == lat || isnormal(lat)) && | |
(0.0 == lng || isnormal(lng))) { | |
ksql_bind_double(stmt, 2, lat); | |
ksql_bind_double(stmt, 3, lng); | |
} else { | |
ksql_bind_null(stmt, 2); | |
ksql_bind_null(stmt, 3); | |
} | |
ksql_bind_int(stmt, 4, time(NULL)); | |
ksql_bind_int(stmt, 5, save ? 1 : 0); | |
bind_if_not_null(stmt, 6, lang); | |
bind_if_not_null(stmt, 7, aside); | |
bind_if_not_null(stmt, 8, image); | |
ksql_bind_int(stmt, 9, user->id); | |
ksql_bind_int(stmt, 10, id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, user->email, | |
"modified entry: %" PRId64, id); | |
db_meta_update(r); | |
return(id); | |
} | |
static int64_t | |
db_entry_new(struct kreq *r, const struct user *user, | |
const char *title, const char *text, | |
double lat, double lng, int save, const char *lang, | |
const char *aside, const char *img) | |
{ | |
struct ksqlstmt *stmt; | |
int64_t id; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_NEW], | |
STMT_ENTRY_NEW); | |
ksql_bind_str(stmt, 0, text); | |
ksql_bind_str(stmt, 1, title); | |
ksql_bind_int(stmt, 2, user->id); | |
if ((0.0 == lat || isnormal(lat)) && | |
(0.0 == lng || isnormal(lng))) { | |
ksql_bind_double(stmt, 3, lat); | |
ksql_bind_double(stmt, 4, lng); | |
} else { | |
ksql_bind_null(stmt, 3); | |
ksql_bind_null(stmt, 4); | |
} | |
ksql_bind_int(stmt, 5, save ? 1 : 0); | |
bind_if_not_null(stmt, 6, lang); | |
bind_if_not_null(stmt, 7, aside); | |
bind_if_not_null(stmt, 8, img); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
ksql_lastid(r->arg, &id); | |
kutil_info(r, user->email, | |
"new entry: %" PRId64, id); | |
db_meta_update(r); | |
return(id); | |
} | |
static int64_t | |
db_sess_new(struct ksql *sql, int64_t cookie, | |
const struct user *user) | |
{ | |
struct ksqlstmt *stmt; | |
int64_t id; | |
ksql_stmt_alloc(sql, &stmt, | |
stmts[STMT_SESS_NEW], | |
STMT_SESS_NEW); | |
ksql_bind_int(stmt, 0, cookie); | |
ksql_bind_int(stmt, 1, user->id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
ksql_lastid(sql, &id); | |
return(id); | |
} | |
struct user * | |
db_user_find(struct ksql *sql, | |
const char *email, const char *pass) | |
{ | |
struct ksqlstmt *stmt; | |
int rc; | |
size_t i; | |
const char *hash; | |
struct user *user; | |
ksql_stmt_alloc(sql, &stmt, | |
stmts[STMT_USER_LOOKUP], | |
STMT_USER_LOOKUP); | |
ksql_bind_str(stmt, 0, email); | |
if (KSQL_ROW != ksql_stmt_step(stmt)) { | |
ksql_stmt_free(stmt); | |
return(NULL); | |
} | |
i = 0; | |
user = kmalloc(sizeof(struct user)); | |
db_user_fill(user, stmt, &i); | |
hash = ksql_stmt_str(stmt, i); | |
#ifdef __OpenBSD__ | |
rc = crypt_checkpass(pass, hash) < 0 ? 0 : 1; | |
#else | |
rc = 0 == strcmp(hash, pass); | |
#endif | |
ksql_stmt_free(stmt); | |
if (0 == rc) { | |
db_user_free(user); | |
user = NULL; | |
} | |
return(user); | |
} | |
static struct user * | |
db_user_sess_get(struct ksql *sql, int64_t id, int64_t cookie) | |
{ | |
struct ksqlstmt *stmt; | |
struct user *u = NULL; | |
if (-1 == id || -1 == cookie) | |
return(NULL); | |
ksql_stmt_alloc(sql, &stmt, | |
stmts[STMT_SESS_GET], | |
STMT_SESS_GET); | |
ksql_bind_int(stmt, 0, id); | |
ksql_bind_int(stmt, 1, cookie); | |
if (KSQL_ROW == ksql_stmt_step(stmt)) { | |
u = kmalloc(sizeof(struct user)); | |
db_user_fill(u, stmt, NULL); | |
} | |
ksql_stmt_free(stmt); | |
return(u); | |
} | |
static void | |
db_sess_del(struct ksql *sql, int64_t id, int64_t cookie) | |
{ | |
struct ksqlstmt *stmt; | |
ksql_stmt_alloc(sql, &stmt, | |
stmts[STMT_SESS_DEL], | |
STMT_SESS_DEL); | |
ksql_bind_int(stmt, 0, id); | |
ksql_bind_int(stmt, 1, cookie); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
} | |
static void | |
db_user_mod_pass(struct kreq *r, | |
const struct user *u, const char *pass) | |
{ | |
struct ksqlstmt *stmt; | |
char hash[64]; | |
#ifdef __OpenBSD__ | |
crypt_newhash(pass, "blowfish,a", hash, sizeof(hash)); | |
#else | |
strlcpy(hash, pass, sizeof(hash)); | |
#endif | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_HASH], | |
STMT_USER_MOD_HASH); | |
ksql_bind_str(stmt, 0, hash); | |
ksql_bind_int(stmt, 1, u->id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "changed password"); | |
} | |
static int | |
db_user_mod_email(struct kreq *r, | |
const struct user *u, const char *email) | |
{ | |
struct ksqlstmt *stmt; | |
enum ksqlc c; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_EMAIL], | |
STMT_USER_MOD_EMAIL); | |
ksql_bind_str(stmt, 0, email); | |
ksql_bind_int(stmt, 1, u->id); | |
c = ksql_stmt_cstep(stmt); | |
ksql_stmt_free(stmt); | |
if (KSQL_CONSTRAINT != c) { | |
kutil_info(r, u->email, | |
"changed email: %s", email); | |
/* Update: this is public data. */ | |
db_meta_update(r); | |
} | |
return(KSQL_CONSTRAINT != c); | |
} | |
static void | |
db_user_mod_enable(struct kreq *r, | |
const struct user *u, int64_t userid, int enable) | |
{ | |
struct ksqlstmt *stmt; | |
if (enable) | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_ENABLE], | |
STMT_USER_MOD_ENABLE); | |
else | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_DISABLE], | |
STMT_USER_MOD_DISABLE); | |
ksql_bind_int(stmt, 0, USER_DISABLED); | |
ksql_bind_int(stmt, 1, userid); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "%s user: %" PRId64, | |
enable ? "enabled" : "disabled", userid); | |
} | |
static void | |
db_mod_meta_template(struct kreq *r, | |
const struct user *u, const char *template) | |
{ | |
struct ksqlstmt *stmt; | |
struct meta meta; | |
/* Make sure the record exists. */ | |
db_meta_get(r, &meta); | |
db_meta_unfill(&meta); | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_META_MOD_TEMPLATE], | |
STMT_META_MOD_TEMPLATE); | |
ksql_bind_int(stmt, 0, time(NULL)); | |
bind_if_not_null(stmt, 1, template); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, | |
"changed meta template: %s", template); | |
} | |
static void | |
db_mod_meta_title(struct kreq *r, | |
const struct user *u, const char *title) | |
{ | |
struct ksqlstmt *stmt; | |
struct meta meta; | |
/* Make sure the record exists. */ | |
db_meta_get(r, &meta); | |
db_meta_unfill(&meta); | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_META_MOD_TITLE], | |
STMT_META_MOD_TITLE); | |
ksql_bind_int(stmt, 0, time(NULL)); | |
bind_if_not_null(stmt, 1, title); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "changed meta title"); | |
} | |
static void | |
db_user_mod_cloud(struct kreq *r, const struct user *u, | |
const char *key, const char *secret, | |
const char *name, const char *path) | |
{ | |
struct ksqlstmt *stmt; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_CLOUD], | |
STMT_USER_MOD_CLOUD); | |
bind_if_not_null(stmt, 0, key); | |
bind_if_not_null(stmt, 1, secret); | |
bind_if_not_null(stmt, 2, name); | |
ksql_bind_str(stmt, 3, path); | |
ksql_bind_int(stmt, 4, u->id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "changed cloud parameters"); | |
} | |
static void | |
db_user_mod_lang(struct kreq *r, | |
const struct user *u, const char *lang) | |
{ | |
struct ksqlstmt *stmt; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_LANG], | |
STMT_USER_MOD_LANG); | |
bind_if_not_null(stmt, 0, lang); | |
ksql_bind_int(stmt, 1, u->id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "changed lang"); | |
} | |
static void | |
db_user_mod_link(struct kreq *r, | |
const struct user *u, const char *link) | |
{ | |
struct ksqlstmt *stmt; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_LINK], | |
STMT_USER_MOD_LINK); | |
bind_if_not_null(stmt, 0, link); | |
ksql_bind_int(stmt, 1, u->id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "changed link"); | |
/* Update: this is public data. */ | |
db_meta_update(r); | |
} | |
static int | |
db_user_add(struct kreq *r, const struct user *u, | |
const char *email, const char *pass, int admin) | |
{ | |
struct ksqlstmt *stmt; | |
char hash[64]; | |
enum ksqlc c; | |
#ifdef __OpenBSD__ | |
crypt_newhash(pass, "blowfish,a", hash, sizeof(hash)); | |
#else | |
strlcpy(hash, pass, sizeof(hash)); | |
#endif | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_ADD], | |
STMT_USER_ADD); | |
ksql_bind_str(stmt, 0, "Anonymous user"); | |
ksql_bind_str(stmt, 1, email); | |
ksql_bind_str(stmt, 2, hash); | |
ksql_bind_int(stmt, 3, admin ? 1 : 0); | |
c = ksql_stmt_cstep(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "added user: %s", email); | |
return(KSQL_CONSTRAINT != c); | |
} | |
static void | |
db_user_mod_name(struct kreq *r, | |
const struct user *u, const char *name) | |
{ | |
struct ksqlstmt *stmt; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_MOD_NAME], | |
STMT_USER_MOD_NAME); | |
ksql_bind_str(stmt, 0, name); | |
ksql_bind_int(stmt, 1, u->id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "changed name"); | |
/* Update: this is public data. */ | |
db_meta_update(r); | |
} | |
static void | |
db_entry_delete(struct kreq *r, | |
const struct user *u, int64_t id) | |
{ | |
struct ksqlstmt *stmt; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_DELETE], | |
STMT_ENTRY_DELETE); | |
ksql_bind_int(stmt, 0, id); | |
ksql_bind_int(stmt, 1, u->id); | |
ksql_stmt_step(stmt); | |
ksql_stmt_free(stmt); | |
kutil_info(r, u->email, "deleted entry: %" PRId64, id); | |
db_meta_update(r); | |
} | |
static void | |
json_if_not_null(struct kjsonreq *req, const char *name, const char *val) | |
{ | |
kjson_putstringp(req, name, NULL == val ? "" : val); | |
} | |
static void | |
json_putuserdata(struct kjsonreq *req, | |
const struct user *u, int public) | |
{ | |
kjson_putstringp(req, "name", u->name); | |
kjson_putstringp(req, "email", u->email); | |
json_if_not_null(req, "link", u->link); | |
kjson_putintp(req, "id", u->id); | |
if ( ! public) { | |
kjson_objp_open(req, "cloud"); | |
json_if_not_null(req, "key", u->cloud.key); | |
json_if_not_null(req, "secret", u->cloud.secret); | |
json_if_not_null(req, "name", u->cloud.name); | |
json_if_not_null(req, "path", u->cloud.path); | |
kjson_putintp(req, "set", u->cloud.set); | |
kjson_obj_close(req); | |
} else | |
kjson_putnullp(req, "cloud"); | |
kjson_objp_open(req, "attrs"); | |
kjson_putboolp(req, "admin", | |
USER_ADMIN & u->flags); | |
kjson_putboolp(req, "disabled", | |
USER_DISABLED & u->flags); | |
kjson_obj_close(req); | |
json_if_not_null(req, "lang", u->lang); | |
} | |
static void | |
json_putuser(struct kjsonreq *req, | |
const struct user *u, int public) | |
{ | |
if (NULL != u) { | |
kjson_objp_open(req, "user"); | |
json_putuserdata(req, u, public); | |
kjson_obj_close(req); | |
} else | |
kjson_putnullp(req, "user"); | |
} | |
static void | |
json_putentry(struct kjsonreq *req, const struct user *u, | |
const struct entry *entry, const char *name) | |
{ | |
if (NULL != name) | |
kjson_objp_open(req, name); | |
else | |
kjson_obj_open(req); | |
json_putuser(req, u, 0); | |
kjson_putintp(req, "ctime", entry->ctime); | |
kjson_putintp(req, "mtime", entry->mtime); | |
kjson_putintp(req, "id", entry->id); | |
kjson_putstringp(req, "content", entry->content); | |
json_if_not_null(req, "aside", entry->aside); | |
json_if_not_null(req, "image", entry->image); | |
kjson_putstringp(req, "title", entry->title); | |
if (entry->coords) { | |
kjson_objp_open(req, "coords"); | |
kjson_putdoublep(req, "lat", entry->lat); | |
kjson_putdoublep(req, "lng", entry->lng); | |
kjson_obj_close(req); | |
} else | |
kjson_putnullp(req, "coords"); | |
kjson_objp_open(req, "attrs"); | |
kjson_putboolp(req, "pending", | |
ENTRY_PENDING & entry->flags); | |
kjson_obj_close(req); | |
json_if_not_null(req, "lang", entry->lang); | |
kjson_obj_close(req); | |
} | |
static void | |
sendremove(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kpn; | |
if (NULL == (kpn = r->fieldmap[KEY_ENTRYID])) { | |
sendhttp(r, KHTTP_400); | |
return; | |
} | |
assert(NULL != u); | |
db_entry_delete(r, u, kpn->parsed.i); | |
sendhttp(r, KHTTP_200); | |
} | |
static void | |
sendsubmit(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kpm, *kpt, *kpi, *kplat, *kplng, *kpl, | |
*kpa, *kpimg; | |
int64_t id; | |
struct kjsonreq req; | |
double lat, lng; | |
int save; | |
/* Are we here to save or to publish? */ | |
save = NULL != r->fieldmap[KEY_SAVE]; | |
assert(NULL != u); | |
kpa = r->fieldmap[KEY_ASIDE]; | |
kpimg = r->fieldmap[KEY_IMAGE]; | |
kpm = r->fieldmap[KEY_MARKDOWN]; | |
kpt = r->fieldmap[KEY_TITLE]; | |
kplat = r->fieldmap[KEY_LATITUDE]; | |
kplng = r->fieldmap[KEY_LONGITUDE]; | |
kpl = r->fieldmap[KEY_LANG]; | |
/* Require fields only if we're publishing. */ | |
if ( ! save && (NULL == kpt || NULL == kpm)) { | |
sendhttp(r, KHTTP_400); | |
return; | |
} | |
/* We need both coordinates for orientation. */ | |
if (NULL != kplat && NULL != kplng) { | |
lat = r->fieldmap[KEY_LATITUDE]->parsed.d; | |
lng = r->fieldmap[KEY_LONGITUDE]->parsed.d; | |
} else | |
lat = lng = 1.0 / 0.0; | |
if (NULL != (kpi = r->fieldmap[KEY_ENTRYID]) && | |
kpi->parsed.i > 0) | |
id = db_entry_modify(r, u, | |
NULL == kpt ? "" : kpt->parsed.s, | |
NULL == kpm ? "" : kpm->parsed.s, | |
kpi->parsed.i, lat, lng, save, | |
NULL != kpl ? kpl->parsed.s : "", | |
NULL != kpa ? kpa->parsed.s : "", | |
NULL != kpimg ? kpimg->parsed.s : ""); | |
else | |
id = db_entry_new(r, u, | |
NULL == kpt ? "" : kpt->parsed.s, | |
NULL == kpm ? "" : kpm->parsed.s, | |
lat, lng, save, | |
NULL != kpl ? kpl->parsed.s : "", | |
NULL != kpa ? kpa->parsed.s : "", | |
NULL != kpimg ? kpimg->parsed.s : ""); | |
sendhttp(r, KHTTP_200); | |
kjson_open(&req, r); | |
kjson_obj_open(&req); | |
kjson_putintp(&req, "id", id); | |
kjson_obj_close(&req); | |
kjson_close(&req); | |
} | |
static void | |
sendmodlang(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kp; | |
kp = r->fieldmap[KEY_LANG]; | |
db_user_mod_lang(r, u, NULL != kp ? kp->parsed.s : ""); | |
sendhttp(r, KHTTP_200); | |
} | |
static void | |
sendmodlink(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kp; | |
kp = r->fieldmap[KEY_LINK]; | |
db_user_mod_link(r, u, NULL != kp ? kp->parsed.s : ""); | |
sendhttp(r, KHTTP_200); | |
} | |
static void | |
sendmodmetatemplate(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kp; | |
kp = r->fieldmap[KEY_META_TEMPLATE]; | |
db_mod_meta_template(r, u, NULL == kp ? "" : kp->parsed.s); | |
sendhttp(r, KHTTP_200); | |
} | |
static void | |
sendmodmetatitle(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kp; | |
kp = r->fieldmap[KEY_META_TITLE]; | |
db_mod_meta_title(r, u, NULL == kp ? "" : kp->parsed.s); | |
sendhttp(r, KHTTP_200); | |
} | |
static void | |
sendmodcloud(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kpn, *kpk, *kps, *kpp; | |
if (NULL != (kpk = r->fieldmap[KEY_CLOUDKEY]) && | |
NULL != (kps = r->fieldmap[KEY_CLOUDSECRET]) && | |
NULL != (kpn = r->fieldmap[KEY_CLOUDNAME]) && | |
NULL != (kpp = r->fieldmap[KEY_CLOUDPATH])) { | |
db_user_mod_cloud(r, u, | |
kpk->parsed.s, kps->parsed.s, | |
kpn->parsed.s, kpp->parsed.s); | |
sendhttp(r, KHTTP_200); | |
} else | |
sendhttp(r, KHTTP_400); | |
} | |
static void | |
sendmodenable(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kpe, *kpn; | |
if (NULL != (kpe = r->fieldmap[KEY_USERID]) && | |
NULL != (kpn = r->fieldmap[KEY_ENABLE]) && | |
u->id != kpe->parsed.i) { | |
db_user_mod_enable(r, u, | |
kpe->parsed.i, | |
kpn->parsed.i ? 1 : 0); | |
sendhttp(r, KHTTP_200); | |
} else | |
sendhttp(r, KHTTP_400); | |
} | |
static void | |
sendmodemail(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kp; | |
int rc; | |
if (NULL != (kp = r->fieldmap[KEY_EMAIL])) { | |
rc = db_user_mod_email(r, u, kp->parsed.s); | |
sendhttp(r, rc ? KHTTP_200 : KHTTP_400); | |
} else | |
sendhttp(r, KHTTP_400); | |
} | |
static void | |
sendmodpass(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kp; | |
if (NULL != (kp = r->fieldmap[KEY_PASS])) { | |
sendhttp(r, KHTTP_200); | |
db_user_mod_pass(r, u, kp->parsed.s); | |
} else | |
sendhttp(r, KHTTP_400); | |
} | |
static void | |
sendmodname(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kp; | |
if (NULL != (kp = r->fieldmap[KEY_NAME])) { | |
sendhttp(r, KHTTP_200); | |
db_user_mod_name(r, u, kp->parsed.s); | |
} else | |
sendhttp(r, KHTTP_400); | |
} | |
static void | |
sendadduser(struct kreq *r, const struct user *u) | |
{ | |
struct kpair *kpe, *kpp, *kpa; | |
int rc; | |
assert(NULL != u && USER_ADMIN & u->flags); | |
kpa = r->fieldmap[KEY_ADMIN]; | |
rc = 0; | |
if (NULL != (kpe = r->fieldmap[KEY_EMAIL]) && | |
NULL != (kpp = r->fieldmap[KEY_PASS])) | |
rc = db_user_add(r, u, kpe->parsed.s, | |
kpp->parsed.s, NULL != kpa); | |
sendhttp(r, rc ? KHTTP_200 : KHTTP_400); | |
} | |
static void | |
sendindex(struct kreq *r, const struct user *u) | |
{ | |
struct kjsonreq req; | |
struct ksqlstmt *stmt; | |
struct kpair *kpi; | |
size_t i; | |
struct user user; | |
struct entry entry; | |
struct meta meta; | |
assert(NULL != u); | |
sendhttp(r, KHTTP_200); | |
kjson_open(&req, r); | |
kjson_obj_open(&req); | |
json_putuser(&req, u, 0); | |
if (USER_ADMIN & u->flags) { | |
db_meta_get(r, &meta); | |
kjson_objp_open(&req, "meta"); | |
kjson_putstringp(&req, "title", | |
NULL == meta.title ? "" : meta.title); | |
kjson_putstringp(&req, "template", | |
NULL == meta.template ? "" : meta.template); | |
kjson_obj_close(&req); | |
db_meta_unfill(&meta); | |
} else | |
kjson_putnullp(&req, "meta"); | |
kjson_arrayp_open(&req, "users"); | |
if (USER_ADMIN & u->flags) { | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_USER_LIST], | |
STMT_USER_LIST); | |
while (KSQL_ROW == ksql_stmt_step(stmt)) { | |
db_user_fill(&user, stmt, NULL); | |
kjson_obj_open(&req); | |
json_putuserdata(&req, &user, 0); | |
kjson_obj_close(&req); | |
} | |
ksql_stmt_free(stmt); | |
} | |
kjson_array_close(&req); | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_LIST_PENDING], | |
STMT_ENTRY_LIST_PENDING); | |
ksql_bind_int(stmt, 0, u->id); | |
kjson_arrayp_open(&req, "pending"); | |
while (KSQL_ROW == ksql_stmt_step(stmt)) { | |
db_entry_fill(&entry, stmt, NULL); | |
json_putentry(&req, NULL, &entry, NULL); | |
db_entry_unfill(&entry); | |
} | |
ksql_stmt_free(stmt); | |
kjson_array_close(&req); | |
if (NULL != (kpi = r->fieldmap[KEY_ENTRYID])) { | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_GET], | |
STMT_ENTRY_GET); | |
ksql_bind_int(stmt, 0, kpi->parsed.i); | |
if (KSQL_ROW == ksql_stmt_step(stmt)) { | |
i = 0; | |
db_user_fill(&user, stmt, &i); | |
db_entry_fill(&entry, stmt, &i); | |
json_putentry(&req, &user, &entry, "entry"); | |
db_user_unfill(&user); | |
db_entry_unfill(&entry); | |
} else | |
kjson_putnullp(&req, "entry"); | |
ksql_stmt_free(stmt); | |
} else | |
kjson_putnullp(&req, "entry"); | |
kjson_obj_close(&req); | |
kjson_close(&req); | |
} | |
static void | |
sendatom(struct kreq *r) | |
{ | |
struct kxmlreq req; | |
struct ksqlstmt *stmt; | |
struct entry entry; | |
struct user user; | |
size_t i; | |
char buf[256]; | |
struct meta meta; | |
struct tm tm; | |
db_meta_get(r, &meta); | |
sendhttp(r, KHTTP_200); | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_LIST_PUBLIC], | |
STMT_ENTRY_LIST_PUBLIC); | |
kxml_open(&req, r, xmls, XML__MAX); | |
kxml_pushattrs(&req, XML_FEED, "xmlns", | |
"http://www.w3.org/2005/Atom", NULL); | |
kutil_epoch2utcstr(meta.mtime, buf, sizeof(buf)); | |
kxml_push(&req, XML_UPDATED); | |
kxml_puts(&req, buf); | |
kxml_pop(&req); | |
snprintf(buf, sizeof(buf), "%s://%s/", | |
kschemes[r->scheme], r->host); | |
kxml_push(&req, XML_ID); | |
kxml_puts(&req, buf); | |
kxml_pop(&req); | |
snprintf(buf, sizeof(buf), "%s://%s%s%s", | |
kschemes[r->scheme], r->host, r->pname, r->fullpath); | |
kxml_pushnullattrs(&req, XML_LINK, | |
"rel", "self", "href", buf, NULL); | |
kxml_push(&req, XML_TITLE); | |
if (NULL == meta.title || '\0' == meta.title[0]) | |
kxml_puts(&req, r->host); | |
else | |
kxml_puts(&req, meta.title); | |
kxml_pop(&req); | |
while (KSQL_ROW == ksql_stmt_step(stmt)) { | |
kxml_push(&req, XML_ENTRY); | |
i = 0; | |
db_user_fill(&user, stmt, &i); | |
db_entry_fill(&entry, stmt, &i); | |
kxml_push(&req, XML_TITLE); | |
kxml_puts(&req, entry.title); | |
kxml_pop(&req); | |
KUTIL_EPOCH2TM(entry.ctime, &tm); | |
snprintf(buf, sizeof(buf), | |
"tag:%s,%.4d-%.2d-%.2d:%" PRId64, | |
r->host, tm.tm_year + 1900, | |
tm.tm_mon, tm.tm_mday, entry.id); | |
kxml_push(&req, XML_ID); | |
kxml_puts(&req, buf); | |
kxml_pop(&req); | |
snprintf(buf, sizeof(buf), | |
"%s://%s%s/%s.html?%s=%" PRId64, | |
kschemes[r->scheme], r->host, | |
r->pname, pages[PAGE_PUBLIC], | |
keys[KEY_ENTRYID].name, entry.id); | |
kxml_pushnullattrs(&req, XML_LINK, | |
"rel", "alternate", | |
"type", kmimetypes[KMIME_TEXT_HTML], | |
"href", buf, NULL); | |
kutil_epoch2utcstr(entry.ctime, buf, sizeof(buf)); | |
kxml_push(&req, XML_PUBLISHED); | |
kxml_puts(&req, buf); | |
kxml_pop(&req); | |
kutil_epoch2utcstr(entry.mtime, buf, sizeof(buf)); | |
kxml_push(&req, XML_UPDATED); | |
kxml_puts(&req, buf); | |
kxml_pop(&req); | |
kxml_push(&req, XML_AUTHOR); | |
kxml_push(&req, XML_NAME); | |
kxml_puts(&req, user.name); | |
kxml_pop(&req); | |
kxml_push(&req, XML_EMAIL); | |
kxml_puts(&req, user.email); | |
kxml_pop(&req); | |
if (NULL != user.link) { | |
kxml_push(&req, XML_URI); | |
kxml_puts(&req, user.link); | |
kxml_pop(&req); | |
} | |
kxml_pop(&req); | |
db_user_unfill(&user); | |
db_entry_unfill(&entry); | |
kxml_pop(&req); | |
} | |
kxml_close(&req); | |
ksql_stmt_free(stmt); | |
db_meta_unfill(&meta); | |
} | |
static int | |
sendtemplate(size_t key, void *arg) | |
{ | |
struct htmldata *data = arg; | |
char buf[1024]; | |
switch (key) { | |
case (TEMPL_ASIDE): | |
khtml_puts(data->req, NULL == data->entry.aside ? | |
"" : data->entry.aside); | |
break; | |
case (TEMPL_AUTHOR_LINK): | |
khtml_puts(data->req, NULL == data->user.link ? | |
"" : data->user.link); | |
break; | |
case (TEMPL_AUTHOR_NAME): | |
khtml_puts(data->req, data->user.name); | |
break; | |
case (TEMPL_CLASSES): | |
if (NULL != data->user.link) | |
khtml_puts(data->req, " author-has-link"); | |
if (NULL != data->entry.aside) | |
khtml_puts(data->req, " blog-has-aside"); | |
if (NULL != data->entry.image) | |
khtml_puts(data->req, " blog-has-image"); | |
if (NULL != data->entry.aside && | |
NULL != data->entry.image) | |
khtml_puts(data->req, " blog-has-image-aside"); | |
if (NULL != data->entry.aside && | |
NULL == data->entry.image) | |
khtml_puts(data->req, " blog-has-only-aside"); | |
if (NULL == data->entry.aside && | |
NULL != data->entry.image) | |
khtml_puts(data->req, " blog-has-only-image"); | |
if (data->entry.ctime == data->entry.mtime) | |
khtml_puts(data->req, " blog-has-only-ctime"); | |
if (data->entry.ctime != data->entry.mtime) | |
khtml_puts(data->req, " blog-has-mtime"); | |
if (data->entry.coords) | |
khtml_puts(data->req, " blog-has-coords"); | |
break; | |
case (TEMPL_CANON): | |
snprintf(buf, sizeof(buf), | |
"%s://%s%s/%s.html?%s=%" PRId64, | |
kschemes[data->req->req->scheme], | |
data->req->req->host, | |
data->req->req->pname, pages[PAGE_PUBLIC], | |
keys[KEY_ENTRYID].name, data->entry.id); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_CANON_QUERY): | |
snprintf(buf, sizeof(buf), "%s=%" PRId64, | |
keys[KEY_ENTRYID].name, data->entry.id); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_CONTENT): | |
khtml_puts(data->req, data->entry.content); | |
break; | |
case (TEMPL_COORD_LAT_DECIMAL): | |
snprintf(buf, sizeof(buf), "%g", data->entry.lat); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_COORD_LNG_DECIMAL): | |
snprintf(buf, sizeof(buf), "%g", data->entry.lng); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_CTIME): | |
snprintf(buf, sizeof(buf), "%lld", | |
(long long)data->entry.ctime); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_CTIME_ISO_8601): | |
kutil_epoch2utcstr | |
(data->entry.ctime, buf, sizeof(buf)); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_IMAGE): | |
khtml_puts(data->req, NULL != data->entry.image ? | |
data->entry.image : ""); | |
break; | |
case (TEMPL_MTIME): | |
snprintf(buf, sizeof(buf), "%lld", | |
(long long)data->entry.mtime); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_MTIME_ISO_8601): | |
kutil_epoch2utcstr | |
(data->entry.mtime, buf, sizeof(buf)); | |
khtml_puts(data->req, buf); | |
break; | |
case (TEMPL_TITLE): | |
khtml_puts(data->req, data->entry.title); | |
break; | |
default: | |
abort(); | |
} | |
return(1); | |
} | |
/* | |
* Our public-facing HTML side. | |
* This only prints a particular blog entry. | |
* Everything we print here is public. | |
* If "u" is non-NULL, however, it's also sent to the client. | |
*/ | |
static void | |
sendpublichtml(struct kreq *r, const struct user *u) | |
{ | |
struct khead *kr; | |
struct kpair *kpi; | |
struct ksqlstmt *stmt; | |
struct khtmlreq req; | |
struct htmldata data; | |
size_t i = 0; | |
char buf[64]; | |
char *fbuf; | |
struct meta meta; | |
struct ktemplate t; | |
if (NULL == (kpi = r->fieldmap[KEY_ENTRYID])) { | |
sendhttp(r, KHTTP_404); | |
return; | |
} | |
db_meta_get(r, &meta); | |
if (NULL == meta.template) { | |
sendhttp(r, KHTTP_404); | |
return; | |
} | |
memset(&req, 0, sizeof(struct khtmlreq)); | |
req.req = r; | |
memset(&data, 0, sizeof(struct htmldata)); | |
data.u = u; | |
data.req = &req; | |
kr = r->reqmap[KREQU_IF_NONE_MATCH]; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_GET_PUBLIC], | |
STMT_ENTRY_GET_PUBLIC); | |
ksql_bind_int(stmt, 0, kpi->parsed.i); | |
if (KSQL_ROW != ksql_stmt_step(stmt)) { | |
ksql_stmt_free(stmt); | |
sendhttp(r, KHTTP_404); | |
return; | |
} | |
db_user_fill(&data.user, stmt, &i); | |
db_entry_fill(&data.entry, stmt, &i); | |
ksql_stmt_free(stmt); | |
snprintf(buf, sizeof(buf), | |
"\"%" PRId64 "\"", meta.mtime); | |
if (NULL != kr && | |
0 == strcmp(buf, kr->val)) { | |
sendhttp(r, KHTTP_304); | |
db_user_unfill(&data.user); | |
db_entry_unfill(&data.entry); | |
return; | |
} | |
memset(&t, 0, sizeof(struct ktemplate)); | |
t.key = templs; | |
t.keysz = TEMPL__MAX; | |
t.arg = &data; | |
t.cb = sendtemplate; | |
kasprintf(&fbuf, "%s", meta.template); | |
sendhttphead(r, KHTTP_200); | |
khttp_head(r, kresps[KRESP_ETAG], "%s", buf); | |
khttp_body(r); | |
if ( ! khttp_template(r, &t, fbuf)) | |
kutil_warnx(r, NULL == u ? NULL : u->email, | |
"khttp_template: %s", fbuf); | |
db_user_unfill(&data.user); | |
db_entry_unfill(&data.entry); | |
db_meta_unfill(&meta); | |
free(fbuf); | |
} | |
/* | |
* Our public-facing JSON side. | |
* Everything we print here is public. | |
* If "u" is non-NULL, however, it's also sent to the client. | |
*/ | |
static void | |
sendpublicjson(struct kreq *r, const struct user *u) | |
{ | |
struct khead *kr; | |
struct kpair *kpi, *kplim, *kpo; | |
struct kjsonreq req; | |
struct ksqlstmt *stmt; | |
struct entry entry; | |
struct user user; | |
size_t first, i; | |
enum stmt estmt; | |
char buf[64]; | |
int omtime = 0; | |
const char *lang; | |
struct meta meta; | |
db_meta_get(r, &meta); | |
/* Should we order by mtime instead of the default? */ | |
if (NULL != (kpo = r->fieldmap[KEY_ORDER]) && | |
0 == strcmp(kpo->parsed.s, "mtime")) | |
omtime = 1; | |
kr = r->reqmap[KREQU_IF_NONE_MATCH]; | |
kplim = r->fieldmap[KEY_LIMIT]; | |
/* Can be NULL or empty: either way, disregard. */ | |
lang = NULL == r->fieldmap[KEY_LANG] ? | |
NULL : '\0' == *r->fieldmap[KEY_LANG]->parsed.s ? | |
NULL : r->fieldmap[KEY_LANG]->parsed.s; | |
if (NULL != (kpi = r->fieldmap[KEY_ENTRYID])) { | |
/* | |
* If we're looking for a specific entry, we ignore all | |
* of the other filters. | |
*/ | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[STMT_ENTRY_GET_PUBLIC], | |
STMT_ENTRY_GET_PUBLIC); | |
ksql_bind_int(stmt, 0, kpi->parsed.i); | |
} else if (NULL != kplim && NULL == lang) { | |
/* Filter by establishing a limit. */ | |
estmt = omtime ? | |
STMT_ENTRY_LIST_PUBLIC_MTIME_LIMIT : | |
STMT_ENTRY_LIST_PUBLIC_LIMIT; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[estmt], estmt); | |
ksql_bind_int(stmt, 0, kplim->parsed.i); | |
} else if (NULL != kplim && NULL != lang) { | |
/* Filter by establishing a limit and language. */ | |
estmt = omtime ? | |
STMT_ENTRY_LIST_PUBLIC_MTIME_LANG_LIMIT : | |
STMT_ENTRY_LIST_PUBLIC_LANG_LIMIT; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[estmt], estmt); | |
ksql_bind_str(stmt, 0, lang); | |
ksql_bind_int(stmt, 1, kplim->parsed.i); | |
} else if (NULL == kplim && NULL != lang) { | |
/* Filter by language. */ | |
estmt = omtime ? | |
STMT_ENTRY_LIST_PUBLIC_MTIME_LANG: | |
STMT_ENTRY_LIST_PUBLIC_LANG; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[estmt], estmt); | |
ksql_bind_str(stmt, 0, lang); | |
} else { | |
/* Do not filter at all: grab all. */ | |
estmt = omtime ? | |
STMT_ENTRY_LIST_PUBLIC_MTIME: | |
STMT_ENTRY_LIST_PUBLIC; | |
ksql_stmt_alloc(r->arg, &stmt, | |
stmts[estmt], estmt); | |
} | |
first = 1; | |
while (KSQL_ROW == ksql_stmt_step(stmt)) { | |
i = 0; | |
db_user_fill(&user, stmt, &i); | |
db_entry_fill(&entry, stmt, &i); | |
if (first) { | |
first = 0; | |
snprintf(buf, sizeof(buf), | |
"\"%" PRId64 "\"", meta.mtime); | |
if (NULL != kr && | |
0 == strcmp(buf, kr->val)) { | |
sendhttp(r, KHTTP_304); | |
ksql_stmt_free(stmt); | |
db_user_unfill(&user); | |
db_entry_unfill(&entry); | |
return; | |
} | |
sendhttphead(r, KHTTP_200); | |
khttp_head(r, kresps[KRESP_ETAG], "%s", buf); | |
khttp_body(r); | |
kjson_open(&req, r); | |
kjson_obj_open(&req); | |
json_putuser(&req, u, 1); | |
kjson_arrayp_open(&req, "entries"); | |
} | |
json_putentry(&req, &user, &entry, NULL); | |
db_user_unfill(&user); | |
db_entry_unfill(&entry); | |
} | |
if (first) { | |
sendhttp(r, KHTTP_200); | |
kjson_open(&req, r); | |
kjson_obj_open(&req); | |
json_putuser(&req, u, 1); | |
kjson_arrayp_open(&req, "entries"); | |
} | |
ksql_stmt_free(stmt); | |
kjson_array_close(&req); | |
kjson_obj_close(&req); | |
kjson_close(&req); | |
db_meta_unfill(&meta); | |
} | |
static void | |
sendlogin(struct kreq *r) | |
{ | |
int64_t sid, cookie; | |
struct kpair *kpi, *kpp; | |
char buf[64]; | |
struct user *u; | |
const char *secure; | |
if (NULL == (kpi = r->fieldmap[KEY_EMAIL]) || | |
NULL == (kpp = r->fieldmap[KEY_PASS])) { | |
sendhttp(r, KHTTP_400); | |
return; | |
} | |
u = db_user_find(r->arg, | |
kpi->parsed.s, kpp->parsed.s); | |
if (NULL == u) { | |
sendhttp(r, KHTTP_400); | |
return; | |
} else if (USER_DISABLED & u->flags) { | |
kutil_info(r, u->email, | |
"logging in when disabled"); | |
sendhttp(r, KHTTP_400); | |
return; | |
} | |
cookie = arc4random(); | |
sid = db_sess_new(r->arg, cookie, u); | |
kutil_epoch2str | |
(time(NULL) + 60 * 60 * 24 * 365, | |
buf, sizeof(buf)); | |
#ifdef SECURE | |
secure = " secure;"; | |
#else | |
secure = ""; | |
#endif | |
khttp_head(r, kresps[KRESP_STATUS], | |
"%s", khttps[KHTTP_200]); | |
khttp_head(r, kresps[KRESP_SET_COOKIE], | |
"%s=%" PRId64 ";%s HttpOnly; path=/; expires=%s", | |
keys[KEY_SESSCOOKIE].name, cookie, secure, buf); | |
khttp_head(r, kresps[KRESP_SET_COOKIE], | |
"%s=%" PRId64 ";%s HttpOnly; path=/; expires=%s", | |
keys[KEY_SESSID].name, sid, secure, buf); | |
khttp_body(r); | |
db_user_free(u); | |
} | |
static void | |
sendlogout(struct kreq *r) | |
{ | |
const char *secure; | |
char buf[32]; | |
kutil_epoch2str(0, buf, sizeof(buf)); | |
#ifdef SECURE | |
secure = " secure;"; | |
#else | |
secure = ""; | |
#endif | |
sendhttphead(r, KHTTP_200); | |
khttp_head(r, kresps[KRESP_SET_COOKIE], | |
"%s=; path=/;%s HttpOnly; expires=%s", | |
keys[KEY_SESSCOOKIE].name, secure, buf); | |
khttp_head(r, kresps[KRESP_SET_COOKIE], | |
"%s=; path=/;%s HttpOnly; expires=%s", | |
keys[KEY_SESSID].name, secure, buf); | |
khttp_body(r); | |
db_sess_del(r->arg, | |
r->cookiemap[KEY_SESSID]->parsed.i, | |
r->cookiemap[KEY_SESSCOOKIE]->parsed.i); | |
} | |
int | |
main(void) | |
{ | |
struct kreq r; | |
enum kcgi_err er; | |
struct ksql *sql; | |
struct ksqlcfg cfg; | |
struct user *u; | |
/* Log into a separate logfile (not system log). */ | |
kutil_openlog(LOGFILE); | |
/* Configure normal database except with foreign keys. */ | |
memset(&cfg, 0, sizeof(struct ksqlcfg)); | |
cfg.flags = KSQL_EXIT_ON_ERR | | |
KSQL_FOREIGN_KEYS | | |
KSQL_SAFE_EXIT; | |
cfg.err = ksqlitemsg; | |
cfg.dberr = ksqlitedbmsg; | |
/* Actually parse HTTP document. */ | |
er = khttp_parse(&r, keys, KEY__MAX, | |
pages, PAGE__MAX, PAGE_INDEX); | |
if (KCGI_OK != er) { | |
fprintf(stderr, "HTTP parse error: %d\n", er); | |
return(EXIT_FAILURE); | |
} | |
#ifdef __OpenBSD__ | |
if (-1 == pledge("stdio rpath cpath wpath flock fattr", NULL)) { | |
kutil_warn(&r, NULL, "pledge"); | |
khttp_free(&r); | |
return(EXIT_FAILURE); | |
} | |
#endif | |
/* | |
* Front line of defence: make sure we're a proper method, make | |
* sure we're a page, make sure we're a JSON file. | |
*/ | |
if (KMETHOD_GET != r.method && | |
KMETHOD_POST != r.method) { | |
sendhttp(&r, KHTTP_405); | |
khttp_free(&r); | |
return(EXIT_SUCCESS); | |
} | |
if (KMIME_APP_JSON != r.mime) { | |
if ((KMIME_TEXT_HTML == r.mime && | |
PAGE_PUBLIC != r.page) || | |
(KMIME_TEXT_XML == r.mime && | |
PAGE_ATOM != r.page) || | |
(KMIME_TEXT_XML != r.mime && | |
KMIME_TEXT_HTML != r.mime)) { | |
sendhttp(&r, KHTTP_404); | |
khttp_puts(&r, "Page not found."); | |
khttp_free(&r); | |
return(EXIT_SUCCESS); | |
} | |
} else if (PAGE_ATOM == r.page) { | |
sendhttp(&r, KHTTP_404); | |
khttp_puts(&r, "Page not found."); | |
khttp_free(&r); | |
return(EXIT_SUCCESS); | |
} | |
/* Allocate database. */ | |
if (NULL == (sql = ksql_alloc(&cfg))) { | |
sendhttp(&r, KHTTP_500); | |
khttp_free(&r); | |
return(EXIT_SUCCESS); | |
} | |
ksql_open(sql, DATADIR "/dblg.db"); | |
r.arg = sql; | |
/* | |
* Assume we're logging in with a session and grab the session | |
* from the database. | |
* This is our first database access. | |
*/ | |
u = db_user_sess_get(r.arg, | |
NULL != r.cookiemap[KEY_SESSID] ? | |
r.cookiemap[KEY_SESSID]->parsed.i : -1, | |
NULL != r.cookiemap[KEY_SESSCOOKIE] ? | |
r.cookiemap[KEY_SESSCOOKIE]->parsed.i : -1); | |
/* User authorisation. */ | |
if (PAGE_LOGIN != r.page && | |
PAGE_PUBLIC != r.page && | |
PAGE_ATOM != r.page && | |
NULL == u) { | |
sendhttp(&r, KHTTP_403); | |
khttp_free(&r); | |
ksql_free(sql); | |
return(EXIT_SUCCESS); | |
} | |
if ((PAGE_ADD_USER == r.page || | |
PAGE_MOD_META_TITLE == r.page || | |
PAGE_MOD_META_TEMPLATE == r.page || | |
PAGE_MOD_ENABLE == r.page) && | |
(NULL == u || ! (USER_ADMIN & u->flags))) { | |
sendhttp(&r, KHTTP_404); | |
khttp_free(&r); | |
ksql_free(sql); | |
return(EXIT_SUCCESS); | |
} | |
if (PAGE_LOGIN != r.page && NULL != u && | |
USER_DISABLED & u->flags) { | |
kutil_info(&r, u->email, | |
"using site when disabled"); | |
sendhttp(&r, KHTTP_404); | |
khttp_free(&r); | |
ksql_free(sql); | |
return(EXIT_SUCCESS); | |
} | |
switch (r.page) { | |
case (PAGE_ADD_USER): | |
sendadduser(&r, u); | |
break; | |
case (PAGE_ATOM): | |
sendatom(&r); | |
break; | |
case (PAGE_INDEX): | |
sendindex(&r, u); | |
break; | |
case (PAGE_LOGIN): | |
sendlogin(&r); | |
break; | |
case (PAGE_LOGOUT): | |
sendlogout(&r); | |
break; | |
case (PAGE_MOD_CLOUD): | |
sendmodcloud(&r, u); | |
break; | |
case (PAGE_MOD_EMAIL): | |
sendmodemail(&r, u); | |
break; | |
case (PAGE_MOD_ENABLE): | |
sendmodenable(&r, u); | |
break; | |
case (PAGE_MOD_LANG): | |
sendmodlang(&r, u); | |
break; | |
case (PAGE_MOD_LINK): | |
sendmodlink(&r, u); | |
break; | |
case (PAGE_MOD_META_TEMPLATE): | |
sendmodmetatemplate(&r, u); | |
break; | |
case (PAGE_MOD_META_TITLE): | |
sendmodmetatitle(&r, u); | |
break; | |
case (PAGE_MOD_NAME): | |
sendmodname(&r, u); | |
break; | |
case (PAGE_MOD_PASS): | |
sendmodpass(&r, u); | |
break; | |
case (PAGE_PUBLIC): | |
if (KMIME_TEXT_HTML == r.mime) | |
sendpublichtml(&r, u); | |
else | |
sendpublicjson(&r, u); | |
break; | |
case (PAGE_REMOVE): | |
sendremove(&r, u); | |
break; | |
case (PAGE_SUBMIT): | |
sendsubmit(&r, u); | |
break; | |
default: | |
abort(); | |
} | |
khttp_free(&r); | |
ksql_free(sql); | |
return(EXIT_SUCCESS); | |
} |