Fossil SCM

fossil-scm / src / json.c
Blame History Raw 2446 lines
1
#ifdef FOSSIL_ENABLE_JSON
2
/*
3
** Copyright (c) 2011-2022 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
**
19
** Code for the JSON API.
20
**
21
** The JSON API's public interface is documented at:
22
**
23
** https://fossil-scm.org/fossil/doc/trunk/www/json-api/index.md
24
**
25
** Notes for hackers...
26
**
27
** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
28
** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions
29
** then dispatch to a JSON-mode-specific command/page handler with the type
30
** fossil_json_f().
31
** See the API docs for that typedef (below) for the semantics of the callbacks.
32
**
33
**
34
*/
35
#include "VERSION.h"
36
#include "config.h"
37
#include "json.h"
38
#include <assert.h>
39
#include <time.h>
40
41
#if INTERFACE
42
#include "json_detail.h" /* workaround for apparent enum limitation
43
in makeheaders */
44
#endif
45
46
const FossilJsonKeys_ FossilJsonKeys = {
47
"anonymousSeed" /*anonymousSeed*/,
48
"authToken" /*authToken*/,
49
"COMMAND_PATH" /*commandPath*/,
50
"mtime" /*mtime*/,
51
"payload" /* payload */,
52
"requestId" /*requestId*/,
53
"resultCode" /*resultCode*/,
54
"resultText" /*resultText*/,
55
"timestamp" /*timestamp*/
56
};
57
58
/*
59
** Given the current request path string, this function returns true
60
** if it refers to a JSON API path. i.e. if (1) it starts with /json
61
** or (2) g.zCmdName is "server" or "cgi" and the path starts with
62
** /somereponame/json. Specifically, it returns 1 in the former case
63
** and 2 for the latter.
64
*/
65
int json_request_is_json_api(const char * zPathInfo){
66
int rc = 0;
67
if(zPathInfo==0){
68
rc = 0;
69
}else if(0==strncmp("/json",zPathInfo,5)
70
&& (zPathInfo[5]==0 || zPathInfo[5]=='/')){
71
rc = 1;
72
}else if(g.zCmdName!=0 && (0==strcmp("server",g.zCmdName)
73
|| 0==strcmp("ui",g.zCmdName)
74
|| 0==strcmp("cgi",g.zCmdName)
75
|| 0==strcmp("http",g.zCmdName)) ){
76
/* When running in server/cgi "directory" mode, zPathInfo is
77
** prefixed with the repository's name, so in order to determine
78
** whether or not we're really running in json mode we have to try
79
** a bit harder. Problem reported here:
80
** https://fossil-scm.org/forum/forumpost/e4953666d6
81
*/
82
ReCompiled * pReg = 0;
83
const char * zErr = fossil_re_compile(&pReg, "^/[^/]+/json(/.*)?", 0);
84
assert(zErr==0 && "Regex compilation failed?");
85
if(zErr==0 &&
86
re_match(pReg, (const unsigned char *)zPathInfo, -1)){
87
rc = 2;
88
}
89
re_free(pReg);
90
}
91
return rc;
92
}
93
94
/*
95
** Returns true (non-0) if fossil appears to be running in JSON mode.
96
** and either has JSON POSTed input awaiting consumption or fossil is
97
** running in HTTP mode (in which case certain JSON data *might* be
98
** available via GET parameters).
99
*/
100
int fossil_has_json(){
101
return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
102
}
103
104
/*
105
** Placeholder /json/XXX page impl for NYI (Not Yet Implemented)
106
** (but planned) pages/commands.
107
*/
108
cson_value * json_page_nyi(){
109
g.json.resultCode = FSL_JSON_E_NYI;
110
return NULL;
111
}
112
113
/*
114
** Given a FossilJsonCodes value, it returns a string suitable for use
115
** as a resultCode string. Returns some unspecified non-empty string
116
** if errCode is not one of the FossilJsonCodes values.
117
*/
118
static char const * json_err_cstr( int errCode ){
119
switch( errCode ){
120
case 0: return "Success";
121
#define C(X,V) case FSL_JSON_E_ ## X: return V
122
123
C(GENERIC,"Generic error");
124
C(INVALID_REQUEST,"Invalid request");
125
C(UNKNOWN_COMMAND,"Unknown command or subcommand");
126
C(UNKNOWN,"Unknown error");
127
C(TIMEOUT,"Timeout reached");
128
C(ASSERT,"Assertion failed");
129
C(ALLOC,"Resource allocation failed");
130
C(NYI,"Not yet implemented");
131
C(PANIC,"x");
132
C(MANIFEST_READ_FAILED,"Reading artifact manifest failed");
133
C(FILE_OPEN_FAILED,"Opening file failed");
134
135
C(AUTH,"Authentication error");
136
C(MISSING_AUTH,"Authentication info missing from request");
137
C(DENIED,"Access denied");
138
C(WRONG_MODE,"Request not allowed (wrong operation mode)");
139
C(LOGIN_FAILED,"Login failed");
140
C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed");
141
C(LOGIN_FAILED_NONAME,"Login failed - name not supplied");
142
C(LOGIN_FAILED_NOPW,"Login failed - password not supplied");
143
C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found");
144
145
C(USAGE,"Usage error");
146
C(INVALID_ARGS,"Invalid argument(s)");
147
C(MISSING_ARGS,"Missing argument(s)");
148
C(AMBIGUOUS_UUID,"Resource identifier is ambiguous");
149
C(UNRESOLVED_UUID,"Provided uuid/tag/branch could not be resolved");
150
C(RESOURCE_ALREADY_EXISTS,"Resource already exists");
151
C(RESOURCE_NOT_FOUND,"Resource not found");
152
153
C(DB,"Database error");
154
C(STMT_PREP,"Statement preparation failed");
155
C(STMT_BIND,"Statement parameter binding failed");
156
C(STMT_EXEC,"Statement execution/stepping failed");
157
C(DB_LOCKED,"Database is locked");
158
C(DB_NEEDS_REBUILD,"Fossil repository needs to be rebuilt");
159
C(DB_NOT_FOUND,"Fossil repository db file could not be found.");
160
C(DB_NOT_VALID, "Fossil repository db file is not valid.");
161
C(DB_NEEDS_CHECKOUT, "Command requires a local check-out.");
162
#undef C
163
default:
164
return "Unknown Error";
165
}
166
}
167
168
/*
169
** Implements the cson_data_dest_f() interface and outputs the data to
170
** a fossil Blob object. pState must be-a initialized (Blob*), to
171
** which n bytes of src will be appended.
172
**/
173
int cson_data_dest_Blob(void * pState, void const * src, unsigned int n){
174
Blob * b = (Blob*)pState;
175
blob_append( b, (char const *)src, (int)n ) /* will die on OOM */;
176
return 0;
177
}
178
179
/*
180
** Convenience wrapper around cson_output() which appends the output
181
** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used.
182
*/
183
int cson_output_Blob( cson_value const * pVal, Blob * pDest,
184
cson_output_opt const * pOpt ){
185
return cson_output( pVal, cson_data_dest_Blob,
186
pDest, pOpt ? pOpt : &g.json.outOpt );
187
}
188
189
/*
190
** Convenience wrapper around cson_parse() which reads its input
191
** from pSrc. pSrc is rewound before parsing.
192
**
193
** pInfo may be NULL. If it is not NULL then it will contain details
194
** about the parse state when this function returns.
195
**
196
** On success a new JSON Object or Array is returned (owned by the
197
** caller). On error NULL is returned.
198
*/
199
cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){
200
cson_value * root = NULL;
201
cson_parse_string( &root, blob_str(pSrc), blob_size(pSrc), NULL, pInfo );
202
return root;
203
}
204
205
/*
206
** Implements the cson_data_dest_f() interface and outputs the data to
207
** cgi_append_content(). pState is ignored.
208
**/
209
int cson_data_dest_cgi(void * pState, void const * src, unsigned int n){
210
cgi_append_content( (char const *)src, (int)n );
211
return 0;
212
}
213
214
/*
215
** Returns a string in the form FOSSIL-XXXX, where XXXX is a
216
** left-zero-padded value of code. The returned buffer is static, and
217
** must be copied if needed for later. The returned value will always
218
** be 11 bytes long (not including the trailing NUL byte).
219
**
220
** In practice we will only ever call this one time per app execution
221
** when constructing the JSON response envelope, so the static buffer
222
** "shouldn't" be a problem.
223
**
224
*/
225
char const * json_rc_cstr( int code ){
226
enum { BufSize = 13 };
227
static char buf[BufSize] = {'F','O','S','S','I','L','-',0};
228
assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
229
sqlite3_snprintf((int)BufSize, buf+7,"%04d", code);
230
return buf;
231
}
232
233
/*
234
** Adds v to the API-internal cleanup mechanism. key is ignored
235
** (legacy) but might be re-introduced and "should" be a unique
236
** (app-wide) value. Failure to insert an item may be caused by any
237
** of the following:
238
**
239
** - Allocation error.
240
** - g.json.gc.a is NULL
241
** - key is NULL or empty.
242
**
243
** Returns 0 on success.
244
**
245
** Ownership of v is transferred to (or shared with) g.json.gc, and v
246
** will be valid until that object is cleaned up or some internal code
247
** incorrectly removes it from the gc (which we never do). If this
248
** function fails, it is fatal to the app (as it indicates an
249
** allocation error (more likely than not) or a serious internal error
250
** such as numeric overflow).
251
*/
252
void json_gc_add( char const * key, cson_value * v ){
253
int const rc = cson_array_append( g.json.gc.a, v );
254
255
assert( NULL != g.json.gc.a );
256
if( 0 != rc ){
257
cson_value_free( v );
258
}
259
assert( (0==rc) && "Adding item to GC failed." );
260
if(0!=rc){
261
fprintf(stderr,"%s: FATAL: alloc error.\n", g.argv[0])
262
/* reminder: allocation error is the only reasonable cause of
263
error here, provided g.json.gc.a and v are not NULL.
264
*/
265
;
266
fossil_exit(1)/*not fossil_panic() b/c it might land us somewhere
267
where this function is called again.
268
*/;
269
}
270
}
271
272
273
/*
274
** Returns the value of json_rc_cstr(code) as a new JSON
275
** string, which is owned by the caller and must eventually
276
** be cson_value_free()d or transferred to a JSON container.
277
*/
278
cson_value * json_rc_string( int code ){
279
return cson_value_new_string( json_rc_cstr(code), 11 );
280
}
281
282
cson_value * json_new_string( char const * str ){
283
return str
284
? cson_value_new_string(str,strlen(str))
285
: NULL;
286
}
287
288
cson_value * json_new_string_f( char const * fmt, ... ){
289
cson_value * v;
290
char * zStr;
291
va_list vargs;
292
va_start(vargs,fmt);
293
zStr = vmprintf(fmt,vargs);
294
va_end(vargs);
295
v = cson_value_new_string(zStr, strlen(zStr));
296
fossil_free(zStr);
297
return v;
298
}
299
300
cson_value * json_new_int( i64 v ){
301
return cson_value_new_integer((cson_int_t)v);
302
}
303
304
/*
305
** Gets a POST/POST.payload/GET/COOKIE/ENV value. The returned memory
306
** is owned by the g.json object (one of its sub-objects). Returns
307
** NULL if no match is found.
308
**
309
** ENV means the system environment (getenv()).
310
**
311
** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV.
312
**
313
** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE,
314
** ENV, but the amalgamation of the GET/POST vars makes it effectively
315
** impossible to do that. Since fossil only uses one cookie, cookie
316
** precedence isn't a real/high-priority problem.
317
*/
318
cson_value * json_getenv( char const * zKey ){
319
cson_value * rc;
320
rc = g.json.reqPayload.o
321
? cson_object_get( g.json.reqPayload.o, zKey )
322
: NULL;
323
if(rc){
324
return rc;
325
}
326
rc = cson_object_get( g.json.param.o, zKey );
327
if( rc ){
328
return rc;
329
}
330
rc = cson_object_get( g.json.post.o, zKey );
331
if(rc){
332
return rc;
333
}else{
334
char const * cv = PD(zKey,NULL);
335
if(!cv && !g.isHTTP){
336
/* reminder to self: in CLI mode i'd like to try
337
find_option(zKey,NULL,XYZ) here, but we don't have a sane
338
default for the XYZ param here.
339
*/
340
cv = fossil_getenv(zKey);
341
}
342
if(cv){/*transform it to JSON for later use.*/
343
/* use sscanf() to figure out if it's an int,
344
and transform it to JSON int if it is.
345
346
FIXME: use strtol(), since it has more accurate
347
error handling.
348
*/
349
int intVal = -1;
350
char endOfIntCheck;
351
int const scanRc = sscanf(cv,"%d%c",&intVal, &endOfIntCheck)
352
/* The %c bit there is to make sure that we don't accept 123x
353
as a number. sscanf() returns the number of tokens
354
successfully parsed, so an RC of 1 will be correct for "123"
355
but "123x" will have RC==2.
356
357
But it appears to not be working that way :/
358
*/
359
;
360
if(1==scanRc){
361
json_setenv( zKey, cson_value_new_integer(intVal) );
362
}else{
363
rc = cson_value_new_string(cv,strlen(cv));
364
json_setenv( zKey, rc );
365
}
366
return rc;
367
}else{
368
return NULL;
369
}
370
}
371
}
372
373
/*
374
** Wrapper around json_getenv() which...
375
**
376
** If it finds a value and that value is-a JSON number or is a string
377
** which looks like an integer or is-a JSON bool/null then it is
378
** converted to an int. If none of those apply then dflt is returned.
379
*/
380
int json_getenv_int(char const * pKey, int dflt ){
381
cson_value const * v = json_getenv(pKey);
382
const cson_type_id type = v ? cson_value_type_id(v) : CSON_TYPE_UNDEF;
383
switch(type){
384
case CSON_TYPE_INTEGER:
385
case CSON_TYPE_DOUBLE:
386
return (int)cson_value_get_integer(v);
387
case CSON_TYPE_STRING: {
388
char const * sv = cson_string_cstr(cson_value_get_string(v));
389
assert( (NULL!=sv) && "This is quite unexpected." );
390
return sv ? atoi(sv) : dflt;
391
}
392
case CSON_TYPE_BOOL:
393
return cson_value_get_bool(v) ? 1 : 0;
394
case CSON_TYPE_NULL:
395
return 0;
396
default:
397
return dflt;
398
}
399
}
400
401
402
/*
403
** Wrapper around json_getenv() which tries to evaluate a payload/env
404
** value as a boolean. Uses mostly the same logic as
405
** json_getenv_int(), with the addition that string values which
406
** either start with a digit 1..9 or the letters [tTyY] are considered
407
** to be true. If this function cannot find a matching key/value then
408
** dflt is returned. e.g. if it finds the key but the value is-a
409
** Object then dftl is returned.
410
**
411
** If an entry is found, this function guarantees that it will return
412
** either 0 or 1, as opposed to "0 or non-zero", so that clients can
413
** pass a different value as dflt. Thus they can use, e.g. -1 to know
414
** whether or not this function found a match (it will return -1 in
415
** that case).
416
*/
417
int json_getenv_bool(char const * pKey, int dflt ){
418
cson_value const * v = json_getenv(pKey);
419
const cson_type_id type = v ? cson_value_type_id(v) : CSON_TYPE_UNDEF;
420
switch(type){
421
case CSON_TYPE_INTEGER:
422
case CSON_TYPE_DOUBLE:
423
return cson_value_get_integer(v) ? 1 : 0;
424
case CSON_TYPE_STRING: {
425
char const * sv = cson_string_cstr(cson_value_get_string(v));
426
assert( (NULL!=sv) && "This is quite unexpected." );
427
if(!*sv || ('0'==*sv)){
428
return 0;
429
}else{
430
return ((('1'<=*sv) && ('9'>=*sv))
431
|| ('t'==*sv) || ('T'==*sv)
432
|| ('y'==*sv) || ('Y'==*sv)
433
)
434
? 1 : 0;
435
}
436
}
437
case CSON_TYPE_BOOL:
438
return cson_value_get_bool(v) ? 1 : 0;
439
case CSON_TYPE_NULL:
440
return 0;
441
default:
442
return dflt;
443
}
444
}
445
446
/*
447
** Returns the string form of a json_getenv() value, but ONLY If that
448
** value is-a String. Non-strings are not converted to strings for
449
** this purpose. Returned memory is owned by g.json or fossil and is
450
** valid until end-of-app or the given key is replaced in fossil's
451
** internals via cgi_replace_parameter() and friends or json_setenv().
452
*/
453
char const * json_getenv_cstr( char const * zKey ){
454
return cson_value_get_cstr( json_getenv(zKey) );
455
}
456
457
/*
458
** An extended form of find_option() which tries to look up a combo
459
** GET/POST/CLI argument.
460
**
461
** zKey must be the GET/POST parameter key. zCLILong must be the "long
462
** form" CLI flag (NULL means to use zKey). zCLIShort may be NULL or
463
** the "short form" CLI flag (if NULL, no short form is used).
464
**
465
** If argPos is >=0 and no other match is found,
466
** json_command_arg(argPos) is also checked.
467
**
468
** On error (no match found) NULL is returned.
469
**
470
** This ONLY works for String JSON/GET/CLI values, not JSON
471
** booleans and whatnot.
472
*/
473
char const * json_find_option_cstr2(char const * zKey,
474
char const * zCLILong,
475
char const * zCLIShort,
476
int argPos){
477
char const * rc = NULL;
478
assert(NULL != zKey);
479
if(!g.isHTTP){
480
rc = find_option(zCLILong ? zCLILong : zKey,
481
zCLIShort, 1);
482
}
483
if(!rc && fossil_has_json()){
484
rc = json_getenv_cstr(zKey);
485
if(!rc && zCLIShort){
486
rc = cson_value_get_cstr( cson_object_get( g.json.param.o, zCLIShort) );
487
}
488
}
489
if(!rc && (argPos>=0)){
490
rc = json_command_arg((unsigned char)argPos);
491
}
492
return rc;
493
}
494
495
/*
496
** Short-hand form of json_find_option_cstr2(zKey,zCLILong,zCLIShort,-1).
497
*/
498
char const * json_find_option_cstr(char const * zKey,
499
char const * zCLILong,
500
char const * zCLIShort){
501
return json_find_option_cstr2(zKey, zCLILong, zCLIShort, -1);
502
}
503
504
/*
505
** The boolean equivalent of json_find_option_cstr().
506
** If the option is not found, dftl is returned.
507
*/
508
int json_find_option_bool(char const * zKey,
509
char const * zCLILong,
510
char const * zCLIShort,
511
int dflt ){
512
int rc = -1;
513
if(!g.isHTTP){
514
if(NULL != find_option(zCLILong ? zCLILong : zKey,
515
zCLIShort, 0)){
516
rc = 1;
517
}
518
}
519
if((-1==rc) && fossil_has_json()){
520
rc = json_getenv_bool(zKey,-1);
521
}
522
return (-1==rc) ? dflt : rc;
523
}
524
525
/*
526
** The integer equivalent of json_find_option_cstr2().
527
** If the option is not found, dftl is returned.
528
*/
529
int json_find_option_int(char const * zKey,
530
char const * zCLILong,
531
char const * zCLIShort,
532
int dflt ){
533
enum { Magic = -1947854832 };
534
int rc = Magic;
535
if(!g.isHTTP){
536
/* FIXME: use strtol() for better error/dflt handling. */
537
char const * opt = find_option(zCLILong ? zCLILong : zKey,
538
zCLIShort, 1);
539
if(NULL!=opt){
540
rc = atoi(opt);
541
}
542
}
543
if(Magic==rc){
544
rc = json_getenv_int(zKey,Magic);
545
}
546
return (Magic==rc) ? dflt : rc;
547
}
548
549
550
/*
551
** Adds v to g.json.param.o using the given key. May cause any prior
552
** item with that key to be destroyed (depends on current reference
553
** count for that value). On success, transfers (or shares) ownership
554
** of v to (or with) g.json.param.o. On error ownership of v is not
555
** modified.
556
*/
557
int json_setenv( char const * zKey, cson_value * v ){
558
return cson_object_set( g.json.param.o, zKey, v );
559
}
560
561
/*
562
** Guesses a RESPONSE Content-Type value based (primarily) on the
563
** HTTP_ACCEPT header.
564
**
565
** It will try to figure out if the client can support
566
** application/json, text/javascript, and will fall back to
567
** text/plain if it cannot figure out anything more specific.
568
**
569
** Returned memory is static and immutable, but if the environment
570
** changes after calling this then subsequent calls to this function
571
** might return different (also static/immutable) values.
572
*/
573
char const * json_guess_content_type(){
574
char const * cset;
575
char doUtf8;
576
cset = PD("HTTP_ACCEPT_CHARSET",NULL);
577
doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
578
? 1 : 0;
579
if( g.json.jsonp ){
580
return doUtf8
581
? "text/javascript; charset=utf-8"
582
: "text/javascript";
583
}else{
584
/*
585
Content-type
586
587
If the browser does not sent an ACCEPT for application/json
588
then we fall back to text/plain.
589
*/
590
char const * cstr;
591
cstr = PD("HTTP_ACCEPT",NULL);
592
if( NULL == cstr ){
593
return doUtf8
594
? "application/json; charset=utf-8"
595
: "application/json";
596
}else{
597
if( strstr( cstr, "application/json" )
598
|| strstr( cstr, "*/*" ) ){
599
return doUtf8
600
? "application/json; charset=utf-8"
601
: "application/json";
602
}else{
603
return "text/plain";
604
}
605
}
606
}
607
}
608
609
/*
610
** Given a request CONTENT_TYPE value, this function returns true
611
** if it is of a type which the JSON API can ostensibly read.
612
**
613
** It accepts any of application/json, text/plain,
614
** application/javascript, or text/javascript. The former is
615
** preferred, but was not widespread when this API was initially
616
** built, so the latter forms are permitted as fallbacks.
617
*/
618
int json_can_consume_content_type(const char * zType){
619
return fossil_strcmp(zType, "application/json")==0
620
|| fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/
621
|| fossil_strcmp(zType,"text/javascript")==0
622
|| fossil_strcmp(zType,"application/javascript")==0;
623
}
624
625
/*
626
** Sends pResponse to the output stream as the response object. This
627
** function does no validation of pResponse except to assert() that it
628
** is not NULL. The caller is responsible for ensuring that it meets
629
** API response envelope conventions.
630
**
631
** In CLI mode pResponse is sent to stdout immediately. In HTTP
632
** mode pResponse replaces any current CGI content but cgi_reply()
633
** is not called to flush the output.
634
**
635
** If g.json.jsonp is not NULL then the content type is set to
636
** text/javascript and the output is wrapped in a jsonp
637
** wrapper.
638
**
639
** This function works only the first time it is called. It "should
640
** not" ever be called more than once but certain calling
641
** constellations might trigger that, in which case the second and
642
** subsequent calls are no-ops.
643
*/
644
void json_send_response( cson_value const * pResponse ){
645
static int once = 0;
646
assert( NULL != pResponse );
647
if( once++ ) return;
648
if( g.isHTTP ){
649
cgi_reset_content();
650
if( g.json.jsonp ){
651
cgi_set_content_type("text/javascript");
652
cgi_printf("%s(",g.json.jsonp);
653
}
654
cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt );
655
if( g.json.jsonp ){
656
cgi_append_content(")",1);
657
}
658
}else{/*CLI mode*/
659
if( g.json.jsonp ){
660
fprintf(stdout,"%s(",g.json.jsonp);
661
}
662
cson_output_FILE( pResponse, stdout, &g.json.outOpt );
663
if( g.json.jsonp ){
664
fwrite(")\n", 2, 1, stdout);
665
}
666
}
667
}
668
669
/*
670
** Returns the current request's JSON authentication token, or NULL if
671
** none is found. The token's memory is owned by (or shared with)
672
** g.json.
673
**
674
** If an auth token is found in the GET/POST request data then fossil
675
** is given that data for use in authentication for this
676
** session. i.e. the GET/POST data overrides fossil's authentication
677
** cookie value (if any) and also works with clients which do not
678
** support cookies.
679
**
680
** Must be called once before login_check_credentials() is called or
681
** we will not be able to replace fossil's internal idea of the auth
682
** info in time (and future changes to that state may cause unexpected
683
** results).
684
**
685
** The result of this call are cached for future calls.
686
**
687
** Special case: if g.useLocalauth is true (i.e. the --localauth flag
688
** was used to start the fossil server instance) and the current
689
** connection is "local", any authToken provided by the user is
690
** ignored and no new token is created: localauth mode trumps the
691
** authToken.
692
*/
693
cson_value * json_auth_token(){
694
assert(g.json.gc.a && "json_bootstrap_early() was not called!");
695
if( g.json.authToken==0 && g.noPswd==0
696
/* g.noPswd!=0 means the user was logged in via a local
697
connection using --localauth. */
698
){
699
/* Try to get an authorization token from GET parameter, POSTed
700
JSON, or fossil cookie (in that order). */
701
g.json.authToken = json_getenv(FossilJsonKeys.authToken);
702
if(g.json.authToken
703
&& cson_value_is_string(g.json.authToken)
704
&& !PD(login_cookie_name(),NULL)){
705
/* tell fossil to use this login info.
706
707
FIXME?: because the JSON bits don't carry around
708
login_cookie_name(), there is(?) a potential(?) login hijacking
709
window here. We may need to change the JSON auth token to be in
710
the form: login_cookie_name()=...
711
712
Then again, the hardened cookie value helps ensure that
713
only a proper key/value match is valid.
714
*/
715
cgi_replace_parameter( login_cookie_name(),
716
cson_value_get_cstr(g.json.authToken) );
717
}else if( g.isHTTP ){
718
/* try fossil's conventional cookie. */
719
/* Reminder: chicken/egg scenario regarding db access in CLI
720
mode because login_cookie_name() needs the db. CLI
721
mode does not use any authentication, so we don't need
722
to support it here.
723
*/
724
char const * zCookie = P(login_cookie_name());
725
if( zCookie && *zCookie ){
726
/* Transfer fossil's cookie to JSON for downstream convenience... */
727
cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
728
json_gc_add( FossilJsonKeys.authToken, v );
729
g.json.authToken = v;
730
}
731
}
732
}
733
return g.json.authToken;
734
}
735
736
/*
737
** If g.json.reqPayload.o is NULL then NULL is returned, else the
738
** given property is searched for in the request payload. If found it
739
** is returned. The returned value is owned by (or shares ownership
740
** with) g.json, and must NOT be cson_value_free()'d by the
741
** caller.
742
*/
743
cson_value * json_req_payload_get(char const *pKey){
744
return g.json.reqPayload.o
745
? cson_object_get(g.json.reqPayload.o,pKey)
746
: NULL;
747
}
748
749
750
/*
751
** Returns non-zero if the json_bootstrap_early() function has already
752
** been called. In general, this function should be used sparingly,
753
** e.g. from low-level support functions like fossil_warning() where
754
** there is genuine uncertainty about whether (or not) the JSON setup
755
** has already been called.
756
*/
757
int json_is_bootstrapped_early(void){
758
return ((g.json.gc.v != NULL) && (g.json.gc.a != NULL));
759
}
760
761
/*
762
** Initializes some JSON bits which need to be initialized relatively
763
** early on. It should be called by any routine which might need to
764
** call into JSON relatively early on in the init process.
765
** Specifically, early on in cgi_init() and json_cmd_top(), but also
766
** from any error reporting routines which might be triggered (early
767
** on in those functions).
768
**
769
** Initializes g.json.gc and g.json.param. This code does not (and
770
** must not) rely on any of the fossil environment having been set
771
** up. e.g. it must not use cgi_parameter() and friends because this
772
** must be called before those data are initialized.
773
**
774
** If called multiple times, calls after the first are a no-op.
775
*/
776
void json_bootstrap_early(void){
777
cson_value * v;
778
779
if(g.json.gc.v!=NULL){
780
/* Avoid multiple bootstrappings. */
781
return;
782
}
783
g.json.timerId = fossil_timer_start();
784
/* g.json.gc is our "garbage collector" - where we put JSON values
785
which need a long lifetime but don't have a logical parent to put
786
them in. */
787
v = cson_value_new_array();
788
g.json.gc.v = v;
789
assert(0 != g.json.gc.v);
790
g.json.gc.a = cson_value_get_array(v);
791
assert(0 != g.json.gc.a);
792
cson_value_add_reference(v)
793
/* Needed to allow us to include this value in other JSON
794
containers without transferring ownership to those containers.
795
All other persistent g.json.XXX.v values get appended to
796
g.json.gc.a, and therefore already have a live reference
797
for this purpose. */
798
;
799
800
/*
801
g.json.param holds the JSONized counterpart of fossil's
802
cgi_parameter_xxx() family of data. We store them as JSON, as
803
opposed to using fossil's data directly, because we can retain
804
full type information for data this way (as opposed to it always
805
being of type string).
806
*/
807
v = cson_value_new_object();
808
g.json.param.v = v;
809
g.json.param.o = cson_value_get_object(v);
810
json_gc_add("$PARAMS", v);
811
}
812
813
/*
814
** Appends a warning object to the (pending) JSON response.
815
**
816
** Code must be a FSL_JSON_W_xxx value from the FossilJsonCodes enum.
817
**
818
** A Warning object has this JSON structure:
819
**
820
** { "code":integer, "text":"string" }
821
**
822
** But the text part is optional.
823
**
824
** If msg is non-NULL and not empty then it is used as the "text"
825
** property's value. It is copied, and need not refer to static
826
** memory.
827
**
828
** CURRENTLY this code only allows a given warning code to be
829
** added one time, and elides subsequent warnings. The intention
830
** is to remove that burden from loops which produce warnings.
831
**
832
** FIXME: if msg is NULL then use a standard string for
833
** the given code. If !*msg then elide the "text" property,
834
** for consistency with how json_err() works.
835
*/
836
void json_warn( int code, char const * fmt, ... ){
837
cson_object * obj = NULL;
838
assert( (code>FSL_JSON_W_START)
839
&& (code<FSL_JSON_W_END)
840
&& "Invalid warning code.");
841
assert(g.json.gc.a && "json_bootstrap_early() was not called!");
842
if(!g.json.warnings){
843
g.json.warnings = cson_new_array();
844
assert((NULL != g.json.warnings) && "Alloc error.");
845
json_gc_add("$WARNINGS",cson_array_value(g.json.warnings));
846
}
847
obj = cson_new_object();
848
cson_array_append(g.json.warnings, cson_object_value(obj));
849
cson_object_set(obj,"code",cson_value_new_integer(code));
850
if(fmt && *fmt){
851
/* FIXME: treat NULL fmt as standard warning message for
852
the code, but we don't have those yet.
853
*/
854
va_list vargs;
855
char * msg;
856
va_start(vargs,fmt);
857
msg = vmprintf(fmt,vargs);
858
va_end(vargs);
859
cson_object_set(obj,"text", cson_value_new_string(msg,strlen(msg)));
860
fossil_free(msg);
861
}
862
}
863
864
/*
865
** Splits zStr (which must not be NULL) into tokens separated by the
866
** given separator character. If doDeHttp is true then each element
867
** will be passed through dehttpize(), otherwise they are used
868
** as-is. Note that tokenization happens before dehttpize(),
869
** which is significant if the encoded tokens might contain the
870
** separator character.
871
**
872
** Each new element is appended to the given target array object,
873
** which must not be NULL and ownership of it is not changed by this
874
** call.
875
**
876
** On success, returns the number of tokens _encountered_. On error a
877
** NEGATIVE number is returned - its absolute value is the number of
878
** tokens encountered (i.e. it reveals which token in zStr was
879
** problematic).
880
**
881
** Achtung: leading and trailing whitespace of elements are elided.
882
**
883
** Achtung: empty elements will be skipped, meaning consecutive empty
884
** elements are collapsed.
885
*/
886
int json_string_split( char const * zStr,
887
char separator,
888
int doDeHttp,
889
cson_array * target ){
890
char const * p = zStr /* current byte */;
891
char const * head /* current start-of-token */;
892
unsigned int len = 0 /* current token's length */;
893
int rc = 0 /* return code (number of added elements)*/;
894
assert( zStr && target );
895
while( fossil_isspace(*p) ){
896
++p;
897
}
898
head = p;
899
for( ; ; ++p){
900
if( !*p || (separator == *p) ){
901
if( len ){/* append head..(head+len) as next array
902
element. */
903
cson_value * part = NULL;
904
char * zPart = NULL;
905
++rc;
906
assert( head != p );
907
zPart = (char*)fossil_malloc(len+1);
908
memcpy(zPart, head, len);
909
zPart[len] = 0;
910
if(doDeHttp){
911
dehttpize(zPart);
912
}
913
if( *zPart ){
914
/* should only fail if someone manages to url-encoded a NUL byte */
915
part = cson_value_new_string(zPart, strlen(zPart));
916
if( 0 != cson_array_append( target, part ) ){
917
cson_value_free(part);
918
rc = -rc;
919
break;
920
}
921
}else{
922
assert(0 && "i didn't think this was possible!");
923
fprintf(stderr,"%s:%d: My God! It's full of stars!\n",
924
__FILE__, __LINE__);
925
fossil_exit(1)
926
/* Not fossil_panic() b/c this code needs to be able to
927
run before some of the fossil/json bits are initialized,
928
and fossil_panic() calls into the JSON API.
929
*/
930
;
931
}
932
fossil_free(zPart);
933
len = 0;
934
}
935
if( !*p ){
936
break;
937
}
938
head = p+1;
939
while( *head && fossil_isspace(*head) ){
940
++head;
941
++p;
942
}
943
if(!*head){
944
break;
945
}
946
continue;
947
}
948
++len;
949
}
950
return rc;
951
}
952
953
/*
954
** Wrapper around json_string_split(), taking the same first 3
955
** parameters as this function, but returns the results as a JSON
956
** Array (if splitting produced tokens) or NULL (if splitting failed
957
** in any way or produced no tokens).
958
**
959
** The returned value is owned by the caller. If not NULL then it
960
** _will_ have a JSON type of Array.
961
*/
962
cson_value * json_string_split2( char const * zStr,
963
char separator,
964
int doDeHttp ){
965
cson_array * a = cson_new_array();
966
int rc = json_string_split( zStr, separator, doDeHttp, a );
967
if( 0>=rc ){
968
cson_free_array(a);
969
a = NULL;
970
}
971
return a ? cson_array_value(a) : NULL;
972
}
973
974
975
/*
976
** Performs some common initialization of JSON-related state. Must be
977
** called by the json_page_top() and json_cmd_top() dispatching
978
** functions to set up the JSON stat used by the dispatched functions.
979
**
980
** Implicitly sets up the login information state in CGI mode, but
981
** does not perform any permissions checking. It _might_ (haven't
982
** tested this) die with an error if an auth cookie is malformed.
983
**
984
** This must be called by the top-level JSON command dispatching code
985
** before they do any work.
986
**
987
** This must only be called once, or an assertion may be triggered.
988
*/
989
void json_bootstrap_late(){
990
static char once = 0 /* guard against multiple runs */;
991
char const * zPath = P("PATH_INFO");
992
assert(g.json.gc.a && "json_bootstrap_early() was not called!");
993
assert( (0==once) && "json_bootstrap_late() called too many times!");
994
if( once ){
995
return;
996
}else{
997
once = 1;
998
}
999
assert(g.json.isJsonMode
1000
&& "g.json.isJsonMode should have been set up by now.");
1001
g.json.resultCode = 0;
1002
g.json.cmd.offset = -1;
1003
g.json.jsonp = PD("jsonp",NULL)
1004
/* FIXME: do some sanity checking on g.json.jsonp and ignore it
1005
if it is not halfway reasonable.
1006
*/
1007
;
1008
if( !g.isHTTP && g.fullHttpReply ){
1009
/* workaround for server mode, so we see it as CGI mode. */
1010
g.isHTTP = 1;
1011
}
1012
1013
if(g.isHTTP){
1014
cgi_set_content_type(json_guess_content_type())
1015
/* reminder: must be done after g.json.jsonp is initialized */
1016
;
1017
#if 0
1018
/* Calling this seems to trigger an SQLITE_MISUSE warning???
1019
Maybe it's not legal to set the logger more than once?
1020
*/
1021
sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0)
1022
/* avoids debug messages on stderr in JSON mode */
1023
;
1024
#endif
1025
}
1026
1027
g.json.cmd.v = cson_value_new_array();
1028
g.json.cmd.a = cson_value_get_array(g.json.cmd.v);
1029
json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v );
1030
/*
1031
The following if/else block translates the PATH_INFO path (in
1032
CLI/server modes) or g.argv (CLI mode) into an internal list so
1033
that we can simplify command dispatching later on.
1034
1035
Note that translating g.argv this way is overkill but allows us to
1036
avoid CLI-only special-case handling in other code, e.g.
1037
json_command_arg().
1038
*/
1039
if( zPath ){/* Either CGI or server mode... */
1040
/* Translate PATH_INFO into JSON array for later convenience. */
1041
json_string_split(zPath, '/', 1, g.json.cmd.a);
1042
}else{/* assume CLI mode */
1043
int i;
1044
char const * arg;
1045
cson_value * part;
1046
for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
1047
arg = g.argv[i];
1048
if( !arg || !*arg ){
1049
continue;
1050
}
1051
if('-' == *arg){
1052
/* workaround to skip CLI args so that
1053
json_command_arg() does not see them.
1054
This assumes that all arguments come LAST
1055
on the command line.
1056
*/
1057
break;
1058
}
1059
part = cson_value_new_string(arg,strlen(arg));
1060
cson_array_append(g.json.cmd.a, part);
1061
}
1062
}
1063
1064
while(!g.isHTTP){
1065
/* Simulate JSON POST data via input file. Pedantic reminder:
1066
error handling does not honor user-supplied g.json.outOpt
1067
because outOpt cannot (generically) be configured until after
1068
POST-reading is finished.
1069
*/
1070
FILE * inFile = NULL;
1071
char const * jfile = find_option("json-input",NULL,1);
1072
Blob json = BLOB_INITIALIZER;
1073
if(!jfile || !*jfile){
1074
break;
1075
}
1076
inFile = (0==strcmp("-",jfile))
1077
? stdin
1078
: fossil_fopen(jfile,"rb");
1079
if(!inFile){
1080
g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
1081
fossil_fatal("Could not open JSON file [%s].",jfile)
1082
/* Does not return. */
1083
;
1084
}
1085
blob_read_from_channel(&json, inFile, -1);
1086
fossil_fclose(inFile);
1087
cgi_parse_POST_JSON(&json);
1088
blob_reset(&json);
1089
break;
1090
}
1091
1092
/* g.json.reqPayload exists only to simplify some of our access to
1093
the request payload. We currently only use this in the context of
1094
Object payloads, not Arrays, strings, etc.
1095
*/
1096
g.json.reqPayload.v = cson_object_get( g.json.post.o,FossilJsonKeys.payload );
1097
if( g.json.reqPayload.v ){
1098
g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v )
1099
/* g.json.reqPayload.o may legally be NULL, which means only that
1100
g.json.reqPayload.v is-not-a Object.
1101
*/;
1102
}
1103
1104
/* Anything which needs json_getenv() and friends should go after
1105
this point.
1106
*/
1107
1108
if(1 == cson_array_length_get(g.json.cmd.a)){
1109
/* special case: if we're at the top path, look for
1110
a "command" request arg which specifies which command
1111
to run.
1112
*/
1113
char const * cmd = json_getenv_cstr("command");
1114
if(cmd){
1115
json_string_split(cmd, '/', 0, g.json.cmd.a);
1116
g.json.cmd.commandStr = cmd;
1117
}
1118
}
1119
1120
1121
if(!g.json.jsonp){
1122
g.json.jsonp = json_find_option_cstr("jsonp",NULL,NULL);
1123
}
1124
if(!g.isHTTP){
1125
g.json.errorDetailParanoia = 0;/*disable error code dumb-down for CLI mode*/
1126
}
1127
1128
{/* set up JSON output formatting options. */
1129
int indent = -1;
1130
indent = json_find_option_int("indent",NULL,"I",-1);
1131
g.json.outOpt.indentation = (0>indent)
1132
? (g.isHTTP ? 0 : 1)
1133
: (unsigned char)indent;
1134
g.json.outOpt.addNewline = g.isHTTP
1135
? 0
1136
: (g.json.jsonp ? 0 : 1);
1137
}
1138
1139
if( g.isHTTP ){
1140
json_auth_token()/* will copy our auth token, if any, to fossil's
1141
core, which we need before we call
1142
login_check_credentials(). */;
1143
login_check_credentials()/* populates g.perm */;
1144
}
1145
else{
1146
/* FIXME: we need an option which allows us to skip this. At least
1147
one known command (/json/version) does not need an opened
1148
repo. The problem here is we cannot know which functions need
1149
it from here (because command dispatching hasn't yet happened)
1150
and all other commands rely on the repo being opened before
1151
they are called. A textbook example of lack of foresight :/.
1152
*/
1153
db_find_and_open_repository(OPEN_ANY_SCHEMA,0);
1154
}
1155
}
1156
1157
/*
1158
** Returns the ndx'th item in the "command path", where index 0 is the
1159
** position of the "json" part of the path. Returns NULL if ndx is out
1160
** of bounds or there is no "json" path element.
1161
**
1162
** In CLI mode the "path" is the list of arguments (skipping argv[0]).
1163
** In server/CGI modes the path is taken from PATH_INFO.
1164
**
1165
** The returned bytes are owned by g.json.cmd.v and _may_ be
1166
** invalidated if that object is modified (depending on how it is
1167
** modified).
1168
**
1169
** Note that CLI options are not included in the command path. Use
1170
** find_option() to get those.
1171
**
1172
*/
1173
char const * json_command_arg(unsigned short ndx){
1174
cson_array * ar = g.json.cmd.a;
1175
assert((NULL!=ar) && "Internal error. Was json_bootstrap_late() called?");
1176
assert((g.argc>1) &&"Internal error - we never should have gotten this far.");
1177
if( g.json.cmd.offset < 0 ){
1178
/* first-time setup. */
1179
short i = 0;
1180
#define NEXT cson_string_cstr( \
1181
cson_value_get_string( \
1182
cson_array_get(ar,i) \
1183
))
1184
char const * tok = NEXT;
1185
while( tok ){
1186
if( g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
1187
? (0==strncmp("json",tok,4))
1188
: (0==strcmp(g.argv[1],tok))
1189
){
1190
g.json.cmd.offset = i;
1191
break;
1192
}
1193
++i;
1194
tok = NEXT;
1195
}
1196
}
1197
#undef NEXT
1198
if(g.json.cmd.offset < 0){
1199
return NULL;
1200
}else{
1201
ndx = g.json.cmd.offset + ndx;
1202
return cson_string_cstr(cson_value_get_string(
1203
cson_array_get( ar, g.json.cmd.offset + ndx )));
1204
}
1205
}
1206
1207
/* Returns the C-string form of json_auth_token(), or NULL
1208
** if json_auth_token() returns NULL.
1209
*/
1210
char const * json_auth_token_cstr(){
1211
return cson_value_get_cstr( json_auth_token() );
1212
}
1213
1214
/*
1215
** Returns the JsonPageDef with the given name, or NULL if no match is
1216
** found.
1217
**
1218
** head must be a pointer to an array of JsonPageDefs in which the
1219
** last entry has a NULL name.
1220
*/
1221
JsonPageDef const * json_handler_for_name( char const * name,
1222
JsonPageDef const * head ){
1223
JsonPageDef const * pageDef = head;
1224
assert( head != NULL );
1225
if(name && *name) for( ; pageDef->name; ++pageDef ){
1226
if( 0 == strcmp(name, pageDef->name) ){
1227
return pageDef;
1228
}
1229
}
1230
return NULL;
1231
}
1232
1233
/*
1234
** Given a Fossil/JSON result code, this function "dumbs it down"
1235
** according to the current value of g.json.errorDetailParanoia. The
1236
** dumbed-down value is returned.
1237
**
1238
** This function assert()s that code is in the inclusive range 0 to
1239
** 9999.
1240
**
1241
** Note that WARNING codes (1..999) are never dumbed down.
1242
**
1243
*/
1244
static int json_dumbdown_rc( int code ){
1245
if(!g.json.errorDetailParanoia
1246
|| !code
1247
|| ((code>=FSL_JSON_W_START) && (code<FSL_JSON_W_END))){
1248
return code;
1249
}else{
1250
int modulo = 0;
1251
assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
1252
switch( g.json.errorDetailParanoia ){
1253
case 1: modulo = 10; break;
1254
case 2: modulo = 100; break;
1255
case 3: modulo = 1000; break;
1256
default: break;
1257
}
1258
if( modulo ) code = code - (code % modulo);
1259
return code;
1260
}
1261
}
1262
1263
/*
1264
** Convenience routine which converts a Julian time value into a Unix
1265
** Epoch timestamp. Requires the db, so this cannot be used before the
1266
** repo is opened (will trigger a fatal error in db_xxx()). The returned
1267
** value is owned by the caller.
1268
*/
1269
cson_value * json_julian_to_timestamp(double j){
1270
return cson_value_new_integer((cson_int_t)
1271
db_int64(0,"SELECT cast(strftime('%%s',%lf) as int)",j)
1272
);
1273
}
1274
1275
/*
1276
** Returns a timestamp value.
1277
*/
1278
cson_int_t json_timestamp(){
1279
return (cson_int_t)time(0);
1280
}
1281
1282
/*
1283
** Returns a new JSON value (owned by the caller) representing
1284
** a timestamp. If timeVal is < 0 then time(0) is used to fetch
1285
** the time, else timeVal is used as-is. The returned value is
1286
** owned by the caller.
1287
*/
1288
cson_value * json_new_timestamp(cson_int_t timeVal){
1289
return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal);
1290
}
1291
1292
/*
1293
** Internal helper for json_create_response(). Appends the first
1294
** g.json.dispatchDepth elements of g.json.cmd.a, skipping the first
1295
** one (the "json" part), to a string and returns that string value
1296
** (which is owned by the caller).
1297
*/
1298
static cson_value * json_response_command_path(){
1299
if(!g.json.cmd.a){
1300
return NULL;
1301
}else{
1302
cson_value * rc = NULL;
1303
Blob path = empty_blob;
1304
unsigned int aLen = g.json.dispatchDepth+1;
1305
/*cson_array_length_get(g.json.cmd.a);*/
1306
unsigned int i = 1;
1307
for( ; i < aLen; ++i ){
1308
char const * part = cson_string_cstr(cson_value_get_string(
1309
cson_array_get(g.json.cmd.a, i)));
1310
if(!part){
1311
#if 1
1312
fossil_warning("Iterating further than expected in %s.",
1313
__FILE__);
1314
#endif
1315
break;
1316
}
1317
blob_appendf(&path,"%s%s", (i>1 ? "/": ""), part);
1318
}
1319
rc = json_new_string((blob_size(&path)>0)
1320
? blob_buffer(&path)
1321
: "")
1322
/* reminder; we need an empty string instead of NULL
1323
in this case, to avoid what outwardly looks like
1324
(but is not) an allocation error in
1325
json_create_response().
1326
*/
1327
;
1328
blob_reset(&path);
1329
return rc;
1330
}
1331
}
1332
1333
/*
1334
** Returns a JSON Object representation of the global g object.
1335
** Returned value is owned by the caller.
1336
*/
1337
cson_value * json_g_to_json(){
1338
cson_object * o = NULL;
1339
cson_object * pay = NULL;
1340
pay = o = cson_new_object();
1341
1342
#define INT(OBJ,K) cson_object_set(o, #K, json_new_int(OBJ.K))
1343
#define CSTR(OBJ,K) cson_object_set(o, #K, OBJ.K ? json_new_string(OBJ.K) \
1344
: cson_value_null())
1345
#define VAL(K,V) cson_object_set(o, #K, (V) ? (V) : cson_value_null())
1346
VAL(capabilities, json_cap_value());
1347
INT(g, argc);
1348
INT(g, isConst);
1349
CSTR(g, zConfigDbName);
1350
INT(g, repositoryOpen);
1351
INT(g, localOpen);
1352
INT(g, minPrefix);
1353
INT(g, fSqlTrace);
1354
INT(g, fSqlStats);
1355
INT(g, fSqlPrint);
1356
INT(g, fQuiet);
1357
INT(g, fHttpTrace);
1358
INT(g, fSystemTrace);
1359
INT(g, fNoSync);
1360
INT(g, iErrPriority);
1361
INT(g, sslNotAvailable);
1362
INT(g, cgiOutput);
1363
INT(g, xferPanic);
1364
INT(g, fullHttpReply);
1365
INT(g, xlinkClusterOnly);
1366
INT(g, fTimeFormat);
1367
INT(g, markPrivate);
1368
INT(g, clockSkewSeen);
1369
INT(g, isHTTP);
1370
INT(g.url, isFile);
1371
INT(g.url, isHttps);
1372
INT(g.url, isSsh);
1373
INT(g.url, port);
1374
INT(g.url, dfltPort);
1375
INT(g, useLocalauth);
1376
INT(g, noPswd);
1377
INT(g, userUid);
1378
INT(g, rcvid);
1379
INT(g, okCsrf);
1380
INT(g, thTrace);
1381
INT(g, isHome);
1382
INT(g, nAux);
1383
INT(g, allowSymlinks);
1384
1385
CSTR(g, zOpenRevision);
1386
CSTR(g, zLocalRoot);
1387
CSTR(g, zPath);
1388
CSTR(g, zExtra);
1389
CSTR(g, zBaseURL);
1390
CSTR(g, zTop);
1391
CSTR(g, zContentType);
1392
CSTR(g, zErrMsg);
1393
CSTR(g.url, name);
1394
CSTR(g.url, hostname);
1395
CSTR(g.url, protocol);
1396
CSTR(g.url, path);
1397
CSTR(g.url, user);
1398
CSTR(g.url, passwd);
1399
CSTR(g.url, canonical);
1400
CSTR(g.url, proxyAuth);
1401
CSTR(g.url, fossil);
1402
CSTR(g, zLogin);
1403
CSTR(g, zSSLIdentity);
1404
CSTR(g, zIpAddr);
1405
CSTR(g, zNonce);
1406
CSTR(g, zCsrfToken);
1407
1408
o = cson_new_object();
1409
cson_object_set(pay, "json", cson_object_value(o) );
1410
INT(g.json, isJsonMode);
1411
INT(g.json, resultCode);
1412
INT(g.json, errorDetailParanoia);
1413
INT(g.json, dispatchDepth);
1414
VAL(authToken, g.json.authToken);
1415
CSTR(g.json, jsonp);
1416
VAL(gc, g.json.gc.v);
1417
VAL(cmd, g.json.cmd.v);
1418
VAL(param, g.json.param.v);
1419
VAL(POST, g.json.post.v);
1420
VAL(warnings, cson_array_value(g.json.warnings));
1421
/*cson_output_opt outOpt;*/
1422
1423
1424
#undef INT
1425
#undef CSTR
1426
#undef VAL
1427
return cson_object_value(pay);
1428
}
1429
1430
1431
/*
1432
** Creates a new Fossil/JSON response envelope skeleton. It is owned
1433
** by the caller, who must eventually free it using cson_value_free(),
1434
** or add it to a cson container to transfer ownership. Returns NULL
1435
** on error.
1436
**
1437
** If payload is not NULL and resultCode is 0 then it is set as the
1438
** "payload" property of the returned object. If resultCode is 0 then
1439
** it defaults to g.json.resultCode. If resultCode is (or defaults to)
1440
** non-zero and payload is not NULL then this function calls
1441
** cson_value_free(payload) and does not insert the payload into the
1442
** response. In either case, ownership of payload is transferred to (or
1443
** shared with, if the caller holds a reference) this function.
1444
**
1445
** pMsg is an optional message string property (resultText) of the
1446
** response. If resultCode is non-0 and pMsg is NULL then
1447
** json_err_cstr() is used to get the error string. The caller may
1448
** provide his own or may use an empty string to suppress the
1449
** resultText property.
1450
**
1451
*/
1452
static cson_value * json_create_response( int resultCode,
1453
char const * pMsg,
1454
cson_value * payload){
1455
cson_value * v = NULL;
1456
cson_value * tmp = NULL;
1457
cson_object * o = NULL;
1458
int rc;
1459
resultCode = json_dumbdown_rc(resultCode ? resultCode : g.json.resultCode);
1460
o = cson_new_object();
1461
v = cson_object_value(o);
1462
if( ! o ) return NULL;
1463
#define SET(K) if(!tmp) goto cleanup; \
1464
cson_value_add_reference(tmp); \
1465
rc = cson_object_set( o, K, tmp ); \
1466
cson_value_free(tmp); \
1467
if(rc) do{ \
1468
tmp = NULL; \
1469
goto cleanup; \
1470
}while(0)
1471
1472
tmp = json_new_string(MANIFEST_UUID);
1473
SET("fossil");
1474
1475
tmp = json_new_timestamp(-1);
1476
SET(FossilJsonKeys.timestamp);
1477
1478
if( 0 != resultCode ){
1479
if( ! pMsg ){
1480
pMsg = g.zErrMsg;
1481
if(!pMsg){
1482
pMsg = json_err_cstr(resultCode);
1483
}
1484
}
1485
tmp = json_new_string(json_rc_cstr(resultCode));
1486
SET(FossilJsonKeys.resultCode);
1487
}
1488
1489
if( pMsg && *pMsg ){
1490
tmp = json_new_string(pMsg);
1491
SET(FossilJsonKeys.resultText);
1492
}
1493
1494
if(g.json.cmd.commandStr){
1495
tmp = json_new_string(g.json.cmd.commandStr);
1496
}else{
1497
tmp = json_response_command_path();
1498
}
1499
if(!tmp){
1500
tmp = json_new_string("???");
1501
}
1502
SET("command");
1503
1504
tmp = json_getenv(FossilJsonKeys.requestId);
1505
if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
1506
1507
if(0){/* these are only intended for my own testing...*/
1508
if(g.json.cmd.v){
1509
tmp = g.json.cmd.v;
1510
SET("$commandPath");
1511
}
1512
if(g.json.param.v){
1513
tmp = g.json.param.v;
1514
SET("$params");
1515
}
1516
if(0){/*Only for debugging, add some info to the response.*/
1517
tmp = cson_value_new_integer( g.json.cmd.offset );
1518
SET("cmd.offset");
1519
tmp = cson_value_new_bool( g.isHTTP );
1520
SET("isCGI");
1521
}
1522
}
1523
1524
if(fossil_timer_is_active(g.json.timerId)){
1525
/* This is, philosophically speaking, not quite the right place
1526
for ending the timer, but this is the one function which all of
1527
the JSON exit paths use (and they call it after processing,
1528
just before they end).
1529
*/
1530
sqlite3_uint64 span = fossil_timer_stop(g.json.timerId);
1531
/* I'm actually seeing sub-uSec runtimes in some tests, but a time of
1532
0 is "just kinda wrong".
1533
*/
1534
cson_object_set(o,"procTimeUs", cson_value_new_integer((cson_int_t)span));
1535
span /= 1000/*for milliseconds */;
1536
cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)span));
1537
assert(!fossil_timer_is_active(g.json.timerId));
1538
g.json.timerId = -1;
1539
}
1540
if(g.json.warnings){
1541
tmp = cson_array_value(g.json.warnings);
1542
SET("warnings");
1543
}
1544
1545
/* Only add the payload to SUCCESS responses. Else delete it. */
1546
if( NULL != payload ){
1547
if( resultCode ){
1548
cson_value_free(payload);
1549
payload = NULL;
1550
}else{
1551
tmp = payload;
1552
SET(FossilJsonKeys.payload);
1553
}
1554
}
1555
1556
if((g.perm.Admin||g.perm.Setup)
1557
&& json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
1558
){
1559
tmp = json_g_to_json();
1560
SET("g");
1561
}
1562
1563
#undef SET
1564
goto ok;
1565
cleanup:
1566
cson_value_free(v);
1567
v = NULL;
1568
ok:
1569
return v;
1570
}
1571
1572
/*
1573
** Outputs a JSON error response to either the cgi_xxx() family of
1574
** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0
1575
** then g.json.resultCode is used. If that is also 0 then the "Unknown
1576
** Error" code is used.
1577
**
1578
** If g.isHTTP then the generated JSON error response object replaces
1579
** any currently buffered page output. Because the output goes via
1580
** the cgi_xxx() family of functions, this function inherits any
1581
** compression which fossil does for its output.
1582
**
1583
** If alsoOutput is true AND g.isHTTP then cgi_reply() is called to
1584
** flush the output (and headers). Generally only do this if you are
1585
** about to call exit().
1586
**
1587
** If !g.isHTTP then alsoOutput is ignored and all output is sent to
1588
** stdout immediately.
1589
**
1590
** For generating the resultText property: if msg is not NULL then it
1591
** is used as-is. If it is NULL then g.zErrMsg is checked, and if that
1592
** is NULL then json_err_cstr(code) is used.
1593
*/
1594
void json_err( int code, char const * msg, int alsoOutput ){
1595
int rc = code ? code : (g.json.resultCode
1596
? g.json.resultCode
1597
: FSL_JSON_E_UNKNOWN);
1598
cson_value * resp = NULL;
1599
if(g.json.isJsonMode==0) return;
1600
rc = json_dumbdown_rc(rc);
1601
if( rc && !msg ){
1602
msg = g.zErrMsg;
1603
if(!msg){
1604
msg = json_err_cstr(rc);
1605
}
1606
}
1607
resp = json_create_response(rc, msg, NULL);
1608
if(!resp){
1609
/* about the only error case here is out-of-memory. DO NOT
1610
call fossil_panic() or fossil_fatal() here because those
1611
allocate.
1612
*/
1613
fprintf(stderr, "%s: Fatal error: could not allocate "
1614
"response object.\n", g.argv[0]);
1615
fossil_exit(1);
1616
}
1617
if( g.isHTTP ){
1618
if(alsoOutput){
1619
json_send_response(resp);
1620
}else{
1621
/* almost a duplicate of json_send_response() :( */
1622
cgi_reset_content();
1623
if( g.json.jsonp ){
1624
cgi_printf("%s(",g.json.jsonp);
1625
}
1626
cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt );
1627
if( g.json.jsonp ){
1628
cgi_append_content(")",1);
1629
}
1630
}
1631
}else{
1632
json_send_response(resp);
1633
}
1634
cson_value_free(resp);
1635
}
1636
1637
/*
1638
** Sets g.json.resultCode and g.zErrMsg, but does not report the error
1639
** via json_err(). Returns the code passed to it.
1640
**
1641
** code must be in the inclusive range 1000..9999.
1642
*/
1643
int json_set_err( int code, char const * fmt, ... ){
1644
assert( (code>=1000) && (code<=9999) );
1645
fossil_free(g.zErrMsg);
1646
g.json.resultCode = code;
1647
if(!fmt || !*fmt){
1648
g.zErrMsg = fossil_strdup(json_err_cstr(code));
1649
}else{
1650
va_list vargs;
1651
char * msg;
1652
va_start(vargs,fmt);
1653
msg = vmprintf(fmt, vargs);
1654
va_end(vargs);
1655
g.zErrMsg = msg;
1656
}
1657
return code;
1658
}
1659
1660
/*
1661
** Iterates through a prepared SELECT statement and converts each row
1662
** to a JSON object. If pTgt is not NULL then this function will
1663
** append the results to pTgt and return cson_array_value(pTgt). If
1664
** pTgt is NULL then a new Array object is created and returned (owned
1665
** by the caller). Each row of pStmt is converted to an Object and
1666
** appended to the array. If the result set has no rows AND pTgt is
1667
** NULL then NULL (not an empty array) is returned.
1668
*/
1669
cson_value * json_stmt_to_array_of_obj(Stmt *pStmt,
1670
cson_array * pTgt){
1671
cson_array * a = pTgt;
1672
char const * warnMsg = NULL;
1673
cson_value * colNamesV = NULL;
1674
cson_array * colNames = NULL;
1675
while( (SQLITE_ROW==db_step(pStmt)) ){
1676
cson_value * row = NULL;
1677
if(!a){
1678
a = cson_new_array();
1679
assert(NULL!=a);
1680
}
1681
if(!colNames){
1682
colNamesV = cson_sqlite3_column_names(pStmt->pStmt);
1683
assert(NULL != colNamesV);
1684
/*Why? cson_value_add_reference(colNamesV) avoids an ownership problem*/;
1685
colNames = cson_value_get_array(colNamesV);
1686
assert(NULL != colNames);
1687
}
1688
row = cson_sqlite3_row_to_object2(pStmt->pStmt, colNames);
1689
if(!row && !warnMsg){
1690
warnMsg = "Could not convert at least one result row to JSON.";
1691
continue;
1692
}
1693
if( 0 != cson_array_append(a, row) ){
1694
cson_value_free(row);
1695
if(pTgt != a) {
1696
cson_free_array(a);
1697
}
1698
assert( 0 && "Alloc error.");
1699
return NULL;
1700
}
1701
}
1702
cson_value_free(colNamesV);
1703
if(warnMsg){
1704
json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, "%s", warnMsg );
1705
}
1706
return cson_array_value(a);
1707
}
1708
1709
/*
1710
** Works just like json_stmt_to_array_of_obj(), but each row in the
1711
** result set is represented as an Array of values instead of an
1712
** Object (key/value pairs). If pTgt is NULL and the statement
1713
** has no results then NULL is returned, not an empty array.
1714
*/
1715
cson_value * json_stmt_to_array_of_array(Stmt *pStmt,
1716
cson_array * pTgt){
1717
cson_array * a = pTgt;
1718
while( (SQLITE_ROW==db_step(pStmt)) ){
1719
cson_value * row = NULL;
1720
if(!a){
1721
a = cson_new_array();
1722
assert(NULL!=a);
1723
}
1724
row = cson_sqlite3_row_to_array(pStmt->pStmt);
1725
cson_array_append(a, row);
1726
}
1727
return cson_array_value(a);
1728
}
1729
1730
cson_value * json_stmt_to_array_of_values(Stmt *pStmt,
1731
int resultColumn,
1732
cson_array * pTgt){
1733
cson_array * a = pTgt;
1734
while( (SQLITE_ROW==db_step(pStmt)) ){
1735
cson_value * row = cson_sqlite3_column_to_value(pStmt->pStmt,
1736
resultColumn);
1737
if(row){
1738
if(!a){
1739
a = cson_new_array();
1740
assert(NULL!=a);
1741
}
1742
cson_array_append(a, row);
1743
}
1744
}
1745
return cson_array_value(a);
1746
}
1747
1748
/*
1749
** Executes the given SQL and runs it through
1750
** json_stmt_to_array_of_obj(), returning the result of that
1751
** function. If resetBlob is true then blob_reset(pSql) is called
1752
** after preparing the query.
1753
**
1754
** pTgt has the same semantics as described for
1755
** json_stmt_to_array_of_obj().
1756
**
1757
** FIXME: change this to take a (char const *) instead of a blob,
1758
** to simplify the trivial use-cases (which don't need a Blob).
1759
*/
1760
cson_value * json_sql_to_array_of_obj(Blob * pSql, cson_array * pTgt,
1761
int resetBlob){
1762
Stmt q = empty_Stmt;
1763
cson_value * pay = NULL;
1764
assert( blob_size(pSql) > 0 );
1765
db_prepare(&q, "%s", blob_str(pSql) /*safe-for-%s*/);
1766
if(resetBlob){
1767
blob_reset(pSql);
1768
}
1769
pay = json_stmt_to_array_of_obj(&q, pTgt);
1770
db_finalize(&q);
1771
return pay;
1772
1773
}
1774
1775
/*
1776
** If the given COMMIT rid has any tags associated with it, this
1777
** function returns a JSON Array containing the tag names (owned by
1778
** the caller), else it returns NULL.
1779
**
1780
** See info_tags_of_checkin() for more details (this is simply a JSON
1781
** wrapper for that function).
1782
**
1783
** If there are no tags then this function returns NULL, not an empty
1784
** Array.
1785
*/
1786
cson_value * json_tags_for_checkin_rid(int rid, int propagatingOnly){
1787
cson_value * v = NULL;
1788
char * tags = info_tags_of_checkin(rid, propagatingOnly);
1789
if(tags){
1790
if(*tags){
1791
v = json_string_split2(tags,',',0);
1792
}
1793
fossil_free(tags);
1794
}
1795
return v;
1796
}
1797
1798
/*
1799
** Returns a "new" value representing the boolean value of zVal
1800
** (false if zVal is NULL). Note that cson does not really allocate
1801
** any memory for boolean values, but they "should" (for reasons of
1802
** style and philosophy) be cleaned up like any other values (but
1803
** it's a no-op for bools).
1804
*/
1805
cson_value * json_value_to_bool(cson_value const * zVal){
1806
return cson_value_get_bool(zVal)
1807
? cson_value_true()
1808
: cson_value_false();
1809
}
1810
1811
/*
1812
** Impl of /json/resultCodes
1813
**
1814
*/
1815
cson_value * json_page_resultCodes(void){
1816
cson_array * list = cson_new_array();
1817
cson_object * obj = NULL;
1818
cson_string * kRC;
1819
cson_string * kSymbol;
1820
cson_string * kNumber;
1821
cson_string * kDesc;
1822
cson_array_reserve( list, 35 );
1823
kRC = cson_new_string("resultCode",10);
1824
kSymbol = cson_new_string("cSymbol",7);
1825
kNumber = cson_new_string("number",6);
1826
kDesc = cson_new_string("description",11);
1827
#define C(K) obj = cson_new_object(); \
1828
cson_object_set_s(obj, kRC,json_new_string(json_rc_cstr(FSL_JSON_E_##K))); \
1829
cson_object_set_s(obj, kSymbol, json_new_string("FSL_JSON_E_"#K) ); \
1830
cson_object_set_s(obj, kNumber, cson_value_new_integer(FSL_JSON_E_##K) ); \
1831
cson_object_set_s(obj, kDesc, \
1832
json_new_string(json_err_cstr(FSL_JSON_E_##K))); \
1833
cson_array_append( list, cson_object_value(obj) ); obj = NULL;
1834
1835
C(GENERIC);
1836
C(INVALID_REQUEST);
1837
C(UNKNOWN_COMMAND);
1838
C(UNKNOWN);
1839
C(TIMEOUT);
1840
C(ASSERT);
1841
C(ALLOC);
1842
C(NYI);
1843
C(PANIC);
1844
C(MANIFEST_READ_FAILED);
1845
C(FILE_OPEN_FAILED);
1846
1847
C(AUTH);
1848
C(MISSING_AUTH);
1849
C(DENIED);
1850
C(WRONG_MODE);
1851
C(LOGIN_FAILED);
1852
C(LOGIN_FAILED_NOSEED);
1853
C(LOGIN_FAILED_NONAME);
1854
C(LOGIN_FAILED_NOPW);
1855
C(LOGIN_FAILED_NOTFOUND);
1856
1857
C(USAGE);
1858
C(INVALID_ARGS);
1859
C(MISSING_ARGS);
1860
C(AMBIGUOUS_UUID);
1861
C(UNRESOLVED_UUID);
1862
C(RESOURCE_ALREADY_EXISTS);
1863
C(RESOURCE_NOT_FOUND);
1864
1865
C(DB);
1866
C(STMT_PREP);
1867
C(STMT_BIND);
1868
C(STMT_EXEC);
1869
C(DB_LOCKED);
1870
C(DB_NEEDS_REBUILD);
1871
C(DB_NOT_FOUND);
1872
C(DB_NOT_VALID);
1873
#undef C
1874
return cson_array_value(list);
1875
}
1876
1877
1878
/*
1879
** /json/version implementation.
1880
**
1881
** Returns the payload object (owned by the caller).
1882
*/
1883
cson_value * json_page_version(void){
1884
cson_value * jval = NULL;
1885
cson_object * jobj = NULL;
1886
jval = cson_value_new_object();
1887
jobj = cson_value_get_object(jval);
1888
#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X)))
1889
FSET(MANIFEST_UUID,"manifestUuid");
1890
FSET(MANIFEST_VERSION,"manifestVersion");
1891
FSET(MANIFEST_DATE,"manifestDate");
1892
FSET(MANIFEST_YEAR,"manifestYear");
1893
FSET(RELEASE_VERSION,"releaseVersion");
1894
cson_object_set( jobj, "releaseVersionNumber",
1895
cson_value_new_integer(RELEASE_VERSION_NUMBER) );
1896
cson_object_set( jobj, "resultCodeParanoiaLevel",
1897
cson_value_new_integer(g.json.errorDetailParanoia) );
1898
FSET(FOSSIL_JSON_API_VERSION, "jsonApiVersion" );
1899
#undef FSET
1900
return jval;
1901
}
1902
1903
1904
/*
1905
** Returns the current user's capabilities string as a String value.
1906
** Returned value is owned by the caller, and will only be NULL if
1907
** g.userUid is invalid or an out of memory error. Or, it turns out,
1908
** in CLI mode (where there is no logged-in user).
1909
*/
1910
cson_value * json_cap_value(){
1911
if(g.userUid<=0){
1912
return NULL;
1913
}else{
1914
Stmt q = empty_Stmt;
1915
cson_value * val = NULL;
1916
db_prepare(&q, "SELECT cap FROM user WHERE uid=%d", g.userUid);
1917
if( db_step(&q)==SQLITE_ROW ){
1918
char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
1919
if( str ){
1920
val = json_new_string(str);
1921
}
1922
}
1923
db_finalize(&q);
1924
return val;
1925
}
1926
}
1927
1928
/*
1929
** Implementation for /json/cap
1930
**
1931
** Returned object contains details about the "capabilities" of the
1932
** current user (what he may/may not do).
1933
**
1934
** This is primarily intended for debuggering, but may have
1935
** a use in client code. (?)
1936
*/
1937
cson_value * json_page_cap(void){
1938
cson_value * payload = cson_value_new_object();
1939
cson_value * sub = cson_value_new_object();
1940
Stmt q;
1941
cson_object * obj = cson_value_get_object(payload);
1942
db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid);
1943
if( db_step(&q)==SQLITE_ROW ){
1944
/* reminder: we don't use g.zLogin because it's 0 for the guest
1945
user and the HTML UI appears to currently allow the name to be
1946
changed (but doing so would break other code). */
1947
char const * str = (char const *)sqlite3_column_text(q.pStmt,0);
1948
if( str ){
1949
cson_object_set( obj, "name",
1950
cson_value_new_string(str,strlen(str)) );
1951
}
1952
str = (char const *)sqlite3_column_text(q.pStmt,1);
1953
if( str ){
1954
cson_object_set( obj, "capabilities",
1955
cson_value_new_string(str,strlen(str)) );
1956
}
1957
}
1958
db_finalize(&q);
1959
cson_object_set( obj, "permissionFlags", sub );
1960
obj = cson_value_get_object(sub);
1961
1962
#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
1963
ADD(Setup,"setup");
1964
ADD(Admin,"admin");
1965
ADD(Password,"password");
1966
ADD(Write,"checkin");
1967
ADD(Read,"checkout");
1968
ADD(Hyperlink,"history");
1969
ADD(Clone,"clone");
1970
ADD(RdWiki,"readWiki");
1971
ADD(NewWiki,"createWiki");
1972
ADD(ApndWiki,"appendWiki");
1973
ADD(WrWiki,"editWiki");
1974
ADD(ModWiki,"moderateWiki");
1975
ADD(RdTkt,"readTicket");
1976
ADD(NewTkt,"createTicket");
1977
ADD(ApndTkt,"appendTicket");
1978
ADD(WrTkt,"editTicket");
1979
ADD(ModTkt,"moderateTicket");
1980
ADD(Attach,"attachFile");
1981
ADD(TktFmt,"createTicketReport");
1982
ADD(RdAddr,"readPrivate");
1983
ADD(Zip,"zip");
1984
ADD(Private,"xferPrivate");
1985
ADD(WrUnver,"writeUnversioned");
1986
ADD(RdForum,"readForum");
1987
ADD(WrForum,"writeForum");
1988
ADD(WrTForum,"writeTrustedForum");
1989
ADD(ModForum,"moderateForum");
1990
ADD(AdminForum,"adminForum");
1991
ADD(EmailAlert,"emailAlert");
1992
ADD(Announce,"announce");
1993
ADD(Debug,"debug");
1994
ADD(Chat,"chat");
1995
#undef ADD
1996
return payload;
1997
}
1998
1999
/*
2000
** Implementation of the /json/stat page/command.
2001
**
2002
*/
2003
cson_value * json_page_stat(void){
2004
i64 t, fsize;
2005
int n, m;
2006
int full;
2007
enum { BufLen = 1000 };
2008
char zBuf[BufLen];
2009
cson_value * jv = NULL;
2010
cson_object * jo = NULL;
2011
cson_value * jv2 = NULL;
2012
cson_object * jo2 = NULL;
2013
char * zTmp = NULL;
2014
if( !g.perm.Read ){
2015
json_set_err(FSL_JSON_E_DENIED,
2016
"Requires 'o' permissions.");
2017
return NULL;
2018
}
2019
full = json_find_option_bool("full",NULL,"f",
2020
json_find_option_bool("verbose",NULL,"v",0));
2021
#define SETBUF(O,K) cson_object_set(O, K, \
2022
cson_value_new_string(zBuf, strlen(zBuf)));
2023
2024
jv = cson_value_new_object();
2025
jo = cson_value_get_object(jv);
2026
2027
zTmp = db_get("project-name",NULL);
2028
cson_object_set(jo, "projectName", json_new_string(zTmp));
2029
fossil_free(zTmp);
2030
zTmp = db_get("project-description",NULL);
2031
cson_object_set(jo, "projectDescription", json_new_string(zTmp));
2032
fossil_free(zTmp);
2033
zTmp = NULL;
2034
fsize = file_size(g.zRepositoryName, ExtFILE);
2035
cson_object_set(jo, "repositorySize",
2036
cson_value_new_integer((cson_int_t)fsize));
2037
2038
if(full){
2039
n = db_int(0, "SELECT count(*) FROM blob");
2040
m = db_int(0, "SELECT count(*) FROM delta");
2041
cson_object_set(jo, "blobCount", cson_value_new_integer((cson_int_t)n));
2042
cson_object_set(jo, "deltaCount", cson_value_new_integer((cson_int_t)m));
2043
if( n>0 ){
2044
int a, b;
2045
Stmt q;
2046
db_prepare(&q, "SELECT total(size), avg(size), max(size)"
2047
" FROM blob WHERE size>0");
2048
db_step(&q);
2049
t = db_column_int64(&q, 0);
2050
cson_object_set(jo, "uncompressedArtifactSize",
2051
cson_value_new_integer((cson_int_t)t));
2052
cson_object_set(jo, "averageArtifactSize",
2053
cson_value_new_integer((cson_int_t)db_column_int(&q, 1)));
2054
cson_object_set(jo, "maxArtifactSize",
2055
cson_value_new_integer((cson_int_t)db_column_int(&q, 2)));
2056
db_finalize(&q);
2057
if( t/fsize < 5 ){
2058
b = 10;
2059
fsize /= 10;
2060
}else{
2061
b = 1;
2062
}
2063
a = t/fsize;
2064
sqlite3_snprintf(BufLen,zBuf, "%d:%d", a, b);
2065
SETBUF(jo, "compressionRatio");
2066
}
2067
n = db_int(0, "SELECT count(distinct mid) FROM mlink /*scan*/");
2068
cson_object_set(jo, "checkinCount", cson_value_new_integer((cson_int_t)n));
2069
n = db_int(0, "SELECT count(*) FROM filename /*scan*/");
2070
cson_object_set(jo, "fileCount", cson_value_new_integer((cson_int_t)n));
2071
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
2072
" WHERE +tagname GLOB 'wiki-*'");
2073
cson_object_set(jo, "wikiPageCount", cson_value_new_integer((cson_int_t)n));
2074
n = db_int(0, "SELECT count(*) FROM tag /*scan*/"
2075
" WHERE +tagname GLOB 'tkt-*'");
2076
cson_object_set(jo, "ticketCount", cson_value_new_integer((cson_int_t)n));
2077
}/*full*/
2078
n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)"
2079
" + 0.99");
2080
cson_object_set(jo, "ageDays", cson_value_new_integer((cson_int_t)n));
2081
cson_object_set(jo, "ageYears", cson_value_new_double(n/365.2425));
2082
sqlite3_snprintf(BufLen, zBuf, db_get("project-code",""));
2083
SETBUF(jo, "projectCode");
2084
cson_object_set(jo, "compiler",
2085
cson_value_new_string(COMPILER_NAME, strlen(COMPILER_NAME)));
2086
2087
jv2 = cson_value_new_object();
2088
jo2 = cson_value_get_object(jv2);
2089
cson_object_set(jo, "sqlite", jv2);
2090
sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)", sqlite3_sourceid(),
2091
&sqlite3_sourceid()[20], sqlite3_libversion());
2092
SETBUF(jo2, "version");
2093
cson_object_set(jo2, "pageCount", cson_value_new_integer(
2094
(cson_int_t)db_int(0, "PRAGMA repository.page_count")));
2095
cson_object_set(jo2, "pageSize", cson_value_new_integer(
2096
(cson_int_t)db_int(0, "PRAGMA repository.page_size")));
2097
cson_object_set(jo2, "freeList", cson_value_new_integer(
2098
(cson_int_t)db_int(0, "PRAGMA repository.freelist_count")));
2099
sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0,"PRAGMA repository.encoding"));
2100
SETBUF(jo2, "encoding");
2101
sqlite3_snprintf(BufLen, zBuf, "%s",
2102
db_text(0, "PRAGMA repository.journal_mode"));
2103
cson_object_set(jo2, "journalMode", *zBuf ?
2104
cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null());
2105
return jv;
2106
#undef SETBUF
2107
}
2108
2109
2110
2111
2112
/*
2113
** Creates a comma-separated list of command names
2114
** taken from zPages. zPages must be an array of objects
2115
** whose final entry MUST have a NULL name value or results
2116
** are undefined.
2117
**
2118
** The list is appended to pOut. The number of items (not bytes)
2119
** appended are returned. If filterByMode is non-0 then the result
2120
** list will contain only commands which are able to run in the
2121
** current run mode (CLI vs. HTTP).
2122
*/
2123
static int json_pagedefs_to_string(JsonPageDef const * zPages,
2124
Blob * pOut, int filterByMode){
2125
int i = 0;
2126
for( ; zPages->name; ++zPages, ++i ){
2127
if(filterByMode){
2128
if(g.isHTTP && zPages->runMode < 0) continue;
2129
else if(zPages->runMode > 0) continue;
2130
}
2131
blob_append(pOut, zPages->name, -1);
2132
if((zPages+1)->name){
2133
blob_append(pOut, ", ",2);
2134
}
2135
}
2136
return i;
2137
}
2138
2139
/*
2140
** Creates an error message using zErrPrefix and the given array of
2141
** JSON command definitions, and sets the g.json error state to
2142
** reflect FSL_JSON_E_MISSING_ARGS. If zErrPrefix is NULL then
2143
** some default is used (e.g. "Try one of: "). If it is "" then
2144
** no prefix is used.
2145
**
2146
** The intention is to provide the user (via the response.resultText)
2147
** a list of available commands/subcommands.
2148
**
2149
*/
2150
void json_dispatch_missing_args_err( JsonPageDef const * pCommands,
2151
char const * zErrPrefix ){
2152
Blob cmdNames = empty_blob;
2153
blob_init(&cmdNames,NULL,0);
2154
if( !zErrPrefix ) {
2155
zErrPrefix = "Try one of: ";
2156
}
2157
blob_append( &cmdNames, zErrPrefix, strlen(zErrPrefix) );
2158
json_pagedefs_to_string(pCommands, &cmdNames, 1);
2159
json_set_err(FSL_JSON_E_MISSING_ARGS, "%s",
2160
blob_str(&cmdNames));
2161
blob_reset(&cmdNames);
2162
}
2163
2164
cson_value * json_page_dispatch_helper(JsonPageDef const * pages){
2165
JsonPageDef const * def;
2166
char const * cmd = json_command_arg(1+g.json.dispatchDepth);
2167
assert( NULL != pages );
2168
if( ! cmd ){
2169
json_dispatch_missing_args_err(pages,
2170
"No subcommand specified. "
2171
"Try one of: ");
2172
return NULL;
2173
}
2174
def = json_handler_for_name( cmd, pages );
2175
if(!def){
2176
json_set_err(FSL_JSON_E_UNKNOWN_COMMAND,
2177
"Unknown subcommand: %s", cmd);
2178
return NULL;
2179
}
2180
else{
2181
++g.json.dispatchDepth;
2182
return (*def->func)();
2183
}
2184
}
2185
2186
2187
/*
2188
** Impl of /json/rebuild. Requires admin privileges.
2189
*/
2190
static cson_value * json_page_rebuild(void){
2191
if( !g.perm.Admin ){
2192
json_set_err(FSL_JSON_E_DENIED,"Requires 'a' privileges.");
2193
return NULL;
2194
}else{
2195
/* Reminder: the db_xxx() ops "should" fail via the fossil core
2196
error handlers, which will cause a JSON error and exit(). i.e. we
2197
don't handle the errors here. TODO: confirm that all these db
2198
routine fail gracefully in JSON mode.
2199
2200
On large repos (e.g. fossil's) this operation is likely to take
2201
longer than the client timeout, which will cause it to fail (but
2202
it's sqlite3, so it'll fail gracefully).
2203
*/
2204
db_close(1);
2205
db_open_repository(g.zRepositoryName);
2206
db_begin_transaction();
2207
rebuild_db(0, 0);
2208
db_end_transaction(0);
2209
return NULL;
2210
}
2211
}
2212
2213
/*
2214
** Impl of /json/g. Requires admin/setup rights.
2215
*/
2216
static cson_value * json_page_g(void){
2217
if(!g.perm.Admin || !g.perm.Setup){
2218
json_set_err(FSL_JSON_E_DENIED,
2219
"Requires 'a' or 's' privileges.");
2220
return NULL;
2221
}
2222
return json_g_to_json();
2223
}
2224
2225
/* Impl in json_login.c. */
2226
cson_value * json_page_anon_password(void);
2227
/* Impl in json_artifact.c. */
2228
cson_value * json_page_artifact(void);
2229
/* Impl in json_branch.c. */
2230
cson_value * json_page_branch(void);
2231
/* Impl in json_diff.c. */
2232
cson_value * json_page_diff(void);
2233
/* Impl in json_dir.c. */
2234
cson_value * json_page_dir(void);
2235
/* Impl in json_login.c. */
2236
cson_value * json_page_login(void);
2237
/* Impl in json_login.c. */
2238
cson_value * json_page_logout(void);
2239
/* Impl in json_query.c. */
2240
cson_value * json_page_query(void);
2241
/* Impl in json_report.c. */
2242
cson_value * json_page_report(void);
2243
/* Impl in json_tag.c. */
2244
cson_value * json_page_tag(void);
2245
/* Impl in json_user.c. */
2246
cson_value * json_page_user(void);
2247
/* Impl in json_config.c. */
2248
cson_value * json_page_config(void);
2249
/* Impl in json_finfo.c. */
2250
cson_value * json_page_finfo(void);
2251
/* Impl in json_status.c. */
2252
cson_value * json_page_status(void);
2253
2254
/*
2255
** Mapping of names to JSON pages/commands. Each name is a subpath of
2256
** /json (in CGI mode) or a subcommand of the json command in CLI mode
2257
*/
2258
static const JsonPageDef JsonPageDefs[] = {
2259
/* please keep alphabetically sorted (case-insensitive)
2260
for maintenance reasons. */
2261
{"anonymousPassword", json_page_anon_password, 0},
2262
{"artifact", json_page_artifact, 0},
2263
{"branch", json_page_branch,0},
2264
{"cap", json_page_cap, 0},
2265
{"config", json_page_config, 0 },
2266
{"diff", json_page_diff, 0},
2267
{"dir", json_page_dir, 0},
2268
{"finfo", json_page_finfo, 0},
2269
{"g", json_page_g, 0},
2270
{"HAI",json_page_version,0},
2271
{"login",json_page_login,0},
2272
{"logout",json_page_logout,0},
2273
{"query",json_page_query,0},
2274
{"rebuild",json_page_rebuild,0},
2275
{"report", json_page_report, 0},
2276
{"resultCodes", json_page_resultCodes,0},
2277
{"settings",json_page_settings,0},
2278
{"stat",json_page_stat,0},
2279
{"status", json_page_status, 0},
2280
{"tag", json_page_tag,0},
2281
/*{"ticket", json_page_nyi,0},*/
2282
{"timeline", json_page_timeline,0},
2283
{"user",json_page_user,0},
2284
{"version",json_page_version,0},
2285
{"whoami",json_page_whoami,0},
2286
{"wiki",json_page_wiki,0},
2287
/* Last entry MUST have a NULL name. */
2288
{NULL,NULL,0}
2289
};
2290
2291
/*
2292
** Internal helper for json_cmd_top() and json_page_top().
2293
**
2294
** Searches JsonPageDefs for a command with the given name. If found,
2295
** it is used to generate and output a JSON response. If not found, it
2296
** generates a JSON-style error response. Returns 0 on success, non-0
2297
** on error. On error it will set g.json's error state.
2298
*/
2299
static int json_dispatch_root_command( char const * zCommand ){
2300
int rc = 0;
2301
cson_value * payload = NULL;
2302
JsonPageDef const * pageDef = NULL;
2303
pageDef = json_handler_for_name(zCommand,&JsonPageDefs[0]);
2304
if( ! pageDef ){
2305
rc = FSL_JSON_E_UNKNOWN_COMMAND;
2306
json_set_err( rc, "Unknown command: %s", zCommand );
2307
}else if( pageDef->runMode < 0 /*CLI only*/){
2308
rc = FSL_JSON_E_WRONG_MODE;
2309
}else if( (g.isHTTP && (pageDef->runMode < 0 /*CLI only*/))
2310
||
2311
(!g.isHTTP && (pageDef->runMode > 0 /*HTTP only*/))
2312
){
2313
rc = FSL_JSON_E_WRONG_MODE;
2314
}
2315
else{
2316
rc = 0;
2317
g.json.dispatchDepth = 1;
2318
payload = (*pageDef->func)();
2319
}
2320
payload = json_create_response(rc, NULL, payload);
2321
json_send_response(payload);
2322
cson_value_free(payload);
2323
return rc;
2324
}
2325
2326
#ifdef FOSSIL_ENABLE_JSON
2327
/* dupe ifdef needed for mkindex */
2328
/*
2329
** WEBPAGE: json
2330
**
2331
** Pages under /json/... must be entered into JsonPageDefs.
2332
** This function dispatches them, and is the HTTP equivalent of
2333
** json_cmd_top().
2334
*/
2335
void json_page_top(void){
2336
char const * zCommand;
2337
assert(g.json.gc.a && "json_bootstrap_early() was not called!");
2338
assert(g.json.cmd.a && "json_bootstrap_late() was not called!");
2339
zCommand = json_command_arg(1);
2340
if(!zCommand || !*zCommand){
2341
json_dispatch_missing_args_err( JsonPageDefs,
2342
"No command (sub-path) specified."
2343
" Try one of: ");
2344
return;
2345
}
2346
json_dispatch_root_command( zCommand );
2347
}
2348
#endif /* FOSSIL_ENABLE_JSON for mkindex */
2349
2350
#ifdef FOSSIL_ENABLE_JSON
2351
/* dupe ifdef needed for mkindex */
2352
/*
2353
** This function dispatches json commands and is the CLI equivalent of
2354
** json_page_top().
2355
**
2356
** COMMAND: json
2357
**
2358
** Usage: %fossil json SUBCOMMAND ?OPTIONS?
2359
**
2360
** In CLI mode, the -R REPO common option is supported. Due to limitations
2361
** in the argument dispatching code, any -FLAGS must come after the final
2362
** sub- (or subsub-) command.
2363
**
2364
** The -json-input FILE option can be used to read JSON data and process
2365
** it like the HTTP interface would. For example:
2366
**
2367
** %fossil json -json-input my.json
2368
**
2369
** The commands include:
2370
**
2371
** anonymousPassword
2372
** artifact
2373
** branch
2374
** cap
2375
** config
2376
** diff
2377
** dir
2378
** g
2379
** login
2380
** logout
2381
** query
2382
** rebuild
2383
** report
2384
** resultCodes
2385
** stat
2386
** tag
2387
** timeline
2388
** user
2389
** version (alias: HAI)
2390
** whoami
2391
** wiki
2392
**
2393
** Run '%fossil json' without any subcommand to see the full list (but be
2394
** aware that some listed might not yet be fully implemented).
2395
**
2396
*/
2397
void json_cmd_top(void){
2398
char const * cmd = NULL;
2399
int rc = 0;
2400
memset( &g.perm, 0xff, sizeof(g.perm) )
2401
/* In CLI mode fossil does not use permissions
2402
and they all default to false. We enable them
2403
here because (A) fossil doesn't use them in local
2404
mode but (B) having them set gives us one less
2405
difference in the CLI/CGI/Server-mode JSON
2406
handling.
2407
*/
2408
;
2409
json_bootstrap_early();
2410
json_bootstrap_late();
2411
if( 2 > cson_array_length_get(g.json.cmd.a) ){
2412
goto usage;
2413
}
2414
#if 0
2415
json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing.");
2416
json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing again.");
2417
#endif
2418
cmd = json_command_arg(1);
2419
if( !cmd || !*cmd ){
2420
goto usage;
2421
}
2422
rc = json_dispatch_root_command( cmd );
2423
if(0 != rc){
2424
/* FIXME: we need a way of passing this error back
2425
up to the routine which called this callback.
2426
e.g. add g.errCode.
2427
*/
2428
fossil_exit(1);
2429
}
2430
return;
2431
usage:
2432
{
2433
cson_value * payload;
2434
json_dispatch_missing_args_err( JsonPageDefs,
2435
"No subcommand specified."
2436
" Try one of: ");
2437
payload = json_create_response(0, NULL, NULL);
2438
json_send_response(payload);
2439
cson_value_free(payload);
2440
fossil_exit(1);
2441
}
2442
}
2443
#endif /* FOSSIL_ENABLE_JSON for mkindex */
2444
2445
#endif /* FOSSIL_ENABLE_JSON */
2446

Keyboard Shortcuts

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