Fossil SCM

fossil-scm / src / json_config.c
Blame History Raw 427 lines
1
#ifdef FOSSIL_ENABLE_JSON
2
/*
3
** Copyright (c) 2011 D. Richard Hipp
4
**
5
** This program is free software; you can redistribute it and/or
6
** modify it under the terms of the Simplified BSD License (also
7
** known as the "2-Clause License" or "FreeBSD License".)
8
**
9
** This program is distributed in the hope that it will be useful,
10
** but without any warranty; without even the implied warranty of
11
** merchantability or fitness for a particular purpose.
12
**
13
** Author contact information:
14
** [email protected]
15
** http://www.hwaci.com/drh/
16
**
17
*/
18
#include "VERSION.h"
19
#include "config.h"
20
#include "json_config.h"
21
22
#if INTERFACE
23
#include "json_detail.h"
24
#endif
25
26
static cson_value * json_config_get(void);
27
static cson_value * json_config_save(void);
28
29
/*
30
** Mapping of /json/config/XXX commands/paths to callbacks.
31
*/
32
static const JsonPageDef JsonPageDefs_Config[] = {
33
{"get", json_config_get, 0},
34
{"save", json_config_save, 0},
35
/* Last entry MUST have a NULL name. */
36
{NULL,NULL,0}
37
};
38
39
static cson_value * json_settings_get(void);
40
static cson_value * json_settings_set(void);
41
/*
42
** Mapping of /json/settings/XXX commands/paths to callbacks.
43
*/
44
static const JsonPageDef JsonPageDefs_Settings[] = {
45
{"get", json_settings_get, 0},
46
{"set", json_settings_set, 0},
47
/* Last entry MUST have a NULL name. */
48
{NULL,NULL,0}
49
};
50
51
52
/*
53
** Implements the /json/config family of pages/commands.
54
**
55
*/
56
cson_value * json_page_config(void){
57
return json_page_dispatch_helper(&JsonPageDefs_Config[0]);
58
}
59
60
/*
61
** Implements the /json/settings family of pages/commands.
62
**
63
*/
64
cson_value * json_page_settings(void){
65
return json_page_dispatch_helper(&JsonPageDefs_Settings[0]);
66
}
67
68
69
/*
70
** JSON-internal mapping of config options to config groups. This is
71
** mostly a copy of the config options in configure.c, but that data
72
** is private and cannot be re-used directly here.
73
*/
74
static const struct JsonConfigProperty {
75
char const * name;
76
int groupMask;
77
} JsonConfigProperties[] = {
78
{ "css", CONFIGSET_CSS },
79
{ "header", CONFIGSET_SKIN },
80
{ "mainmenu", CONFIGSET_SKIN },
81
{ "footer", CONFIGSET_SKIN },
82
{ "details", CONFIGSET_SKIN },
83
{ "js", CONFIGSET_SKIN },
84
{ "default-skin", CONFIGSET_SKIN },
85
{ "logo-mimetype", CONFIGSET_SKIN },
86
{ "logo-image", CONFIGSET_SKIN },
87
{ "background-mimetype", CONFIGSET_SKIN },
88
{ "background-image", CONFIGSET_SKIN },
89
{ "icon-mimetype", CONFIGSET_SKIN },
90
{ "icon-image", CONFIGSET_SKIN },
91
{ "timeline-date-format", CONFIGSET_SKIN },
92
{ "timeline-default-style", CONFIGSET_SKIN },
93
{ "timeline-dwelltime", CONFIGSET_SKIN },
94
{ "timeline-closetime", CONFIGSET_SKIN },
95
{ "timeline-hard-newlines", CONFIGSET_SKIN },
96
{ "timeline-max-comment", CONFIGSET_SKIN },
97
{ "timeline-plaintext", CONFIGSET_SKIN },
98
{ "timeline-truncate-at-blank", CONFIGSET_SKIN },
99
{ "timeline-tslink-info", CONFIGSET_SKIN },
100
{ "timeline-utc", CONFIGSET_SKIN },
101
{ "adunit", CONFIGSET_SKIN },
102
{ "adunit-omit-if-admin", CONFIGSET_SKIN },
103
{ "adunit-omit-if-user", CONFIGSET_SKIN },
104
{ "default-csp", CONFIGSET_SKIN },
105
{ "sitemap-extra", CONFIGSET_SKIN },
106
{ "safe-html", CONFIGSET_SKIN },
107
108
{ "project-name", CONFIGSET_PROJ },
109
{ "short-project-name", CONFIGSET_PROJ },
110
{ "project-description", CONFIGSET_PROJ },
111
{ "index-page", CONFIGSET_PROJ },
112
{ "manifest", CONFIGSET_PROJ },
113
{ "binary-glob", CONFIGSET_PROJ },
114
{ "clean-glob", CONFIGSET_PROJ },
115
{ "ignore-glob", CONFIGSET_PROJ },
116
{ "keep-glob", CONFIGSET_PROJ },
117
{ "crlf-glob", CONFIGSET_PROJ },
118
{ "crnl-glob", CONFIGSET_PROJ },
119
{ "encoding-glob", CONFIGSET_PROJ },
120
{ "empty-dirs", CONFIGSET_PROJ },
121
{ "dotfiles", CONFIGSET_PROJ },
122
{ "parent-project-code", CONFIGSET_PROJ },
123
{ "parent-project-name", CONFIGSET_PROJ },
124
{ "hash-policy", CONFIGSET_PROJ },
125
{ "comment-format", CONFIGSET_PROJ },
126
{ "mimetypes", CONFIGSET_PROJ },
127
{ "forbid-delta-manifests", CONFIGSET_PROJ },
128
{ "mv-rm-files", CONFIGSET_PROJ },
129
130
{ "user-color-map", CONFIGSET_USER },
131
132
{ "ticket-table", CONFIGSET_TKT },
133
{ "ticket-common", CONFIGSET_TKT },
134
{ "ticket-change", CONFIGSET_TKT },
135
{ "ticket-newpage", CONFIGSET_TKT },
136
{ "ticket-viewpage", CONFIGSET_TKT },
137
{ "ticket-editpage", CONFIGSET_TKT },
138
{ "ticket-reportlist", CONFIGSET_TKT },
139
{ "ticket-report-template", CONFIGSET_TKT },
140
{ "ticket-key-template", CONFIGSET_TKT },
141
{ "ticket-title-expr", CONFIGSET_TKT },
142
{ "ticket-closed-expr", CONFIGSET_TKT },
143
144
{NULL, 0}
145
};
146
147
148
/*
149
** Impl of /json/config/get. Requires setup rights.
150
**
151
*/
152
static cson_value * json_config_get(void){
153
cson_object * pay = NULL;
154
Stmt q = empty_Stmt;
155
Blob sql = empty_blob;
156
char const * zName = NULL;
157
int confMask = 0;
158
char optSkinBackups = 0;
159
unsigned int i;
160
if(!g.perm.Setup){
161
json_set_err(FSL_JSON_E_DENIED, "Requires 's' permissions.");
162
return NULL;
163
}
164
165
i = g.json.dispatchDepth + 1;
166
zName = json_command_arg(i);
167
for( ; zName; zName = json_command_arg(++i) ){
168
if(0==(strcmp("all", zName))){
169
confMask = CONFIGSET_ALL;
170
}else if(0==(strcmp("project", zName))){
171
confMask |= CONFIGSET_PROJ;
172
}else if(0==(strcmp("skin", zName))){
173
confMask |= (CONFIGSET_CSS|CONFIGSET_SKIN);
174
}else if(0==(strcmp("ticket", zName))){
175
confMask |= CONFIGSET_TKT;
176
}else if(0==(strcmp("skin-backup", zName))){
177
optSkinBackups = 1;
178
}else{
179
json_set_err( FSL_JSON_E_INVALID_ARGS,
180
"Unknown config area: %s", zName);
181
return NULL;
182
}
183
}
184
185
if(!confMask && !optSkinBackups){
186
json_set_err(FSL_JSON_E_MISSING_ARGS, "No configuration area(s) selected.");
187
}
188
blob_append(&sql,
189
"SELECT name, value"
190
" FROM config "
191
" WHERE 0 ", -1);
192
{
193
const struct JsonConfigProperty * prop = &JsonConfigProperties[0];
194
blob_append(&sql," OR name IN (",-1);
195
for( i = 0; prop->name; ++prop ){
196
if(prop->groupMask & confMask){
197
if( i++ ){
198
blob_append(&sql,",",1);
199
}
200
blob_append_sql(&sql, "%Q", prop->name);
201
}
202
}
203
blob_append(&sql,") ", -1);
204
}
205
206
207
if( optSkinBackups ){
208
blob_append(&sql, " OR name GLOB 'skin:*'", -1);
209
}
210
blob_append(&sql," ORDER BY name", -1);
211
db_prepare(&q, "%s", blob_sql_text(&sql));
212
blob_reset(&sql);
213
pay = cson_new_object();
214
while( (SQLITE_ROW==db_step(&q)) ){
215
cson_object_set(pay,
216
db_column_text(&q,0),
217
json_new_string(db_column_text(&q,1)));
218
}
219
db_finalize(&q);
220
return cson_object_value(pay);
221
}
222
223
/*
224
** Impl of /json/config/save.
225
**
226
** TODOs:
227
*/
228
static cson_value * json_config_save(void){
229
json_set_err(FSL_JSON_E_NYI, NULL);
230
return NULL;
231
}
232
233
/*
234
** Impl of /json/settings/get.
235
*/
236
static cson_value * json_settings_get(void){
237
cson_object * pay = cson_new_object(); /* output payload */
238
int nSetting, i; /* setting count and loop var */
239
const Setting *aSetting = setting_info(&nSetting);
240
const char * zRevision = 0; /* revision to look for
241
versioned settings in */
242
char * zUuid = 0; /* Resolved UUID of zRevision */
243
Stmt q = empty_Stmt; /* Config-search query */
244
Stmt qFoci = empty_Stmt; /* foci query */
245
246
if( !g.perm.Read ){
247
json_set_err( FSL_JSON_E_DENIED, "Fetching settings requires 'o' access." );
248
return NULL;
249
}
250
zRevision = json_find_option_cstr("version",NULL,NULL);
251
if( 0!=zRevision ){
252
int rid = name_to_uuid2(zRevision, "ci", &zUuid);
253
if(rid<=0){
254
json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,
255
"Cannot find the given version.");
256
return NULL;
257
}
258
db_multi_exec("CREATE VIRTUAL TABLE IF NOT EXISTS "
259
"temp.foci USING files_of_checkin;");
260
db_prepare(&qFoci,
261
"SELECT uuid FROM temp.foci WHERE "
262
"checkinID=%d AND filename='.fossil-settings/' || :name",
263
rid);
264
}
265
zRevision = 0;
266
267
if( g.localOpen ){
268
db_prepare(&q,
269
"SELECT 'checkout', value FROM vvar WHERE name=:name"
270
" UNION ALL "
271
"SELECT 'repo', value FROM config WHERE name=:name"
272
);
273
}else{
274
db_prepare(&q,
275
"SELECT 'repo', value FROM config WHERE name=:name"
276
);
277
}
278
for(i=0; i<nSetting; ++i){
279
const Setting *pSet = &aSetting[i];
280
cson_object * jSet;
281
cson_value * pVal = 0, * pSrc = 0;
282
jSet = cson_new_object();
283
cson_object_set(pay, pSet->name, cson_object_value(jSet));
284
cson_object_set(jSet, "versionable",cson_value_new_bool(pSet->versionable));
285
cson_object_set(jSet, "sensitive", cson_value_new_bool(pSet->sensitive));
286
cson_object_set(jSet, "defaultValue", (pSet->def && pSet->def[0])
287
? json_new_string(pSet->def)
288
: cson_value_null());
289
if( 0==pSet->sensitive || 0!=g.perm.Setup ){
290
if( pSet->versionable ){
291
/* Check to see if this is overridden by a versionable
292
** settings file */
293
if( 0!=zUuid ){
294
/* Attempt to find a versioned setting stored in the given
295
** check-in version. */
296
db_bind_text(&qFoci, ":name", pSet->name);
297
if( SQLITE_ROW==db_step(&qFoci) ){
298
int frid = fast_uuid_to_rid(db_column_text(&qFoci, 0));
299
Blob content;
300
blob_zero(&content);
301
if( 0!=content_get(frid, &content) ){
302
pSrc = json_new_string("versioned");
303
pVal = json_new_string(blob_str(&content));
304
}
305
blob_reset(&content);
306
}
307
db_reset(&qFoci);
308
}
309
if( 0==pSrc && g.localOpen ){
310
/* Pull value from a checkout-local .fossil-settings/X file,
311
** if one exists. */
312
Blob versionedPathname;
313
blob_zero(&versionedPathname);
314
blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
315
g.zLocalRoot, pSet->name);
316
if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
317
Blob content;
318
blob_zero(&content);
319
blob_read_from_file(&content, blob_str(&versionedPathname),ExtFILE);
320
pSrc = json_new_string("versioned");
321
pVal = json_new_string(blob_str(&content));
322
blob_reset(&content);
323
}
324
blob_reset(&versionedPathname);
325
}
326
}
327
if( 0==pSrc ){
328
/* Setting is not versionable or we had no versioned value, so
329
** use the value from localdb.vvar or repository.config (in
330
** that order). */
331
db_bind_text(&q, ":name", pSet->name);
332
if( SQLITE_ROW==db_step(&q) ){
333
pSrc = json_new_string(db_column_text(&q, 0));
334
pVal = json_new_string(db_column_text(&q, 1));
335
}
336
db_reset(&q);
337
}
338
}
339
cson_object_set(jSet, "valueSource", pSrc ? pSrc : cson_value_null());
340
cson_object_set(jSet, "value", pVal ? pVal : cson_value_null());
341
}/*aSetting loop*/
342
db_finalize(&q);
343
db_finalize(&qFoci);
344
fossil_free(zUuid);
345
return cson_object_value(pay);
346
}
347
348
/*
349
** Impl of /json/settings/set.
350
**
351
** Input payload is an object mapping setting names to values. All
352
** values are set in the repository.config table. It has no response
353
** payload.
354
*/
355
static cson_value * json_settings_set(void){
356
Stmt q = empty_Stmt; /* Config-set query */
357
cson_object_iterator objIter = cson_object_iterator_empty;
358
cson_kvp * pKvp;
359
int nErr = 0, nProp = 0;
360
361
if( 0==g.perm.Setup ){
362
json_set_err( FSL_JSON_E_DENIED, "Setting settings requires 's' access." );
363
return NULL;
364
}
365
else if( 0==g.json.reqPayload.o ){
366
json_set_err(FSL_JSON_E_MISSING_ARGS,
367
"Missing payload of setting-to-value mappings.");
368
return NULL;
369
}
370
371
db_unprotect(PROTECT_CONFIG);
372
db_prepare(&q,
373
"INSERT OR REPLACE INTO config (name, value, mtime) "
374
"VALUES(:name, :value, CAST(strftime('%%s') AS INT))"
375
);
376
db_begin_transaction();
377
cson_object_iter_init( g.json.reqPayload.o, &objIter );
378
while( (pKvp = cson_object_iter_next(&objIter)) ){
379
char const * zKey = cson_string_cstr( cson_kvp_key(pKvp) );
380
cson_value * pVal;
381
const Setting *pSetting = db_find_setting( zKey, 0 );
382
if( 0==pSetting ){
383
nErr = json_set_err(FSL_JSON_E_INVALID_ARGS,
384
"Unknown setting: %s", zKey);
385
break;
386
}
387
pVal = cson_kvp_value(pKvp);
388
switch( cson_value_type_id(pVal) ){
389
case CSON_TYPE_NULL:
390
db_multi_exec("DELETE FROM config WHERE name=%Q", pSetting->name);
391
continue;
392
case CSON_TYPE_BOOL:
393
db_bind_int(&q, ":value", cson_value_get_bool(pVal) ? 1 : 0);
394
break;
395
case CSON_TYPE_INTEGER:
396
db_bind_int64(&q, ":value", cson_value_get_integer(pVal));
397
break;
398
case CSON_TYPE_DOUBLE:
399
db_bind_double(&q, ":value", cson_value_get_double(pVal));
400
break;
401
case CSON_TYPE_STRING:
402
db_bind_text(&q, ":value", cson_value_get_cstr(pVal));
403
break;
404
default:
405
nErr = json_set_err(FSL_JSON_E_USAGE,
406
"Invalid value type for setting '%s'.",
407
pSetting->name);
408
break;
409
}
410
if( 0!=nErr ) break;
411
db_bind_text(&q, ":name", zKey);
412
db_step(&q);
413
db_reset(&q);
414
++nProp;
415
}
416
db_finalize(&q);
417
if( 0==nErr && 0==nProp ){
418
nErr = json_set_err(FSL_JSON_E_INVALID_ARGS,
419
"Payload contains no settings to set.");
420
}
421
db_end_transaction(nErr);
422
db_protect_pop();
423
return NULL;
424
}
425
426
#endif /* FOSSIL_ENABLE_JSON */
427

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button