From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/json_config.c | 426 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 426 insertions(+) create mode 100644 src/json_config.c (limited to 'src/json_config.c') 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; iname, 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 */ -- cgit v1.3.1