| /*
* Sai web - ./src/web/w-visible.c
*
* Copyright (C) 2019 - 2026 Andy Green <andy@warmcat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation:
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* Which projects a vhost shows
*
* A sai-web vhost can be given a "projects" pvo listing the projects (repo
* names) it shows, so the events of one sai-server can be offered as different
* canned views, eg, on differently-named unix sockets for the front-end proxy
* to mount. Without the pvo, the vhost shows every project.
*
* The list is kept twice from the one parse: as a list for checks done in C,
* and as a TEMP table on the vhd's own connection to the events db, so the sql
* listing events can be scoped by a fixed fragment. TEMP tables only exist on
* the connection that made them, so each vhost's list is private to it.
*
* Everything that tells browsers about, or acts on, a project goes through
* these, so that a restricted vhost behaves as if the other projects don't
* exist.
*/
#include <libwebsockets.h>
#include <string.h>
#include "w-private.h"
#define SAIW_VISIBLE_SQL \
" and repo_name in (select name from temp.saiw_visible)"
typedef struct saiw_visible_project {
lws_dll2_t list; /* vhd->visible_projects */
/* name over-allocated here */
} saiw_visible_project_t;
static const char *
saiw_visible_name(const saiw_visible_project_t *vp)
{
return (const char *)&vp[1];
}
static int
saiw_visible_listed(struct vhd *vhd, const char *project)
{
lws_start_foreach_dll(struct lws_dll2 *, p,
vhd->visible_projects.head) {
saiw_visible_project_t *vp = lws_container_of(p,
saiw_visible_project_t, list);
if (!strcmp(saiw_visible_name(vp), project))
return 1;
} lws_end_foreach_dll(p);
return 0;
}
int
saiw_project_visible(struct vhd *vhd, const char *project)
{
return !saiw_restricted(vhd) || saiw_visible_listed(vhd, project);
}
int
saiw_event_visible(struct vhd *vhd, const char *uuid)
{
char event_uuid[33];
sqlite3_stmt *sm;
int r = 0;
if (!saiw_restricted(vhd))
return 1;
/* a task uuid starts with its event's uuid */
if (strlen(uuid) < 32)
return 0;
sai_task_uuid_to_event_uuid(event_uuid, uuid);
if (sqlite3_prepare_v2(vhd->pdb, "SELECT 1 FROM events WHERE uuid = ?"
SAIW_VISIBLE_SQL, -1, &sm, NULL) != SQLITE_OK) {
lwsl_err("%s: prepare failed: %s\n", __func__,
sqlite3_errmsg(vhd->pdb));
return 0;
}
sqlite3_bind_text(sm, 1, event_uuid, -1, SQLITE_TRANSIENT);
r = sqlite3_step(sm) == SQLITE_ROW;
sqlite3_finalize(sm);
return r;
}
const char *
saiw_visible_sql(struct vhd *vhd)
{
return saiw_restricted(vhd) ? SAIW_VISIBLE_SQL : "";
}
static int
saiw_visible_add(struct vhd *vhd, const char *name)
{
saiw_visible_project_t *vp;
size_t len = strlen(name);
sqlite3_stmt *sm;
if (!len || saiw_visible_listed(vhd, name))
/* listed twice is the same as once */
return 0;
vp = lwsac_use_zero(&vhd->ac_visible, sizeof(*vp) + len + 1, 512);
if (!vp)
return 1;
memcpy((char *)&vp[1], name, len + 1);
if (sqlite3_prepare_v2(vhd->pdb, "INSERT INTO temp.saiw_visible "
"(name) VALUES (?)", -1, &sm, NULL) != SQLITE_OK)
return 1;
sqlite3_bind_text(sm, 1, name, -1, SQLITE_TRANSIENT);
if (sai_sqlite3_step_done(vhd->pdb, sm, "list visible project"))
return 1;
lws_dll2_add_tail(&vp->list, &vhd->visible_projects);
return 0;
}
/*
* Parse the "projects" pvo, a comma-separated list, eg,
*
* "projects": "libwebsockets, sai",
*
* Whitespace around the names is ignored. Names may use A-Z a-z 0-9 _ - and .
*
* Call after vhd->pdb is open. Returns 0 if OK, including when there is no
* pvo. If there is one but it can't be understood or lists nothing, it fails
* rather than show this vhost everything.
*/
int
saiw_visible_init(struct vhd *vhd, void *pvo)
{
struct lws_tokenize ts;
const char *list;
char name[65];
lws_dll2_owner_clear(&vhd->visible_projects);
if (lws_pvo_get_str(pvo, "projects", &list))
return 0;
if (sai_sqlite3_statement(vhd->pdb, "CREATE TEMP TABLE IF NOT EXISTS "
"saiw_visible (name TEXT PRIMARY KEY);",
"create saiw_visible"))
return 1;
lws_tokenize_init(&ts, list, LWS_TOKENIZE_F_COMMA_SEP_LIST |
LWS_TOKENIZE_F_MINUS_NONTERM |
LWS_TOKENIZE_F_DOT_NONTERM |
LWS_TOKENIZE_F_NO_INTEGERS |
LWS_TOKENIZE_F_NO_FLOATS);
do {
ts.e = (int8_t)lws_tokenize(&ts);
switch (ts.e) {
case LWS_TOKZE_ENDED:
break;
case LWS_TOKZE_TOKEN:
if (lws_tokenize_cstr(&ts, name, sizeof(name))) {
lwsl_err("%s: project name too long\n",
__func__);
goto bail;
}
if (saiw_visible_add(vhd, name))
goto bail;
break;
case LWS_TOKZE_DELIMITER:
/* COMMA_SEP_LIST makes sure commas are where they go */
if (*ts.token == ',')
break;
goto bail;
default:
/* eg, missing or doubled commas, or bad characters */
goto bail;
}
} while (ts.e > 0);
if (!saiw_restricted(vhd)) {
lwsl_err("%s: \"projects\" pvo lists no projects\n", __func__);
return 1;
}
lwsl_notice("%s: vhost %s shows %u projects: %s\n", __func__,
lws_get_vhost_name(vhd->vhost),
(unsigned int)vhd->visible_projects.count, list);
return 0;
bail:
lwsl_err("%s: unable to use \"projects\" pvo '%s'\n", __func__, list);
saiw_visible_destroy(vhd);
return 1;
}
void
saiw_visible_destroy(struct vhd *vhd)
{
/* the TEMP table goes with the db connection */
lws_dll2_owner_clear(&vhd->visible_projects);
lwsac_free(&vhd->ac_visible);
}
|