Implement changing group assignments through the API

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER 2021-01-20 16:36:02 +01:00
parent 7fa2dac90a
commit 5c3a2b509c
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
6 changed files with 434 additions and 208 deletions

View File

@ -14,18 +14,18 @@
#include "routes.h"
#include "../database/gravity-db.h"
static int get_list(struct mg_connection *conn,
const int code,
const enum gravity_list_type listtype,
const char *filter)
static int api_list_read(struct mg_connection *conn,
const int code,
const enum gravity_list_type listtype,
const char *argument)
{
const char *sql_msg = NULL;
if(!gravityDB_readTable(listtype, filter, &sql_msg))
if(!gravityDB_readTable(listtype, argument, &sql_msg))
{
cJSON *json = JSON_NEW_OBJ();
// Add filter (may be NULL = not available)
JSON_OBJ_REF_STR(json, "filter", filter);
// Add argument (may be NULL = not available)
JSON_OBJ_REF_STR(json, "argument", argument);
// Add SQL message (may be NULL = not available)
if (sql_msg != NULL) {
@ -121,8 +121,8 @@ static int get_list(struct mg_connection *conn,
JSON_DELETE(items);
cJSON *json = JSON_NEW_OBJ();
// Add filter (may be NULL = not available)
JSON_OBJ_REF_STR(json, "filter", filter);
// Add argument (may be NULL = not available)
JSON_OBJ_REF_STR(json, "argument", argument);
// Add SQL message (may be NULL = not available)
if (sql_msg != NULL) {
@ -138,134 +138,85 @@ static int get_list(struct mg_connection *conn,
}
}
static int api_list_read(struct mg_connection *conn,
const enum gravity_list_type listtype)
{
// Extract domain from path (option for GET)
const struct mg_request_info *request = mg_get_request_info(conn);
char domain_filter[1024] = { 0 };
// Advance one character to strip "/"
const char *encoded_uri = strrchr(request->local_uri, '/')+1u;
// Decode URL (necessary for regular expressions, harmless for domains)
if(strlen(encoded_uri) != 0 &&
strcmp(encoded_uri, "exact") != 0 &&
strcmp(encoded_uri, "regex") != 0 &&
strcmp(encoded_uri, "allow") != 0 &&
strcmp(encoded_uri, "deny") != 0 &&
strcmp(encoded_uri, "list") != 0 &&
strcmp(encoded_uri, "group") != 0 &&
strcmp(encoded_uri, "adlist") != 0)
mg_url_decode(encoded_uri, strlen(encoded_uri), domain_filter, sizeof(domain_filter), 0);
return get_list(conn, 200, listtype, domain_filter);
}
static int api_list_write(struct mg_connection *conn,
const enum gravity_list_type listtype,
const enum http_method method)
const enum http_method method,
const char *argument)
{
tablerow row;
bool need_domain = false, need_name = false, need_address = false;
switch (listtype)
{
case GRAVITY_GROUPS:
need_name = true;
break;
tablerow row = { 0 };
case GRAVITY_ADLISTS:
need_address = true;
break;
case GRAVITY_DOMAINLIST_ALLOW_EXACT:
case GRAVITY_DOMAINLIST_DENY_EXACT:
case GRAVITY_DOMAINLIST_ALLOW_REGEX:
case GRAVITY_DOMAINLIST_DENY_REGEX:
case GRAVITY_DOMAINLIST_ALLOW_ALL:
case GRAVITY_DOMAINLIST_DENY_ALL:
case GRAVITY_DOMAINLIST_ALL_EXACT:
case GRAVITY_DOMAINLIST_ALL_REGEX:
case GRAVITY_DOMAINLIST_ALL_ALL:
need_domain = true;
break;
}
// Set argument
row.argument = argument;
// Extract payload
char payload[1024] = { 0 };
const char *argument = NULL;
http_get_payload(conn, payload, sizeof(payload));
// Try to extract data from payload
char domain[256] = { 0 };
if(need_domain)
{
if(GET_VAR("domain", domain, payload) < 1)
{
return send_json_error(conn, 400,
"bad_request",
"No \"domain\" string in body data",
NULL);
}
row.domain = domain;
argument = domain;
cJSON *obj = cJSON_Parse(payload);
if (obj == NULL) {
return send_json_error(conn, 400,
"bad_request",
"Invalid request body data",
NULL);
}
char name[256] = { 0 };
if(need_name)
{
if(GET_VAR("name", name, payload) < 1)
{
return send_json_error(conn, 400,
"bad_request",
"No \"name\" string in body data",
NULL);
}
row.name = name;
argument = name;
cJSON *json_enabled = cJSON_GetObjectItemCaseSensitive(obj, "enabled");
if (!cJSON_IsBool(json_enabled)) {
cJSON_Delete(obj);
return send_json_error(conn, 400,
"bad_request",
"No \"enabled\" boolean in body data",
NULL);
}
row.enabled = cJSON_IsTrue(json_enabled);
char address[256] = { 0 };
if(need_address)
{
if(GET_VAR("address", address, payload) < 1)
{
return send_json_error(conn, 400,
"bad_request",
"No \"address\" string in body data",
NULL);
}
row.address = address;
argument = address;
}
row.enabled = true;
get_bool_var(payload, "enabled", &row.enabled);
char comment[256] = { 0 };
if(GET_VAR("comment", comment, payload) > 0)
row.comment = comment;
cJSON *json_comment = cJSON_GetObjectItemCaseSensitive(obj, "comment");
if(cJSON_IsString(json_comment))
row.comment = json_comment->valuestring;
else
row.comment = NULL;
char description[256] = { 0 };
if(GET_VAR("description", description, payload) > 0)
row.description = description;
cJSON *json_description = cJSON_GetObjectItemCaseSensitive(obj, "description");
if(cJSON_IsString(json_description))
row.description = json_description->valuestring;
else
row.description = NULL;
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(obj, "name");
if(cJSON_IsString(json_name))
row.name = json_name->valuestring;
else
row.name = NULL;
cJSON *json_oldtype = cJSON_GetObjectItemCaseSensitive(obj, "oldtype");
if(cJSON_IsString(json_oldtype))
row.oldtype = json_oldtype->valuestring;
else
row.oldtype = NULL;
// Try to add domain to table
const char *sql_msg = NULL;
if(gravityDB_addToTable(listtype, row, &sql_msg, method))
bool okay = false;
if(gravityDB_addToTable(listtype, &row, &sql_msg, method))
{
// Send GET style reply with code 201 Created
return get_list(conn, 201, listtype, argument);
cJSON *groups = cJSON_GetObjectItemCaseSensitive(obj, "groups");
if(groups != NULL)
okay = gravityDB_edit_groups(listtype, groups, &row, &sql_msg);
}
else
if(!okay)
{
// Error adding domain, prepare error object
cJSON *json = JSON_NEW_OBJ();
JSON_OBJ_COPY_STR(json, "argument", argument);
JSON_OBJ_REF_STR(json, "argument", argument);
JSON_OBJ_ADD_BOOL(json, "enabled", row.enabled);
if(row.comment != NULL)
JSON_OBJ_REF_STR(json, "comment", row.comment);
if(row.description != NULL)
JSON_OBJ_REF_STR(json, "description", row.description);
if(row.name != NULL)
JSON_OBJ_REF_STR(json, "name", row.name);
if(row.oldtype != NULL)
JSON_OBJ_REF_STR(json, "oldtype", row.oldtype);
// Add SQL message (may be NULL = not available)
if (sql_msg != NULL) {
@ -274,25 +225,28 @@ static int api_list_write(struct mg_connection *conn,
JSON_OBJ_ADD_NULL(json, "sql_msg");
}
// Free memory not needed any longer
cJSON_Delete(obj);
// Send error reply
return send_json_error(conn, 400, // 400 Bad Request
"database_error",
"Could not add to gravity database",
json);
}
// else: everything is okay
// Free memory not needed any longer
cJSON_Delete(obj);
// Send GET style reply with code 201 Created
return api_list_read(conn, 201, listtype, argument);
}
static int api_list_remove(struct mg_connection *conn,
const enum gravity_list_type listtype)
const enum gravity_list_type listtype,
const char *argument)
{
const struct mg_request_info *request = mg_get_request_info(conn);
char argument[1024] = { 0 };
// Advance one character to strip "/"
const char *encoded_uri = strrchr(request->local_uri, '/')+1u;
// Decode URL (necessary for regular expressions, harmless for domains)
mg_url_decode(encoded_uri, strlen(encoded_uri), argument, sizeof(argument)-1u, 0);
cJSON *json = JSON_NEW_OBJ();
const char *sql_msg = NULL;
if(gravityDB_delFromTable(listtype, argument, &sql_msg))
@ -331,24 +285,25 @@ int api_list(struct mg_connection *conn)
enum gravity_list_type listtype;
bool can_modify = false;
const struct mg_request_info *request = mg_get_request_info(conn);
if(startsWith("/api/group", request->local_uri))
const char *argument = NULL;
if((argument = startsWith("/api/group", request->local_uri)) != NULL)
{
listtype = GRAVITY_GROUPS;
can_modify = true;
}
else if(startsWith("/api/adlist", request->local_uri))
else if((argument = startsWith("/api/adlist", request->local_uri)) != NULL)
{
listtype = GRAVITY_ADLISTS;
can_modify = true;
}
else if(startsWith("/api/list/allow", request->local_uri))
else if((argument = startsWith("/api/list/allow", request->local_uri)) != NULL)
{
if(startsWith("/api/list/allow/exact", request->local_uri))
if((argument = startsWith("/api/list/allow/exact", request->local_uri)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALLOW_EXACT;
can_modify = true;
}
else if(startsWith("/api/list/allow/regex", request->local_uri))
else if((argument = startsWith("/api/list/allow/regex", request->local_uri)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_ALLOW_REGEX;
can_modify = true;
@ -356,14 +311,14 @@ int api_list(struct mg_connection *conn)
else
listtype = GRAVITY_DOMAINLIST_ALLOW_ALL;
}
else if(startsWith("/api/list/deny", request->local_uri))
else if((argument = startsWith("/api/list/deny", request->local_uri)) != NULL)
{
if(startsWith("/api/list/deny/exact", request->local_uri))
if((argument = startsWith("/api/list/deny/exact", request->local_uri)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_DENY_EXACT;
can_modify = true;
}
else if(startsWith("/api/list/deny/regex", request->local_uri))
else if((argument = startsWith("/api/list/deny/regex", request->local_uri)) != NULL)
{
listtype = GRAVITY_DOMAINLIST_DENY_REGEX;
can_modify = true;
@ -373,28 +328,31 @@ int api_list(struct mg_connection *conn)
}
else
{
if(startsWith("/api/list/exact", request->local_uri))
if((argument = startsWith("/api/list/exact", request->local_uri)) != NULL)
listtype = GRAVITY_DOMAINLIST_ALL_EXACT;
else if(startsWith("/api/list/regex", request->local_uri))
else if((argument = startsWith("/api/list/regex", request->local_uri)) != NULL)
listtype = GRAVITY_DOMAINLIST_ALL_REGEX;
else
{
argument = startsWith("/api/list", request->local_uri);
listtype = GRAVITY_DOMAINLIST_ALL_ALL;
}
}
const enum http_method method = http_method(conn);
if(method == HTTP_GET)
{
return api_list_read(conn, listtype);
return api_list_read(conn, 200, listtype, argument);
}
else if(can_modify && (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH))
{
// Add item from list
return api_list_write(conn, listtype, method);
return api_list_write(conn, listtype, method, argument);
}
else if(can_modify && method == HTTP_DELETE)
{
// Delete item from list
return api_list_remove(conn, listtype);
return api_list_remove(conn, listtype, argument);
}
else if(!can_modify)
{

View File

@ -1364,7 +1364,7 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int n
return true;
}
bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow row,
bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row,
const char **message, const enum http_method method)
{
if(gravity_db == NULL)
@ -1391,7 +1391,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow
case GRAVITY_GROUPS:
case GRAVITY_ADLISTS:
// No type required for this table
case GRAVITY_CLIENTS:
// No type required for these tables
break;
// Aggregate types cannot be handled by this routine
@ -1403,6 +1404,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow
default:
return false;
}
row->type_int = type;
// Prepare SQLite statement
sqlite3_stmt* stmt = NULL;
@ -1410,58 +1412,46 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow
if(method == HTTP_POST) // Create NEW entry, error if existing
{
if(listtype == GRAVITY_GROUPS)
querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:name,:enabled,:description);";
querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:argument,:enabled,:description);";
else if(listtype == GRAVITY_ADLISTS)
querystr = "INSERT INTO adlist (address,enabled,description) VALUES (:address,:enabled,:description);";
querystr = "INSERT INTO adlist (address,enabled,description) VALUES (:argument,:enabled,:description);";
else // domainlist
querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:domain,:type,:enabled,:comment);";
querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:argument,:type,:enabled,:comment);";
}
else // Create new or replace existing entry, no error if existing
// We have to use a subquery here to avoid violating FOREIGN KEY
// contraints (REPLACE recreates (= new ID) entries instead of updating them)
if(listtype == GRAVITY_GROUPS)
querystr = "REPLACE INTO \"group\" (name,enabled,description,id,date_added) "
"VALUES (:name,:enabled,:description,"
"(SELECT id FROM \"group\" WHERE name = :name),"
"(SELECT date_added FROM \"group\" WHERE name = :name));";
"VALUES (:argument,:enabled,:description,"
"(SELECT id FROM \"group\" WHERE name = :argument),"
"(SELECT date_added FROM \"group\" WHERE name = :argument));";
else if(listtype == GRAVITY_ADLISTS)
querystr = "REPLACE INTO adlist (address,enabled,description,id,date_added) "
"VALUES (:address,:enabled,:description,"
"(SELECT id FROM adlist WHERE address = :address),"
"(SELECT date_added FROM adlist WHERE address = :address));";
"VALUES (:argument,:enabled,:description,"
"(SELECT id FROM adlist WHERE address = :argument),"
"(SELECT date_added FROM adlist WHERE address = :argument));";
else // domainlist
querystr = "REPLACE INTO domainlist (domain,type,enabled,comment,id,date_added) "
"VALUES (:domain,:type,:enabled,:comment,"
"(SELECT id FROM domainlist WHERE domain = :domain and type = :type),"
"(SELECT date_added FROM domainlist WHERE domain = :domain and type = :type));";
"VALUES (:argument,:type,:enabled,:comment,"
"(SELECT id FROM domainlist WHERE domain = :argument and type = :oldtype),"
"(SELECT date_added FROM domainlist WHERE domain = :argument and type = :oldtype));";
int rc = sqlite3_prepare_v2(gravity_db, querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK )
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s) - SQL error prepare (%i): %s",
type, row.domain, row.name, rc, *message);
logg("gravityDB_addToTable(%d, %s) - SQL error prepare (%i): %s",
type, row->domain, rc, *message);
return false;
}
// Bind domain to prepared statement (if requested)
int idx = sqlite3_bind_parameter_index(stmt, ":domain");
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.domain, -1, SQLITE_STATIC)) != SQLITE_OK)
// Bind argument to prepared statement (if requested)
int idx = sqlite3_bind_parameter_index(stmt, ":argument");
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->argument, -1, SQLITE_STATIC)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s): Failed to bind domain (error %d) - %s",
type, row.domain, row.name, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Bind name to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":name");
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.name, -1, SQLITE_STATIC)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s): Failed to bind name (error %d) - %s",
type, row.domain, row.name, rc, *message);
logg("gravityDB_addToTable(%d, %s): Failed to bind argument (error %d) - %s",
type, row->argument, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
@ -1472,20 +1462,62 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow
if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, type)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s): Failed to bind type (error %d) - %s",
type, row.domain, row.name, rc, *message);
logg("gravityDB_addToTable(%d, %s): Failed to bind type (error %d) - %s",
type, row->domain, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Bind oldtype to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":oldtype");
if(idx > 0)
{
if(row->oldtype == NULL)
{
*message = "Field oldtype missing from request.";
logg("gravityDB_addToTable(%d, %s): Oldtype missing",
type, row->domain);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
int oldtype = -1;
if(strcasecmp("allow/exact", row->oldtype) == 0)
oldtype = 0;
else if(strcasecmp("deny/exact", row->oldtype) == 0)
oldtype = 1;
else if(strcasecmp("allow/regex", row->oldtype) == 0)
oldtype = 2;
else if(strcasecmp("deny/regex", row->oldtype) == 0)
oldtype = 3;
else
{
*message = "Cannot interpret oldtype field.";
logg("gravityDB_addToTable(%d, %s): Failed to identify oldtype \"%s\"",
type, row->domain, row->oldtype);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
if((rc = sqlite3_bind_int(stmt, idx, oldtype)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s): Failed to bind oldtype (error %d) - %s",
type, row->domain, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
}
// Bind enabled boolean to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":enabled");
if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row.enabled ? 1 : 0)) != SQLITE_OK)
if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row->enabled ? 1 : 0)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s): Failed to bind enabled (error %d) - %s",
type, row.domain, row.name, rc, *message);
logg("gravityDB_addToTable(%d, %s): Failed to bind enabled (error %d) - %s",
type, row->domain, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
@ -1493,11 +1525,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow
// Bind comment string to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":comment");
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.comment, -1, SQLITE_STATIC)) != SQLITE_OK)
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->comment, -1, SQLITE_STATIC)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s): Failed to bind comment (error %d) - %s",
type, row.domain, row.name, rc, *message);
logg("gravityDB_addToTable(%d, %s): Failed to bind comment (error %d) - %s",
type, row->domain, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
@ -1505,23 +1537,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow
// Bind description string to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":description");
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.description, -1, SQLITE_STATIC)) != SQLITE_OK)
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->description, -1, SQLITE_STATIC)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s): Failed to bind description (error %d) - %s",
type, row.domain, row.name, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Bind address string to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":address");
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row.address, -1, SQLITE_STATIC)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_addToTable(%d, %s, %s): Failed to bind address (error %d) - %s",
type, row.domain, row.name, rc, *message);
logg("gravityDB_addToTable(%d, %s): Failed to bind description (error %d) - %s",
type, row->domain, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
@ -1572,7 +1592,8 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a
case GRAVITY_GROUPS:
case GRAVITY_ADLISTS:
// No type required for this table
case GRAVITY_CLIENTS:
// No type required for these tables
break;
// Aggregate types cannot be handled by this routine
@ -1627,6 +1648,14 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a
return false;
}
// Debug output
if(config.debug & DEBUG_API)
{
logg("SQL: %s", querystr);
logg(" :argument = \"%s\"", argument);
logg(" :type = \"%i\"", type);
}
// Perform step
bool okay = false;
if((rc = sqlite3_step(stmt)) == SQLITE_DONE)
@ -1647,7 +1676,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* a
}
static sqlite3_stmt* read_stmt = NULL;
bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filter, const char **message)
bool gravityDB_readTable(const enum gravity_list_type listtype, const char *argument, const char **message)
{
if(gravity_db == NULL)
{
@ -1688,7 +1717,8 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt
break;
case GRAVITY_GROUPS:
case GRAVITY_ADLISTS:
// No type required for this table
case GRAVITY_CLIENTS:
// No type required for these tables
break;
}
@ -1699,20 +1729,20 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt
const char *extra = "";
if(listtype == GRAVITY_GROUPS)
{
if(filter[0] != '\0')
extra = " WHERE name = :filter;";
if(argument != NULL && argument[0] != '\0')
extra = " WHERE name = :argument;";
sprintf(querystr, "SELECT id,name,enabled,date_added,date_modified,description FROM \"group\"%s;", extra);
}
else if(listtype == GRAVITY_ADLISTS)
{
if(filter[0] != '\0')
extra = " WHERE address = :filter;";
if(argument != NULL && argument[0] != '\0')
extra = " WHERE address = :argument;";
sprintf(querystr, "SELECT id,address,enabled,date_added,date_modified,comment FROM adlist%s;", extra);
}
else // domainlist
{
if(filter[0] != '\0')
extra = " AND domain = :filter;";
if(argument != NULL && argument[0] != '\0')
extra = " AND domain = :argument;";
sprintf(querystr, "SELECT id,type,domain,enabled,date_added,date_modified,comment,"
"(SELECT GROUP_CONCAT(group_id) FROM domainlist_by_group g WHERE g.domainlist_id = d.id) AS group_ids "
"FROM domainlist d WHERE d.type IN (%s)%s;", type, extra);
@ -1727,18 +1757,25 @@ bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filt
return false;
}
// Bind filter to prepared statement (if requested)
int idx = sqlite3_bind_parameter_index(read_stmt, "filter");
if(idx > 0 && (rc = sqlite3_bind_text(read_stmt, idx, filter, -1, SQLITE_STATIC)) != SQLITE_OK)
// Bind argument to prepared statement (if requested)
int idx = sqlite3_bind_parameter_index(read_stmt, ":argument");
if(idx > 0 && (rc = sqlite3_bind_text(read_stmt, idx, argument, -1, SQLITE_STATIC)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_readTable(%d => (%s), %s): Failed to bind filter (error %d) - %s",
listtype, type, filter, rc, *message);
logg("gravityDB_readTable(%d => (%s), %s): Failed to bind argument (error %d) - %s",
listtype, type, argument, rc, *message);
sqlite3_reset(read_stmt);
sqlite3_finalize(read_stmt);
return false;
}
// Debug output
if(config.debug & DEBUG_API)
{
logg("SQL: %s", querystr);
logg(" :argument = \"%s\"", argument);
}
return true;
}
@ -1832,3 +1869,220 @@ void gravityDB_readTableFinalize(void)
// Finalize statement
sqlite3_finalize(read_stmt);
}
bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups,
const tablerow *row, const char **message)
{
if(gravity_db == NULL)
{
*message = "Database not available";
return false;
}
// Prepare SQLite statements
const char *get_querystr, *del_querystr, *add_querystr;
if(listtype == GRAVITY_GROUPS)
return false;
else if(listtype == GRAVITY_CLIENTS)
{
get_querystr = "SELECT id FROM client WHERE name = :argument";
del_querystr = "DELETE FROM client_by_group WHERE client_id = :id);";
add_querystr = "INSERT INTO client_by_group (client_id,group_id) VALUES (:id,:gid);";
}
else if(listtype == GRAVITY_ADLISTS)
{
get_querystr = "SELECT id FROM adlist WHERE address = :argument";
del_querystr = "DELETE FROM adlist_by_group WHERE adlist_id = :id;";
add_querystr = "INSERT INTO adlist_by_group (adlist_id,group_id) VALUES (:id,:gid);";
}
else // domainlist
{
get_querystr = "SELECT id FROM domainlist WHERE domain = :argument AND type = :type";
del_querystr = "DELETE FROM domainlist_by_group WHERE domainlist_id = :id;";
add_querystr = "INSERT INTO domainlist_by_group (domainlist_id,group_id) VALUES (:id,:gid);";
}
// First step: Get ID of the item to modify
sqlite3_stmt* stmt = NULL;
int rc = sqlite3_prepare_v2(gravity_db, get_querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK )
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d) - SQL error prepare SELECT (%i): %s",
listtype, rc, *message);
return false;
}
// Bind argument string to prepared statement (if requested)
int idx = sqlite3_bind_parameter_index(stmt, ":argument");
if(idx > 0 && (rc = sqlite3_bind_text(stmt, idx, row->argument, -1, SQLITE_STATIC)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d): Failed to bind argument SELECT (error %d) - %s",
listtype, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Bind type to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":type");
if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, row->type_int)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d): Failed to bind type SELECT (error %d) - %s",
listtype, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Perform step
bool okay = false;
int id = -1;
if((rc = sqlite3_step(stmt)) == SQLITE_ROW)
{
// Get ID of domain
id = sqlite3_column_int(stmt, 0);
okay = true;
}
else
{
*message = sqlite3_errmsg(gravity_db);
}
logg("SELECT: %i -> %i", rc, id);
// Debug output
if(config.debug & DEBUG_API)
{
logg("SQL: %s", get_querystr);
logg(" :argument = \"%s\"", row->argument);
logg(" :type = \"%d\"", row->type_int);
}
// Finalize statement
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
// Return early if getting the ID failed
if(!okay)
return false;
// Second step: Delete all existing group associations for this item
rc = sqlite3_prepare_v2(gravity_db, del_querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK )
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d) - SQL error prepare DELETE (%i): %s",
listtype, rc, *message);
return false;
}
// Bind id to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":id");
if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, id)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d): Failed to bind id DELETE (error %d) - %s",
listtype, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Perform step
if((rc = sqlite3_step(stmt)) == SQLITE_DONE)
{
// All groups deleted
}
else
{
okay = false;
*message = sqlite3_errmsg(gravity_db);
}
logg("DELETE: %i", rc);
// Debug output
if(config.debug & DEBUG_API)
{
logg("SQL: %s", del_querystr);
logg(" :id = \"%d\"", id);
}
// Finalize statement
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
// Return early if deleting the existing group associations failed
if(!okay)
return false;
// Third step: Create new group associations for this item
rc = sqlite3_prepare_v2(gravity_db, add_querystr, -1, &stmt, NULL);
if( rc != SQLITE_OK )
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d) - SQL error prepare INSERT (%i): %s",
listtype, rc, *message);
return false;
}
// Bind id to prepared statement (if requested)
idx = sqlite3_bind_parameter_index(stmt, ":id");
if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, id)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d): Failed to bind id INSERT (error %d) - %s",
listtype, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Loop over all loops in array
const int groupcount = cJSON_GetArraySize(groups);
logg("groupscount = %d", groupcount);
for(int i = 0; i < groupcount; i++)
{
cJSON *group = cJSON_GetArrayItem(groups, i);
if(group == NULL || !cJSON_IsNumber(group))
continue;
idx = sqlite3_bind_parameter_index(stmt, ":gid");
if(idx > 0 && (rc = sqlite3_bind_int(stmt, idx, group->valueint)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
logg("gravityDB_edit_groups(%d): Failed to bind gid INSERT (error %d) - %s",
listtype, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
return false;
}
// Perform step
if((rc = sqlite3_step(stmt)) != SQLITE_DONE)
{
okay = false;
*message = sqlite3_errmsg(gravity_db);
break;
}
logg("INSERT: %i -> (%i,%i)", rc, id, group->valueint);
// Debug output
if(config.debug & DEBUG_API)
{
logg("SQL: %s", add_querystr);
logg(" :id = \"%d\"", id);
logg(" :gid = \"%d\"", group->valueint);
}
// Reset before next iteration, this will not clear the id binding
sqlite3_reset(stmt);
}
// Finalize statement
sqlite3_finalize(stmt);
return okay;
}

View File

@ -28,6 +28,8 @@ typedef struct {
const char *comment;
const char *group_ids;
const char *description;
const char *argument;
const char *oldtype;
long id;
time_t date_added;
time_t date_modified;
@ -56,8 +58,10 @@ bool gravityDB_get_regex_client_groups(clientsData* client, const unsigned int n
bool gravityDB_readTable(const enum gravity_list_type listtype, const char *filter, const char **message);
bool gravityDB_readTableGetRow(tablerow *row, const char **message);
void gravityDB_readTableFinalize(void);
bool gravityDB_addToTable(const enum gravity_list_type listtype, const tablerow row,
bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row,
const char **message, const enum http_method method);
bool gravityDB_delFromTable(const enum gravity_list_type listtype, const char* domain_name, const char **message);
bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups,
const tablerow *row, const char **message);
#endif //GRAVITY_H

View File

@ -167,7 +167,8 @@ enum gravity_list_type {
GRAVITY_DOMAINLIST_ALL_REGEX,
GRAVITY_DOMAINLIST_ALL_ALL,
GRAVITY_GROUPS,
GRAVITY_ADLISTS
GRAVITY_ADLISTS,
GRAVITY_CLIENTS
} __attribute__ ((packed));
enum gravity_tables {

View File

@ -164,9 +164,18 @@ bool http_get_payload(struct mg_connection *conn, char *payload, const size_t si
return false;
}
bool __attribute__((pure)) startsWith(const char *path, const char *uri)
const char* __attribute__((pure)) startsWith(const char *path, const char *uri)
{
return strncmp(path, uri, strlen(path)) == 0;
if(strncmp(path, uri, strlen(path)) == 0)
if(uri[strlen(path)] == '/')
// Path match with argument after ".../"
return uri + strlen(path) + 1u;
else
// Path match without argument
return "";
else
// Path does not match
return NULL;
}
bool http_get_cookie_int(struct mg_connection *conn, const char *cookieName, int *i)

View File

@ -49,6 +49,6 @@ enum http_method { HTTP_UNKNOWN, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP
int http_method(struct mg_connection *conn);
// Utils
bool startsWith(const char *path, const char *uri) __attribute__((pure));
const char *startsWith(const char *path, const char *uri) __attribute__((pure));
#endif // HTTP_H