Allow narrowing down the (ad)list type using the (optional) query parameter ?type={allow,block}

Signed-off-by: DL6ER <dl6er@dl6er.de>
This commit is contained in:
DL6ER 2024-02-13 06:43:11 +01:00
parent 9e3ccd917d
commit 7a919cbf59
No known key found for this signature in database
GPG Key ID: 00135ACBD90B28DD
5 changed files with 164 additions and 36 deletions

View File

@ -5,6 +5,7 @@ components:
summary: Modify list
parameters:
- $ref: 'lists.yaml#/components/parameters/list'
- $ref: 'lists.yaml#/components/parameters/listtype'
get:
summary: Get lists
tags:
@ -449,3 +450,14 @@ components:
required: true
description: Address of the list
example: https://hosts-file.net/ad_servers.txt
listtype:
in: query
name: type
schema:
type: string
enum:
- "allow"
- "block"
required: false
description: Type of list, optional
example: block

View File

@ -36,7 +36,7 @@ static int api_list_read(struct ftl_conn *api,
sql_msg);
}
tablerow table;
tablerow table = { 0 };
cJSON *rows = JSON_NEW_ARRAY();
while(gravityDB_readTableGetRow(listtype, &table, &sql_msg))
{
@ -48,7 +48,9 @@ static int api_list_read(struct ftl_conn *api,
JSON_COPY_STR_TO_OBJECT(row, "name", table.name);
JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment);
}
else if(listtype == GRAVITY_ADLISTS)
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
JSON_COPY_STR_TO_OBJECT(row, "address", table.address);
JSON_COPY_STR_TO_OBJECT(row, "comment", table.comment);
@ -126,7 +128,9 @@ static int api_list_read(struct ftl_conn *api,
JSON_ADD_NUMBER_TO_OBJECT(row, "date_modified", table.date_modified);
// Properties added in https://github.com/pi-hole/pi-hole/pull/3951
if(listtype == GRAVITY_ADLISTS)
if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
JSON_REF_STR_IN_OBJECT(row, "type", table.type);
JSON_ADD_NUMBER_TO_OBJECT(row, "date_updated", table.date_updated);
@ -147,7 +151,9 @@ static int api_list_read(struct ftl_conn *api,
cJSON *json = JSON_NEW_OBJECT();
if(listtype == GRAVITY_GROUPS)
objname = "groups";
else if(listtype == GRAVITY_ADLISTS)
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
objname = "lists";
else if(listtype == GRAVITY_CLIENTS)
objname = "clients";
@ -268,6 +274,8 @@ static int api_list_write(struct ftl_conn *api,
}
case GRAVITY_ADLISTS:
case GRAVITY_ADLISTS_BLOCK:
case GRAVITY_ADLISTS_ALLOW:
{
cJSON *json_address = cJSON_GetObjectItemCaseSensitive(api->payload.json, "address");
if(cJSON_IsString(json_address) && strlen(json_address->valuestring) > 0)
@ -331,6 +339,10 @@ static int api_list_write(struct ftl_conn *api,
NULL);
}
}
else if(listtype == GRAVITY_ADLISTS_BLOCK)
row.type_int = ADLIST_BLOCK;
else if(listtype == GRAVITY_ADLISTS_ALLOW)
row.type_int = ADLIST_ALLOW;
else
{
cJSON *json_type = cJSON_GetObjectItemCaseSensitive(api->payload.json, "type");
@ -552,7 +564,9 @@ static int api_list_remove(struct ftl_conn *api,
if(listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT ||
listtype == GRAVITY_DOMAINLIST_DENY_EXACT ||
listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX ||
listtype == GRAVITY_DOMAINLIST_DENY_REGEX)
listtype == GRAVITY_DOMAINLIST_DENY_REGEX ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
int type = -1;
switch (listtype)
@ -568,12 +582,17 @@ static int api_list_remove(struct ftl_conn *api,
break;
case GRAVITY_DOMAINLIST_DENY_REGEX:
type = 3;
break;
case GRAVITY_ADLISTS_BLOCK:
type = ADLIST_BLOCK;
break;
case GRAVITY_ADLISTS_ALLOW:
type = ADLIST_ALLOW;
break;
// Not handled herein
case GRAVITY_GROUPS:
case GRAVITY_ADLISTS:
case GRAVITY_CLIENTS:
// No type required for these tables
break;
// Aggregate types cannot be handled by this routine
case GRAVITY_GRAVITY:
case GRAVITY_ANTIGRAVITY:
case GRAVITY_DOMAINLIST_ALLOW_ALL:
@ -582,7 +601,7 @@ static int api_list_remove(struct ftl_conn *api,
case GRAVITY_DOMAINLIST_ALL_REGEX:
case GRAVITY_DOMAINLIST_ALL_ALL:
default:
return false;
break;
}
// Create new JSON array with the item and type:
@ -827,6 +846,30 @@ int api_list(struct ftl_conn *api)
api->request->local_uri_raw);
}
// If this is a request for a list, we check if there is a request
// parameter narrowing down which kind of list. If so, we modify the
// list type accordingly
if(listtype == GRAVITY_ADLISTS && api->request->query_string != NULL)
{
// Check if there is a type parameter
char typestr[16] = { 0 };
if(get_string_var(api->request->query_string, "type", typestr, sizeof(typestr)) > 0)
{
if(strcasecmp(typestr, "allow") == 0)
listtype = GRAVITY_ADLISTS_ALLOW;
else if(strcasecmp(typestr, "block") == 0)
listtype = GRAVITY_ADLISTS_BLOCK;
else
{
// Invalid type parameter
return send_json_error(api, 400,
"bad_request",
"Invalid request: Invalid type parameter (should be either \"allow\" or \"block\")",
api->request->query_string);
}
}
}
if(api->method == HTTP_GET)
{
// Read list item identified by URI (or read them all)

View File

@ -1575,6 +1575,8 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row,
// Nothing to be done for these tables
case GRAVITY_GROUPS:
case GRAVITY_ADLISTS:
case GRAVITY_ADLISTS_BLOCK:
case GRAVITY_ADLISTS_ALLOW:
case GRAVITY_CLIENTS:
break;
@ -1597,7 +1599,9 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row,
{
querystr = "INSERT INTO \"group\" (name,enabled,description) VALUES (:item,:enabled,:comment);";
}
else if(listtype == GRAVITY_ADLISTS)
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
querystr = "INSERT INTO adlist (address,enabled,comment,type) VALUES (:item,:enabled,:comment,:type);";
}
@ -1605,7 +1609,7 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row,
{
querystr = "INSERT INTO client (ip,comment) VALUES (:item,:comment);";
}
else // domainlis
else // domainlist
{
querystr = "INSERT INTO domainlist (domain,type,enabled,comment) VALUES (:item,:type,:enabled,:comment);";
}
@ -1625,9 +1629,11 @@ bool gravityDB_addToTable(const enum gravity_list_type listtype, tablerow *row,
querystr = "UPDATE \"group\" SET name = :name, enabled = :enabled, description = :comment "
"WHERE name = :item";
}
else if(listtype == GRAVITY_ADLISTS)
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
querystr = "INSERT INTO adlist (address,enabled,comment,type) VALUES (:item,:enabled,:comment,:type) "\
"ON CONFLICT(address) DO UPDATE SET enabled = :enabled, comment = :comment, type = :type;";
"ON CONFLICT(address,type) DO UPDATE SET enabled = :enabled, comment = :comment, type = :type;";
else if(listtype == GRAVITY_CLIENTS)
querystr = "INSERT INTO client (ip,comment) VALUES (:item,:comment) "\
"ON CONFLICT(ip) DO UPDATE SET comment = :comment;";
@ -1825,11 +1831,14 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
return false;
}
const bool isDomain = listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT ||
listtype == GRAVITY_DOMAINLIST_DENY_EXACT ||
listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX ||
listtype == GRAVITY_DOMAINLIST_DENY_REGEX ||
listtype == GRAVITY_DOMAINLIST_ALL_ALL; // batch delete
const bool hasType = listtype == GRAVITY_DOMAINLIST_ALLOW_EXACT ||
listtype == GRAVITY_DOMAINLIST_DENY_EXACT ||
listtype == GRAVITY_DOMAINLIST_ALLOW_REGEX ||
listtype == GRAVITY_DOMAINLIST_DENY_REGEX ||
listtype == GRAVITY_DOMAINLIST_ALL_ALL ||
listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW;
// Begin transaction
const char *querystr = "BEGIN TRANSACTION;";
@ -1843,7 +1852,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
}
// Create temporary table for JSON argument
if(isDomain)
if(hasType)
// Create temporary table for domains to be deleted
querystr = "CREATE TEMPORARY TABLE deltable (type INT, item TEXT);";
else
@ -1885,7 +1894,7 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
sqlite3_finalize(stmt);
// Prepare statement for inserting items into virtual table
if(isDomain)
if(hasType)
querystr = "INSERT INTO deltable (type, item) VALUES (:type, :item);";
else
querystr = "INSERT INTO deltable (item) VALUES (:item);";
@ -1910,12 +1919,24 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
{
// Bind type to prepared statement
cJSON *type = cJSON_GetObjectItemCaseSensitive(it, "type");
int type_int = cJSON_IsNumber(type) ? type->valueint : -1;
if(listtype == GRAVITY_ADLISTS_BLOCK)
type_int = ADLIST_BLOCK;
else if(listtype == GRAVITY_ADLISTS_ALLOW)
type_int = ADLIST_ALLOW;
else if(listtype == GRAVITY_ADLISTS && cJSON_IsString(type))
{
if(strcasecmp(type->valuestring, "block") == 0)
type_int = ADLIST_BLOCK;
else if(strcasecmp(type->valuestring, "allow") == 0)
type_int = ADLIST_ALLOW;
}
const int type_idx = sqlite3_bind_parameter_index(stmt, ":type");
if(type_idx > 0 && (!cJSON_IsNumber(type) || (rc = sqlite3_bind_int(stmt, type_idx, type->valueint)) != SQLITE_OK))
if(type_idx > 0 && (rc = sqlite3_bind_int(stmt, type_idx, type_int)) != SQLITE_OK)
{
*message = sqlite3_errmsg(gravity_db);
log_err("gravityDB_delFromTable(%d): Failed to bind type (error %d) - %s",
type->valueint, rc, *message);
type_int, rc, *message);
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
@ -1981,12 +2002,15 @@ bool gravityDB_delFromTable(const enum gravity_list_type listtype, const cJSON*
const char *querystrs[4] = {NULL, NULL, NULL, NULL};
if(listtype == GRAVITY_GROUPS)
querystrs[0] = "DELETE FROM \"group\" WHERE name IN (SELECT item FROM deltable);";
else if(listtype == GRAVITY_ADLISTS)
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
// This is actually a three-step deletion to satisfy foreign-key constraints
querystrs[0] = "DELETE FROM gravity WHERE adlist_id IN (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable));";
querystrs[1] = "DELETE FROM antigravity WHERE adlist_id IN (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable));";
querystrs[2] = "DELETE FROM adlist WHERE address IN (SELECT item FROM deltable);";
// This is actually a four-step deletion to satisfy foreign-key constraints
querystrs[0] = "DELETE FROM gravity WHERE adlist_id IN (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable WHERE type = 0));";
querystrs[1] = "DELETE FROM antigravity WHERE adlist_id IN (SELECT id FROM adlist WHERE address IN (SELECT item FROM deltable WHERE type = 1));";
querystrs[2] = "DELETE FROM adlist WHERE address IN (SELECT item FROM deltable WHERE type = 0) AND type = 0;";
querystrs[3] = "DELETE FROM adlist WHERE address IN (SELECT item FROM deltable WHERE type = 1) AND type = 1;";
}
else if(listtype == GRAVITY_CLIENTS)
querystrs[0] = "DELETE FROM client WHERE ip IN (SELECT item FROM deltable);";
@ -2096,12 +2120,16 @@ bool gravityDB_readTable(const enum gravity_list_type listtype,
case GRAVITY_DOMAINLIST_ALL_ALL:
type = "0,1,2,3";
break;
// No type required for these tables
case GRAVITY_GRAVITY:
case GRAVITY_ANTIGRAVITY:
case GRAVITY_GROUPS:
case GRAVITY_ADLISTS:
case GRAVITY_CLIENTS:
// No type required for these tables
case GRAVITY_ADLISTS:
// Type is set in the SQL query directly
case GRAVITY_ADLISTS_BLOCK:
case GRAVITY_ADLISTS_ALLOW:
break;
}
@ -2133,19 +2161,29 @@ bool gravityDB_readTable(const enum gravity_list_type listtype,
}
snprintf(querystr, buflen, "SELECT id,name,enabled,date_added,date_modified,description AS comment FROM \"group\"%s;", filter);
}
else if(listtype == GRAVITY_ADLISTS)
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
if(listtype == GRAVITY_ADLISTS_BLOCK)
filter = "type = 0";
else if(listtype == GRAVITY_ADLISTS_ALLOW)
filter = "type = 1";
else
filter = "TRUE";
const char *filter2 = "";
if(item != NULL && item[0] != '\0')
{
if(exact)
filter = " WHERE address = :item";
filter2 = " AND address = :item";
else
filter = " WHERE address LIKE :item";
filter2 = " AND address LIKE :item";
}
snprintf(querystr, buflen, "SELECT id,type,address,enabled,date_added,date_modified,comment,"
"(SELECT GROUP_CONCAT(group_id) FROM adlist_by_group g WHERE g.adlist_id = a.id) AS group_ids,"
"date_updated,number,invalid_domains,status,abp_entries "
"FROM adlist a%s;", filter);
"FROM adlist a WHERE %s%s;", filter, filter2);
}
else if(listtype == GRAVITY_CLIENTS)
{
@ -2312,6 +2350,8 @@ bool gravityDB_readTableGetRow(const enum gravity_list_type listtype, tablerow *
}
}
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_ALLOW ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_GRAVITY ||
listtype == GRAVITY_ANTIGRAVITY)
{
@ -2328,6 +2368,10 @@ bool gravityDB_readTableGetRow(const enum gravity_list_type listtype, tablerow *
break;
}
}
else
{
row->type = "unknown";
}
}
else if(strcasecmp(cname, "domain") == 0)
@ -2425,9 +2469,14 @@ bool gravityDB_edit_groups(const enum gravity_list_type listtype, cJSON *groups,
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)
else if(listtype == GRAVITY_ADLISTS ||
listtype == GRAVITY_ADLISTS_BLOCK ||
listtype == GRAVITY_ADLISTS_ALLOW)
{
get_querystr = "SELECT id FROM adlist WHERE address = :item";
if(listtype == GRAVITY_ADLISTS)
get_querystr = "SELECT id FROM adlist WHERE address = :item";
else
get_querystr = "SELECT id FROM adlist WHERE address = :item AND type = :type";
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);";
}

View File

@ -189,7 +189,9 @@ enum gravity_list_type {
GRAVITY_ADLISTS,
GRAVITY_CLIENTS,
GRAVITY_GRAVITY,
GRAVITY_ANTIGRAVITY
GRAVITY_ANTIGRAVITY,
GRAVITY_ADLISTS_BLOCK,
GRAVITY_ADLISTS_ALLOW
} __attribute__ ((packed));
enum gravity_tables {

View File

@ -1420,6 +1420,28 @@
[[ ${lines[0]} == "145" ]]
}
@test "Check /api/lists?type=block returning only blocking lists" {
run bash -c 'curl -s 127.0.0.1/api/lists?type=block | jq ".lists[].type"'
printf "%s\n" "${lines[@]}"
# Check no allow entries are present
[[ ${lines[@]} != *"allow"* ]]
}
@test "Check /api/lists?type=allow returning only allowing lists" {
run bash -c 'curl -s 127.0.0.1/api/lists?type=allow | jq ".lists[].type"'
printf "%s\n" "${lines[@]}"
# Check no block entries are present
[[ ${lines[@]} != *"block"* ]]
}
@test "Check /api/lists without type parameter returning all lists" {
run bash -c 'curl -s 127.0.0.1/api/lists | jq ".lists[].type"'
printf "%s\n" "${lines[@]}"
# Check both block and allow entries are present
[[ ${lines[@]} == *"allow"* ]]
[[ ${lines[@]} == *"block"* ]]
}
@test "API authorization (without password): No login required" {
run bash -c 'curl -s 127.0.0.1/api/auth'
printf "%s\n" "${lines[@]}"