summaryrefslogtreecommitdiff
path: root/src/json_config.c
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
committercashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
commit4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch)
treec296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/json_config.c
downloadrelic-master.tar.gz
initial commitHEADmaster
Diffstat (limited to 'src/json_config.c')
-rw-r--r--src/json_config.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/src/json_config.c b/src/json_config.c
new file mode 100644
index 0000000..7d67553
--- /dev/null
+++ b/src/json_config.c
@@ -0,0 +1,426 @@
+#ifdef FOSSIL_ENABLE_JSON
+/*
+** Copyright (c) 2011 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+**
+** This program 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.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*/
+#include "VERSION.h"
+#include "config.h"
+#include "json_config.h"
+
+#if INTERFACE
+#include "json_detail.h"
+#endif
+
+static cson_value * json_config_get(void);
+static cson_value * json_config_save(void);
+
+/*
+** Mapping of /json/config/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Config[] = {
+{"get", json_config_get, 0},
+{"save", json_config_save, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+static cson_value * json_settings_get(void);
+static cson_value * json_settings_set(void);
+/*
+** Mapping of /json/settings/XXX commands/paths to callbacks.
+*/
+static const JsonPageDef JsonPageDefs_Settings[] = {
+{"get", json_settings_get, 0},
+{"set", json_settings_set, 0},
+/* Last entry MUST have a NULL name. */
+{NULL,NULL,0}
+};
+
+
+/*
+** Implements the /json/config family of pages/commands.
+**
+*/
+cson_value * json_page_config(void){
+ return json_page_dispatch_helper(&JsonPageDefs_Config[0]);
+}
+
+/*
+** Implements the /json/settings family of pages/commands.
+**
+*/
+cson_value * json_page_settings(void){
+ return json_page_dispatch_helper(&JsonPageDefs_Settings[0]);
+}
+
+
+/*
+** JSON-internal mapping of config options to config groups. This is
+** mostly a copy of the config options in configure.c, but that data
+** is private and cannot be re-used directly here.
+*/
+static const struct JsonConfigProperty {
+ char const * name;
+ int groupMask;
+} JsonConfigProperties[] = {
+{ "css", CONFIGSET_CSS },
+{ "header", CONFIGSET_SKIN },
+{ "mainmenu", CONFIGSET_SKIN },
+{ "footer", CONFIGSET_SKIN },
+{ "details", CONFIGSET_SKIN },
+{ "js", CONFIGSET_SKIN },
+{ "default-skin", CONFIGSET_SKIN },
+{ "logo-mimetype", CONFIGSET_SKIN },
+{ "logo-image", CONFIGSET_SKIN },
+{ "background-mimetype", CONFIGSET_SKIN },
+{ "background-image", CONFIGSET_SKIN },
+{ "icon-mimetype", CONFIGSET_SKIN },
+{ "icon-image", CONFIGSET_SKIN },
+{ "timeline-date-format", CONFIGSET_SKIN },
+{ "timeline-default-style", CONFIGSET_SKIN },
+{ "timeline-dwelltime", CONFIGSET_SKIN },
+{ "timeline-closetime", CONFIGSET_SKIN },
+{ "timeline-hard-newlines", CONFIGSET_SKIN },
+{ "timeline-max-comment", CONFIGSET_SKIN },
+{ "timeline-plaintext", CONFIGSET_SKIN },
+{ "timeline-truncate-at-blank", CONFIGSET_SKIN },
+{ "timeline-tslink-info", CONFIGSET_SKIN },
+{ "timeline-utc", CONFIGSET_SKIN },
+{ "adunit", CONFIGSET_SKIN },
+{ "adunit-omit-if-admin", CONFIGSET_SKIN },
+{ "adunit-omit-if-user", CONFIGSET_SKIN },
+{ "default-csp", CONFIGSET_SKIN },
+{ "sitemap-extra", CONFIGSET_SKIN },
+{ "safe-html", CONFIGSET_SKIN },
+
+{ "project-name", CONFIGSET_PROJ },
+{ "short-project-name", CONFIGSET_PROJ },
+{ "project-description", CONFIGSET_PROJ },
+{ "index-page", CONFIGSET_PROJ },
+{ "manifest", CONFIGSET_PROJ },
+{ "binary-glob", CONFIGSET_PROJ },
+{ "clean-glob", CONFIGSET_PROJ },
+{ "ignore-glob", CONFIGSET_PROJ },
+{ "keep-glob", CONFIGSET_PROJ },
+{ "crlf-glob", CONFIGSET_PROJ },
+{ "crnl-glob", CONFIGSET_PROJ },
+{ "encoding-glob", CONFIGSET_PROJ },
+{ "empty-dirs", CONFIGSET_PROJ },
+{ "dotfiles", CONFIGSET_PROJ },
+{ "parent-project-code", CONFIGSET_PROJ },
+{ "parent-project-name", CONFIGSET_PROJ },
+{ "hash-policy", CONFIGSET_PROJ },
+{ "comment-format", CONFIGSET_PROJ },
+{ "mimetypes", CONFIGSET_PROJ },
+{ "forbid-delta-manifests", CONFIGSET_PROJ },
+{ "mv-rm-files", CONFIGSET_PROJ },
+
+{ "user-color-map", CONFIGSET_USER },
+
+{ "ticket-table", CONFIGSET_TKT },
+{ "ticket-common", CONFIGSET_TKT },
+{ "ticket-change", CONFIGSET_TKT },
+{ "ticket-newpage", CONFIGSET_TKT },
+{ "ticket-viewpage", CONFIGSET_TKT },
+{ "ticket-editpage", CONFIGSET_TKT },
+{ "ticket-reportlist", CONFIGSET_TKT },
+{ "ticket-report-template", CONFIGSET_TKT },
+{ "ticket-key-template", CONFIGSET_TKT },
+{ "ticket-title-expr", CONFIGSET_TKT },
+{ "ticket-closed-expr", CONFIGSET_TKT },
+
+{NULL, 0}
+};
+
+
+/*
+** Impl of /json/config/get. Requires setup rights.
+**
+*/
+static cson_value * json_config_get(void){
+ cson_object * pay = NULL;
+ Stmt q = empty_Stmt;
+ Blob sql = empty_blob;
+ char const * zName = NULL;
+ int confMask = 0;
+ char optSkinBackups = 0;
+ unsigned int i;
+ if(!g.perm.Setup){
+ json_set_err(FSL_JSON_E_DENIED, "Requires 's' permissions.");
+ return NULL;
+ }
+
+ i = g.json.dispatchDepth + 1;
+ zName = json_command_arg(i);
+ for( ; zName; zName = json_command_arg(++i) ){
+ if(0==(strcmp("all", zName))){
+ confMask = CONFIGSET_ALL;
+ }else if(0==(strcmp("project", zName))){
+ confMask |= CONFIGSET_PROJ;
+ }else if(0==(strcmp("skin", zName))){
+ confMask |= (CONFIGSET_CSS|CONFIGSET_SKIN);
+ }else if(0==(strcmp("ticket", zName))){
+ confMask |= CONFIGSET_TKT;
+ }else if(0==(strcmp("skin-backup", zName))){
+ optSkinBackups = 1;
+ }else{
+ json_set_err( FSL_JSON_E_INVALID_ARGS,
+ "Unknown config area: %s", zName);
+ return NULL;
+ }
+ }
+
+ if(!confMask && !optSkinBackups){
+ json_set_err(FSL_JSON_E_MISSING_ARGS, "No configuration area(s) selected.");
+ }
+ blob_append(&sql,
+ "SELECT name, value"
+ " FROM config "
+ " WHERE 0 ", -1);
+ {
+ const struct JsonConfigProperty * prop = &JsonConfigProperties[0];
+ blob_append(&sql," OR name IN (",-1);
+ for( i = 0; prop->name; ++prop ){
+ if(prop->groupMask & confMask){
+ if( i++ ){
+ blob_append(&sql,",",1);
+ }
+ blob_append_sql(&sql, "%Q", prop->name);
+ }
+ }
+ blob_append(&sql,") ", -1);
+ }
+
+
+ if( optSkinBackups ){
+ blob_append(&sql, " OR name GLOB 'skin:*'", -1);
+ }
+ blob_append(&sql," ORDER BY name", -1);
+ db_prepare(&q, "%s", blob_sql_text(&sql));
+ blob_reset(&sql);
+ pay = cson_new_object();
+ while( (SQLITE_ROW==db_step(&q)) ){
+ cson_object_set(pay,
+ db_column_text(&q,0),
+ json_new_string(db_column_text(&q,1)));
+ }
+ db_finalize(&q);
+ return cson_object_value(pay);
+}
+
+/*
+** Impl of /json/config/save.
+**
+** TODOs:
+*/
+static cson_value * json_config_save(void){
+ json_set_err(FSL_JSON_E_NYI, NULL);
+ return NULL;
+}
+
+/*
+** Impl of /json/settings/get.
+*/
+static cson_value * json_settings_get(void){
+ cson_object * pay = cson_new_object(); /* output payload */
+ int nSetting, i; /* setting count and loop var */
+ const Setting *aSetting = setting_info(&nSetting);
+ const char * zRevision = 0; /* revision to look for
+ versioned settings in */
+ char * zUuid = 0; /* Resolved UUID of zRevision */
+ Stmt q = empty_Stmt; /* Config-search query */
+ Stmt qFoci = empty_Stmt; /* foci query */
+
+ if( !g.perm.Read ){
+ json_set_err( FSL_JSON_E_DENIED, "Fetching settings requires 'o' access." );
+ return NULL;
+ }
+ zRevision = json_find_option_cstr("version",NULL,NULL);
+ if( 0!=zRevision ){
+ int rid = name_to_uuid2(zRevision, "ci", &zUuid);
+ if(rid<=0){
+ json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
+ "Cannot find the given version.");
+ return NULL;
+ }
+ db_multi_exec("CREATE VIRTUAL TABLE IF NOT EXISTS "
+ "temp.foci USING files_of_checkin;");
+ db_prepare(&qFoci,
+ "SELECT uuid FROM temp.foci WHERE "
+ "checkinID=%d AND filename='.fossil-settings/' || :name",
+ rid);
+ }
+ zRevision = 0;
+
+ if( g.localOpen ){
+ db_prepare(&q,
+ "SELECT 'checkout', value FROM vvar WHERE name=:name"
+ " UNION ALL "
+ "SELECT 'repo', value FROM config WHERE name=:name"
+ );
+ }else{
+ db_prepare(&q,
+ "SELECT 'repo', value FROM config WHERE name=:name"
+ );
+ }
+ for(i=0; i<nSetting; ++i){
+ const Setting *pSet = &aSetting[i];
+ cson_object * jSet;
+ cson_value * pVal = 0, * pSrc = 0;
+ jSet = cson_new_object();
+ cson_object_set(pay, pSet->name, cson_object_value(jSet));
+ cson_object_set(jSet, "versionable",cson_value_new_bool(pSet->versionable));
+ cson_object_set(jSet, "sensitive", cson_value_new_bool(pSet->sensitive));
+ cson_object_set(jSet, "defaultValue", (pSet->def && pSet->def[0])
+ ? json_new_string(pSet->def)
+ : cson_value_null());
+ if( 0==pSet->sensitive || 0!=g.perm.Setup ){
+ if( pSet->versionable ){
+ /* Check to see if this is overridden by a versionable
+ ** settings file */
+ if( 0!=zUuid ){
+ /* Attempt to find a versioned setting stored in the given
+ ** check-in version. */
+ db_bind_text(&qFoci, ":name", pSet->name);
+ if( SQLITE_ROW==db_step(&qFoci) ){
+ int frid = fast_uuid_to_rid(db_column_text(&qFoci, 0));
+ Blob content;
+ blob_zero(&content);
+ if( 0!=content_get(frid, &content) ){
+ pSrc = json_new_string("versioned");
+ pVal = json_new_string(blob_str(&content));
+ }
+ blob_reset(&content);
+ }
+ db_reset(&qFoci);
+ }
+ if( 0==pSrc && g.localOpen ){
+ /* Pull value from a checkout-local .fossil-settings/X file,
+ ** if one exists. */
+ Blob versionedPathname;
+ blob_zero(&versionedPathname);
+ blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
+ g.zLocalRoot, pSet->name);
+ if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
+ Blob content;
+ blob_zero(&content);
+ blob_read_from_file(&content, blob_str(&versionedPathname),ExtFILE);
+ pSrc = json_new_string("versioned");
+ pVal = json_new_string(blob_str(&content));
+ blob_reset(&content);
+ }
+ blob_reset(&versionedPathname);
+ }
+ }
+ if( 0==pSrc ){
+ /* Setting is not versionable or we had no versioned value, so
+ ** use the value from localdb.vvar or repository.config (in
+ ** that order). */
+ db_bind_text(&q, ":name", pSet->name);
+ if( SQLITE_ROW==db_step(&q) ){
+ pSrc = json_new_string(db_column_text(&q, 0));
+ pVal = json_new_string(db_column_text(&q, 1));
+ }
+ db_reset(&q);
+ }
+ }
+ cson_object_set(jSet, "valueSource", pSrc ? pSrc : cson_value_null());
+ cson_object_set(jSet, "value", pVal ? pVal : cson_value_null());
+ }/*aSetting loop*/
+ db_finalize(&q);
+ db_finalize(&qFoci);
+ fossil_free(zUuid);
+ return cson_object_value(pay);
+}
+
+/*
+** Impl of /json/settings/set.
+**
+** Input payload is an object mapping setting names to values. All
+** values are set in the repository.config table. It has no response
+** payload.
+*/
+static cson_value * json_settings_set(void){
+ Stmt q = empty_Stmt; /* Config-set query */
+ cson_object_iterator objIter = cson_object_iterator_empty;
+ cson_kvp * pKvp;
+ int nErr = 0, nProp = 0;
+
+ if( 0==g.perm.Setup ){
+ json_set_err( FSL_JSON_E_DENIED, "Setting settings requires 's' access." );
+ return NULL;
+ }
+ else if( 0==g.json.reqPayload.o ){
+ json_set_err(FSL_JSON_E_MISSING_ARGS,
+ "Missing payload of setting-to-value mappings.");
+ return NULL;
+ }
+
+ db_unprotect(PROTECT_CONFIG);
+ db_prepare(&q,
+ "INSERT OR REPLACE INTO config (name, value, mtime) "
+ "VALUES(:name, :value, CAST(strftime('%%s') AS INT))"
+ );
+ db_begin_transaction();
+ cson_object_iter_init( g.json.reqPayload.o, &objIter );
+ while( (pKvp = cson_object_iter_next(&objIter)) ){
+ char const * zKey = cson_string_cstr( cson_kvp_key(pKvp) );
+ cson_value * pVal;
+ const Setting *pSetting = db_find_setting( zKey, 0 );
+ if( 0==pSetting ){
+ nErr = json_set_err(FSL_JSON_E_INVALID_ARGS,
+ "Unknown setting: %s", zKey);
+ break;
+ }
+ pVal = cson_kvp_value(pKvp);
+ switch( cson_value_type_id(pVal) ){
+ case CSON_TYPE_NULL:
+ db_multi_exec("DELETE FROM config WHERE name=%Q", pSetting->name);
+ continue;
+ case CSON_TYPE_BOOL:
+ db_bind_int(&q, ":value", cson_value_get_bool(pVal) ? 1 : 0);
+ break;
+ case CSON_TYPE_INTEGER:
+ db_bind_int64(&q, ":value", cson_value_get_integer(pVal));
+ break;
+ case CSON_TYPE_DOUBLE:
+ db_bind_double(&q, ":value", cson_value_get_double(pVal));
+ break;
+ case CSON_TYPE_STRING:
+ db_bind_text(&q, ":value", cson_value_get_cstr(pVal));
+ break;
+ default:
+ nErr = json_set_err(FSL_JSON_E_USAGE,
+ "Invalid value type for setting '%s'.",
+ pSetting->name);
+ break;
+ }
+ if( 0!=nErr ) break;
+ db_bind_text(&q, ":name", zKey);
+ db_step(&q);
+ db_reset(&q);
+ ++nProp;
+ }
+ db_finalize(&q);
+ if( 0==nErr && 0==nProp ){
+ nErr = json_set_err(FSL_JSON_E_INVALID_ARGS,
+ "Payload contains no settings to set.");
+ }
+ db_end_transaction(nErr);
+ db_protect_pop();
+ return NULL;
+}
+
+#endif /* FOSSIL_ENABLE_JSON */