Fossil SCM

More cleanups to the cson_cgi removal refactoring. Added common "indent" parameter to control indentation of JSON (uses cson_output_opt.indentation semantics).

stephan 2011-09-18 05:45 UTC json
Commit b3653265d1552d8392c93088ac9f9efd3c1ec364
4 files changed +35 -22 +94 -14 +9 -4 +2 -1
+35 -22
--- src/cgi.c
+++ src/cgi.c
@@ -723,38 +723,39 @@
723723
}
724724
}
725725
}
726726
727727
/*
728
-** Reads a JSON object from the first contentLen bytes of zIn.
729
-** On success 0 is returned and g.json.post is updated to hold
730
-** the content. On error non-0 is returned.
728
+** Reads a JSON object from the first contentLen bytes of zIn. On
729
+** g.json.post is updated to hold the content. On error a
730
+** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit(0) is
731
+** called.
731732
*/
732
-static int cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
733
+static void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
733734
cson_value * jv = NULL;
734735
int rc;
735736
CgiPostReadState state;
736
- cson_parse_info pinfo = cson_parse_info_empty;
737737
assert( 0 != contentLen );
738738
state.fh = zIn;
739739
state.len = contentLen;
740740
state.pos = 0;
741
- rc = cson_parse( &jv, cson_data_source_FILE_n, &state, NULL, &pinfo );
742
- if( rc ){
743
-#if 0
744
- fprintf(stderr, "%s: Parsing POST as JSON failed: code=%d (%s) line=%u, col=%u\n",
745
- __FILE__, rc, cson_rc_string(rc), pinfo.line, pinfo.col );
746
-#endif
747
- return rc;
748
- }
749
- rc = json_gc_add( "$POST", jv, 1 );
750
- if( 0 == rc ){
741
+ rc = cson_parse( &jv, cson_data_source_FILE_n, &state, NULL, NULL );
742
+ if(rc){
743
+ goto invalidRequest;
744
+ }else{
745
+ json_gc_add( "POST.JSON", jv, 1 );
751746
g.json.post.v = jv;
752747
g.json.post.o = cson_value_get_object( jv );
753
- assert( g.json.post.o && "FIXME: also support an Array as POST data node." );
748
+ if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */
749
+ goto invalidRequest;
750
+ }
754751
}
755
- return rc;
752
+ return;
753
+ invalidRequest:
754
+ cgi_set_content_type(json_guess_content_type());
755
+ json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
756
+ fossil_exit(0);
756757
}
757758
758759
759760
/*
760761
** Initialize the query parameter database. Information is pulled from
@@ -764,10 +765,11 @@
764765
void cgi_init(void){
765766
char *z;
766767
const char *zType;
767768
int len;
768769
json_main_bootstrap();
770
+ g.isCGI = 1;
769771
cgi_destination(CGI_BODY);
770772
771773
z = (char*)P("HTTP_COOKIE");
772774
if( z ){
773775
z = mprintf("%s",z);
@@ -809,15 +811,26 @@
809811
}else if( fossil_strcmp(zType, "application/json")
810812
|| fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/
811813
|| fossil_strcmp(zType,"application/javascript")){
812814
g.json.isJsonMode = 1;
813815
cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
814
- cgi_set_content_type("application/json")
815
- /* FIXME: guess a proper content type value based on the
816
- Accept header. We have code for this in cson_cgi.
817
- */
818
- ;
816
+ /* FIXMEs:
817
+
818
+ - See if fossil really needs g.cgiIn to be set for this purpose
819
+ (i don't think it does). If it does then fill g.cgiIn and
820
+ refactor to parse the JSON from there.
821
+
822
+ - After parsing POST JSON, copy the "first layer" of keys/values
823
+ to cgi_setenv(), honoring the upper-case distinction used
824
+ in add_param_list(). However...
825
+
826
+ - If we do that then we might get a disconnect in precedence of
827
+ GET/POST arguments. i prefer for GET entries to take precedence
828
+ over like-named POST entries, but in order for that to happen we
829
+ need to process QUERY_STRING _after_ reading the POST data.
830
+ */
831
+ cgi_set_content_type(json_guess_content_type());
819832
}
820833
}
821834
822835
}
823836
824837
--- src/cgi.c
+++ src/cgi.c
@@ -723,38 +723,39 @@
723 }
724 }
725 }
726
727 /*
728 ** Reads a JSON object from the first contentLen bytes of zIn.
729 ** On success 0 is returned and g.json.post is updated to hold
730 ** the content. On error non-0 is returned.
 
731 */
732 static int cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
733 cson_value * jv = NULL;
734 int rc;
735 CgiPostReadState state;
736 cson_parse_info pinfo = cson_parse_info_empty;
737 assert( 0 != contentLen );
738 state.fh = zIn;
739 state.len = contentLen;
740 state.pos = 0;
741 rc = cson_parse( &jv, cson_data_source_FILE_n, &state, NULL, &pinfo );
742 if( rc ){
743 #if 0
744 fprintf(stderr, "%s: Parsing POST as JSON failed: code=%d (%s) line=%u, col=%u\n",
745 __FILE__, rc, cson_rc_string(rc), pinfo.line, pinfo.col );
746 #endif
747 return rc;
748 }
749 rc = json_gc_add( "$POST", jv, 1 );
750 if( 0 == rc ){
751 g.json.post.v = jv;
752 g.json.post.o = cson_value_get_object( jv );
753 assert( g.json.post.o && "FIXME: also support an Array as POST data node." );
 
 
754 }
755 return rc;
 
 
 
 
756 }
757
758
759 /*
760 ** Initialize the query parameter database. Information is pulled from
@@ -764,10 +765,11 @@
764 void cgi_init(void){
765 char *z;
766 const char *zType;
767 int len;
768 json_main_bootstrap();
 
769 cgi_destination(CGI_BODY);
770
771 z = (char*)P("HTTP_COOKIE");
772 if( z ){
773 z = mprintf("%s",z);
@@ -809,15 +811,26 @@
809 }else if( fossil_strcmp(zType, "application/json")
810 || fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/
811 || fossil_strcmp(zType,"application/javascript")){
812 g.json.isJsonMode = 1;
813 cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
814 cgi_set_content_type("application/json")
815 /* FIXME: guess a proper content type value based on the
816 Accept header. We have code for this in cson_cgi.
817 */
818 ;
 
 
 
 
 
 
 
 
 
 
 
819 }
820 }
821
822 }
823
824
--- src/cgi.c
+++ src/cgi.c
@@ -723,38 +723,39 @@
723 }
724 }
725 }
726
727 /*
728 ** Reads a JSON object from the first contentLen bytes of zIn. On
729 ** g.json.post is updated to hold the content. On error a
730 ** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit(0) is
731 ** called.
732 */
733 static void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
734 cson_value * jv = NULL;
735 int rc;
736 CgiPostReadState state;
 
737 assert( 0 != contentLen );
738 state.fh = zIn;
739 state.len = contentLen;
740 state.pos = 0;
741 rc = cson_parse( &jv, cson_data_source_FILE_n, &state, NULL, NULL );
742 if(rc){
743 goto invalidRequest;
744 }else{
745 json_gc_add( "POST.JSON", jv, 1 );
 
 
 
 
 
746 g.json.post.v = jv;
747 g.json.post.o = cson_value_get_object( jv );
748 if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */
749 goto invalidRequest;
750 }
751 }
752 return;
753 invalidRequest:
754 cgi_set_content_type(json_guess_content_type());
755 json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
756 fossil_exit(0);
757 }
758
759
760 /*
761 ** Initialize the query parameter database. Information is pulled from
@@ -764,10 +765,11 @@
765 void cgi_init(void){
766 char *z;
767 const char *zType;
768 int len;
769 json_main_bootstrap();
770 g.isCGI = 1;
771 cgi_destination(CGI_BODY);
772
773 z = (char*)P("HTTP_COOKIE");
774 if( z ){
775 z = mprintf("%s",z);
@@ -809,15 +811,26 @@
811 }else if( fossil_strcmp(zType, "application/json")
812 || fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/
813 || fossil_strcmp(zType,"application/javascript")){
814 g.json.isJsonMode = 1;
815 cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
816 /* FIXMEs:
817
818 - See if fossil really needs g.cgiIn to be set for this purpose
819 (i don't think it does). If it does then fill g.cgiIn and
820 refactor to parse the JSON from there.
821
822 - After parsing POST JSON, copy the "first layer" of keys/values
823 to cgi_setenv(), honoring the upper-case distinction used
824 in add_param_list(). However...
825
826 - If we do that then we might get a disconnect in precedence of
827 GET/POST arguments. i prefer for GET entries to take precedence
828 over like-named POST entries, but in order for that to happen we
829 need to process QUERY_STRING _after_ reading the POST data.
830 */
831 cgi_set_content_type(json_guess_content_type());
832 }
833 }
834
835 }
836
837
+94 -14
--- src/json.c
+++ src/json.c
@@ -226,10 +226,56 @@
226226
*/
227227
int json_setenv( char const * zKey, cson_value * v ){
228228
return cson_object_set( g.json.param.o, zKey, v );
229229
}
230230
231
+/*
232
+** Guesses a RESPONSE Content-Type value based (primarily) on the
233
+** HTTP_ACCEPT header.
234
+**
235
+** It will try to figure out if the client can support
236
+** application/json or application/javascript, and will fall back to
237
+** text/plain if it cannot figure out anything more specific.
238
+**
239
+** Returned memory is static and immutable.
240
+**
241
+*/
242
+char const * json_guess_content_type(){
243
+ char const * cset;
244
+ char doUtf8;
245
+ cset = PD("HTTP_ACCEPT_CHARSET",NULL);
246
+ doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
247
+ ? 1 : 0;
248
+ if( g.json.jsonp ){
249
+ return doUtf8
250
+ ? "application/javascript; charset=utf-8"
251
+ : "application/javascript";
252
+ }else{
253
+ /*
254
+ Content-type
255
+
256
+ If the browser does not sent an ACCEPT for application/json
257
+ then we fall back to text/plain.
258
+ */
259
+ char const * cstr;
260
+ cstr = PD("HTTP_ACCEPT",NULL);
261
+ if( NULL == cstr ){
262
+ return doUtf8
263
+ ? "application/json; charset=utf-8"
264
+ : "application/json";
265
+ }else{
266
+ if( strstr( cstr, "application/json" )
267
+ || strstr( cstr, "*/*" ) ){
268
+ return doUtf8
269
+ ? "application/json; charset=utf-8"
270
+ : "application/json";
271
+ }else{
272
+ return "text/plain";
273
+ }
274
+ }
275
+ }
276
+}
231277
232278
/*
233279
** Returns the current request's JSON authentication token, or NULL if
234280
** none is found. The token's memory is owned by (or shared with)
235281
** g.json.
@@ -320,11 +366,10 @@
320366
*/
321367
static void json_mode_bootstrap(){
322368
static char once = 0 /* guard against multiple runs */;
323369
char const * zPath = P("PATH_INFO");
324370
cson_value * pathSplit = NULL;
325
- cson_array * ar = NULL;
326371
assert( (0==once) && "json_mode_bootstrap() called too many times!");
327372
if( once ){
328373
return;
329374
}else{
330375
once = 1;
@@ -334,17 +379,24 @@
334379
g.json.cmd.offset = -1;
335380
if( !g.isCGI && g.fullHttpReply ){
336381
/* workaround for server mode, so we see it as CGI mode. */
337382
g.isCGI = 1;
338383
}
384
+ if(! g.json.post.v ){
385
+ /* If cgi_init() reads POSTed JSON then it sets the content type.
386
+ If it did not then we need to set it.
387
+ */
388
+ cgi_set_content_type(json_guess_content_type());
389
+ }
390
+
339391
#if defined(NDEBUG)
340392
/* avoids debug messages on stderr in JSON mode */
341393
sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0);
342394
#endif
343395
344396
g.json.cmd.v = cson_value_new_array();
345
- ar = g.json.cmd.a = cson_value_get_array(g.json.cmd.v);
397
+ g.json.cmd.a = cson_value_get_array(g.json.cmd.v);
346398
json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v, 1 );
347399
/*
348400
The following if/else block translates the PATH_INFO path (in
349401
CLI/server modes) or g.argv (CLI mode) into an internal list so
350402
that we can simplify command dispatching later on.
@@ -351,20 +403,17 @@
351403
352404
Note that translating g.argv this way is overkill but allows us to
353405
avoid CLI-only special-case handling in other code, e.g.
354406
json_command_arg().
355407
*/
356
- if( zPath ){/* either CLI or server mode... */
357
- /* Translate fossil's PATH_INFO into cson_cgi for later
358
- convenience, to help consolidate how we handle CGI/server
359
- modes. This block is hit when running in plain server mode.
360
- */
408
+ if( zPath ){/* Either CGI or server mode... */
409
+ /* Translate PATH_INFO into JSON for later convenience. */
361410
char const * p = zPath /* current byte */;
362411
char const * head = p /* current start-of-token */;
363412
unsigned int len = 0 /* current token's lengh */;
364413
assert( g.isCGI && "g.isCGI should have been set by now." );
365
- for( ;*p!='?'; ++p){
414
+ for( ; ; ++p){
366415
if( !*p || ('/' == *p) ){
367416
if( len ){
368417
cson_value * part;
369418
char * zPart;
370419
assert( head != p );
@@ -373,11 +422,11 @@
373422
memcpy(zPart, head, len);
374423
zPart[len] = 0;
375424
dehttpize(zPart);
376425
part = cson_value_new_string(zPart, strlen(zPart));
377426
free(zPart);
378
- cson_array_append( ar, part );
427
+ cson_array_append( g.json.cmd.a, part );
379428
len = 0;
380429
}
381430
if( !*p ){
382431
break;
383432
}
@@ -394,11 +443,11 @@
394443
arg = g.argv[i];
395444
if( !arg || !*arg ){
396445
continue;
397446
}
398447
part = cson_value_new_string(arg,strlen(arg));
399
- cson_array_append(ar, part);
448
+ cson_array_append(g.json.cmd.a, part);
400449
}
401450
}
402451
403452
/* g.json.reqPayload exists only to simplify some of our access to
404453
the request payload. We currently only use this in the context of
@@ -408,10 +457,31 @@
408457
g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v )
409458
/* g.json.reqPayload.o may legally be NULL, which means only that
410459
g.json.reqPayload.v is-not-a Object.
411460
*/;
412461
}
462
+
463
+ do{/* set up JSON out formatting options. */
464
+ unsigned char indent = g.isCGI ? 0 : 1;
465
+ cson_value const * indentV = json_getenv("indent");
466
+ if(indentV){
467
+ if(cson_value_is_string(indentV)){
468
+ int n = atoi(cson_string_cstr(cson_value_get_string(indentV)));
469
+ indent = (n>0)
470
+ ? (unsigned char)n
471
+ : 0;
472
+ }else if(cson_value_is_number(indentV)){
473
+ double n = cson_value_get_integer(indentV);
474
+ indent = (n>0)
475
+ ? (unsigned char)n
476
+ : 0;
477
+ }
478
+ }
479
+ g.json.outOpt.indentation = indent;
480
+ g.json.outOpt.addNewline = g.isCGI ? 0 : 1;
481
+ }while(0);
482
+
413483
json_auth_token()/* will copy our auth token, if any, to fossil's core. */;
414484
if( g.isCGI ){
415485
login_check_credentials()/* populates g.perm */;
416486
}
417487
else{
@@ -930,20 +1000,21 @@
9301000
*/
9311001
cson_value * json_page_stat(void){
9321002
i64 t, fsize;
9331003
int n, m;
9341004
const char *zDb;
935
- enum { BufLen = 200 };
1005
+ enum { BufLen = 1000 };
9361006
char zBuf[BufLen];
9371007
cson_value * jv = NULL;
9381008
cson_object * jo = NULL;
9391009
cson_value * jv2 = NULL;
9401010
cson_object * jo2 = NULL;
941
-#if 0 /* FIXME: credentials */
9421011
login_check_credentials();
943
- if( !g.okRead ){ login_needed(); return NULL; }
944
-#endif
1012
+ if( !g.perm.Read ){
1013
+ g.json.resultCode = FSL_JSON_E_DENIED;
1014
+ return NULL;
1015
+ }
9451016
#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));
9461017
9471018
jv = cson_value_new_object();
9481019
jo = cson_value_get_object(jv);
9491020
@@ -1116,10 +1187,19 @@
11161187
void json_cmd_top(void){
11171188
char const * cmd = NULL;
11181189
int rc = 1002;
11191190
cson_value * payload = NULL;
11201191
JsonPageDef const * pageDef;
1192
+ memset( &g.perm, 0xff, sizeof(g.perm) )
1193
+ /* In CLI mode fossil does not use permissions
1194
+ and they all default to false. We enable them
1195
+ here because (A) fossil doesn't use them in local
1196
+ mode but (B) having them set gives us one less
1197
+ difference in the CLI/CGI/Server-mode JSON
1198
+ handling.
1199
+ */
1200
+ ;
11211201
json_main_bootstrap();
11221202
json_mode_bootstrap();
11231203
if( g.argc<3 ){
11241204
goto usage;
11251205
}
11261206
--- src/json.c
+++ src/json.c
@@ -226,10 +226,56 @@
226 */
227 int json_setenv( char const * zKey, cson_value * v ){
228 return cson_object_set( g.json.param.o, zKey, v );
229 }
230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
232 /*
233 ** Returns the current request's JSON authentication token, or NULL if
234 ** none is found. The token's memory is owned by (or shared with)
235 ** g.json.
@@ -320,11 +366,10 @@
320 */
321 static void json_mode_bootstrap(){
322 static char once = 0 /* guard against multiple runs */;
323 char const * zPath = P("PATH_INFO");
324 cson_value * pathSplit = NULL;
325 cson_array * ar = NULL;
326 assert( (0==once) && "json_mode_bootstrap() called too many times!");
327 if( once ){
328 return;
329 }else{
330 once = 1;
@@ -334,17 +379,24 @@
334 g.json.cmd.offset = -1;
335 if( !g.isCGI && g.fullHttpReply ){
336 /* workaround for server mode, so we see it as CGI mode. */
337 g.isCGI = 1;
338 }
 
 
 
 
 
 
 
339 #if defined(NDEBUG)
340 /* avoids debug messages on stderr in JSON mode */
341 sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0);
342 #endif
343
344 g.json.cmd.v = cson_value_new_array();
345 ar = g.json.cmd.a = cson_value_get_array(g.json.cmd.v);
346 json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v, 1 );
347 /*
348 The following if/else block translates the PATH_INFO path (in
349 CLI/server modes) or g.argv (CLI mode) into an internal list so
350 that we can simplify command dispatching later on.
@@ -351,20 +403,17 @@
351
352 Note that translating g.argv this way is overkill but allows us to
353 avoid CLI-only special-case handling in other code, e.g.
354 json_command_arg().
355 */
356 if( zPath ){/* either CLI or server mode... */
357 /* Translate fossil's PATH_INFO into cson_cgi for later
358 convenience, to help consolidate how we handle CGI/server
359 modes. This block is hit when running in plain server mode.
360 */
361 char const * p = zPath /* current byte */;
362 char const * head = p /* current start-of-token */;
363 unsigned int len = 0 /* current token's lengh */;
364 assert( g.isCGI && "g.isCGI should have been set by now." );
365 for( ;*p!='?'; ++p){
366 if( !*p || ('/' == *p) ){
367 if( len ){
368 cson_value * part;
369 char * zPart;
370 assert( head != p );
@@ -373,11 +422,11 @@
373 memcpy(zPart, head, len);
374 zPart[len] = 0;
375 dehttpize(zPart);
376 part = cson_value_new_string(zPart, strlen(zPart));
377 free(zPart);
378 cson_array_append( ar, part );
379 len = 0;
380 }
381 if( !*p ){
382 break;
383 }
@@ -394,11 +443,11 @@
394 arg = g.argv[i];
395 if( !arg || !*arg ){
396 continue;
397 }
398 part = cson_value_new_string(arg,strlen(arg));
399 cson_array_append(ar, part);
400 }
401 }
402
403 /* g.json.reqPayload exists only to simplify some of our access to
404 the request payload. We currently only use this in the context of
@@ -408,10 +457,31 @@
408 g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v )
409 /* g.json.reqPayload.o may legally be NULL, which means only that
410 g.json.reqPayload.v is-not-a Object.
411 */;
412 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413 json_auth_token()/* will copy our auth token, if any, to fossil's core. */;
414 if( g.isCGI ){
415 login_check_credentials()/* populates g.perm */;
416 }
417 else{
@@ -930,20 +1000,21 @@
930 */
931 cson_value * json_page_stat(void){
932 i64 t, fsize;
933 int n, m;
934 const char *zDb;
935 enum { BufLen = 200 };
936 char zBuf[BufLen];
937 cson_value * jv = NULL;
938 cson_object * jo = NULL;
939 cson_value * jv2 = NULL;
940 cson_object * jo2 = NULL;
941 #if 0 /* FIXME: credentials */
942 login_check_credentials();
943 if( !g.okRead ){ login_needed(); return NULL; }
944 #endif
 
 
945 #define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));
946
947 jv = cson_value_new_object();
948 jo = cson_value_get_object(jv);
949
@@ -1116,10 +1187,19 @@
1116 void json_cmd_top(void){
1117 char const * cmd = NULL;
1118 int rc = 1002;
1119 cson_value * payload = NULL;
1120 JsonPageDef const * pageDef;
 
 
 
 
 
 
 
 
 
1121 json_main_bootstrap();
1122 json_mode_bootstrap();
1123 if( g.argc<3 ){
1124 goto usage;
1125 }
1126
--- src/json.c
+++ src/json.c
@@ -226,10 +226,56 @@
226 */
227 int json_setenv( char const * zKey, cson_value * v ){
228 return cson_object_set( g.json.param.o, zKey, v );
229 }
230
231 /*
232 ** Guesses a RESPONSE Content-Type value based (primarily) on the
233 ** HTTP_ACCEPT header.
234 **
235 ** It will try to figure out if the client can support
236 ** application/json or application/javascript, and will fall back to
237 ** text/plain if it cannot figure out anything more specific.
238 **
239 ** Returned memory is static and immutable.
240 **
241 */
242 char const * json_guess_content_type(){
243 char const * cset;
244 char doUtf8;
245 cset = PD("HTTP_ACCEPT_CHARSET",NULL);
246 doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
247 ? 1 : 0;
248 if( g.json.jsonp ){
249 return doUtf8
250 ? "application/javascript; charset=utf-8"
251 : "application/javascript";
252 }else{
253 /*
254 Content-type
255
256 If the browser does not sent an ACCEPT for application/json
257 then we fall back to text/plain.
258 */
259 char const * cstr;
260 cstr = PD("HTTP_ACCEPT",NULL);
261 if( NULL == cstr ){
262 return doUtf8
263 ? "application/json; charset=utf-8"
264 : "application/json";
265 }else{
266 if( strstr( cstr, "application/json" )
267 || strstr( cstr, "*/*" ) ){
268 return doUtf8
269 ? "application/json; charset=utf-8"
270 : "application/json";
271 }else{
272 return "text/plain";
273 }
274 }
275 }
276 }
277
278 /*
279 ** Returns the current request's JSON authentication token, or NULL if
280 ** none is found. The token's memory is owned by (or shared with)
281 ** g.json.
@@ -320,11 +366,10 @@
366 */
367 static void json_mode_bootstrap(){
368 static char once = 0 /* guard against multiple runs */;
369 char const * zPath = P("PATH_INFO");
370 cson_value * pathSplit = NULL;
 
371 assert( (0==once) && "json_mode_bootstrap() called too many times!");
372 if( once ){
373 return;
374 }else{
375 once = 1;
@@ -334,17 +379,24 @@
379 g.json.cmd.offset = -1;
380 if( !g.isCGI && g.fullHttpReply ){
381 /* workaround for server mode, so we see it as CGI mode. */
382 g.isCGI = 1;
383 }
384 if(! g.json.post.v ){
385 /* If cgi_init() reads POSTed JSON then it sets the content type.
386 If it did not then we need to set it.
387 */
388 cgi_set_content_type(json_guess_content_type());
389 }
390
391 #if defined(NDEBUG)
392 /* avoids debug messages on stderr in JSON mode */
393 sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0);
394 #endif
395
396 g.json.cmd.v = cson_value_new_array();
397 g.json.cmd.a = cson_value_get_array(g.json.cmd.v);
398 json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v, 1 );
399 /*
400 The following if/else block translates the PATH_INFO path (in
401 CLI/server modes) or g.argv (CLI mode) into an internal list so
402 that we can simplify command dispatching later on.
@@ -351,20 +403,17 @@
403
404 Note that translating g.argv this way is overkill but allows us to
405 avoid CLI-only special-case handling in other code, e.g.
406 json_command_arg().
407 */
408 if( zPath ){/* Either CGI or server mode... */
409 /* Translate PATH_INFO into JSON for later convenience. */
 
 
 
410 char const * p = zPath /* current byte */;
411 char const * head = p /* current start-of-token */;
412 unsigned int len = 0 /* current token's lengh */;
413 assert( g.isCGI && "g.isCGI should have been set by now." );
414 for( ; ; ++p){
415 if( !*p || ('/' == *p) ){
416 if( len ){
417 cson_value * part;
418 char * zPart;
419 assert( head != p );
@@ -373,11 +422,11 @@
422 memcpy(zPart, head, len);
423 zPart[len] = 0;
424 dehttpize(zPart);
425 part = cson_value_new_string(zPart, strlen(zPart));
426 free(zPart);
427 cson_array_append( g.json.cmd.a, part );
428 len = 0;
429 }
430 if( !*p ){
431 break;
432 }
@@ -394,11 +443,11 @@
443 arg = g.argv[i];
444 if( !arg || !*arg ){
445 continue;
446 }
447 part = cson_value_new_string(arg,strlen(arg));
448 cson_array_append(g.json.cmd.a, part);
449 }
450 }
451
452 /* g.json.reqPayload exists only to simplify some of our access to
453 the request payload. We currently only use this in the context of
@@ -408,10 +457,31 @@
457 g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v )
458 /* g.json.reqPayload.o may legally be NULL, which means only that
459 g.json.reqPayload.v is-not-a Object.
460 */;
461 }
462
463 do{/* set up JSON out formatting options. */
464 unsigned char indent = g.isCGI ? 0 : 1;
465 cson_value const * indentV = json_getenv("indent");
466 if(indentV){
467 if(cson_value_is_string(indentV)){
468 int n = atoi(cson_string_cstr(cson_value_get_string(indentV)));
469 indent = (n>0)
470 ? (unsigned char)n
471 : 0;
472 }else if(cson_value_is_number(indentV)){
473 double n = cson_value_get_integer(indentV);
474 indent = (n>0)
475 ? (unsigned char)n
476 : 0;
477 }
478 }
479 g.json.outOpt.indentation = indent;
480 g.json.outOpt.addNewline = g.isCGI ? 0 : 1;
481 }while(0);
482
483 json_auth_token()/* will copy our auth token, if any, to fossil's core. */;
484 if( g.isCGI ){
485 login_check_credentials()/* populates g.perm */;
486 }
487 else{
@@ -930,20 +1000,21 @@
1000 */
1001 cson_value * json_page_stat(void){
1002 i64 t, fsize;
1003 int n, m;
1004 const char *zDb;
1005 enum { BufLen = 1000 };
1006 char zBuf[BufLen];
1007 cson_value * jv = NULL;
1008 cson_object * jo = NULL;
1009 cson_value * jv2 = NULL;
1010 cson_object * jo2 = NULL;
 
1011 login_check_credentials();
1012 if( !g.perm.Read ){
1013 g.json.resultCode = FSL_JSON_E_DENIED;
1014 return NULL;
1015 }
1016 #define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf)));
1017
1018 jv = cson_value_new_object();
1019 jo = cson_value_get_object(jv);
1020
@@ -1116,10 +1187,19 @@
1187 void json_cmd_top(void){
1188 char const * cmd = NULL;
1189 int rc = 1002;
1190 cson_value * payload = NULL;
1191 JsonPageDef const * pageDef;
1192 memset( &g.perm, 0xff, sizeof(g.perm) )
1193 /* In CLI mode fossil does not use permissions
1194 and they all default to false. We enable them
1195 here because (A) fossil doesn't use them in local
1196 mode but (B) having them set gives us one less
1197 difference in the CLI/CGI/Server-mode JSON
1198 handling.
1199 */
1200 ;
1201 json_main_bootstrap();
1202 json_mode_bootstrap();
1203 if( g.argc<3 ){
1204 goto usage;
1205 }
1206
+9 -4
--- src/login.c
+++ src/login.c
@@ -951,14 +951,19 @@
951951
/*
952952
** Call this routine when the credential check fails. It causes
953953
** a redirect to the "login" page.
954954
*/
955955
void login_needed(void){
956
- const char *zUrl = PD("REQUEST_URI", "index");
957
- cgi_redirect(mprintf("login?g=%T", zUrl));
958
- /* NOTREACHED */
959
- assert(0);
956
+ if(g.json.isJsonMode){
957
+ json_err( FSL_JSON_E_DENIED, NULL, 1 );
958
+ fossil_exit(0);
959
+ }else{
960
+ const char *zUrl = PD("REQUEST_URI", "index");
961
+ cgi_redirect(mprintf("login?g=%T", zUrl));
962
+ /* NOTREACHED */
963
+ assert(0);
964
+ }
960965
}
961966
962967
/*
963968
** Call this routine if the user lacks okHistory permission. If
964969
** the anonymous user has okHistory permission, then paint a mesage
965970
--- src/login.c
+++ src/login.c
@@ -951,14 +951,19 @@
951 /*
952 ** Call this routine when the credential check fails. It causes
953 ** a redirect to the "login" page.
954 */
955 void login_needed(void){
956 const char *zUrl = PD("REQUEST_URI", "index");
957 cgi_redirect(mprintf("login?g=%T", zUrl));
958 /* NOTREACHED */
959 assert(0);
 
 
 
 
 
960 }
961
962 /*
963 ** Call this routine if the user lacks okHistory permission. If
964 ** the anonymous user has okHistory permission, then paint a mesage
965
--- src/login.c
+++ src/login.c
@@ -951,14 +951,19 @@
951 /*
952 ** Call this routine when the credential check fails. It causes
953 ** a redirect to the "login" page.
954 */
955 void login_needed(void){
956 if(g.json.isJsonMode){
957 json_err( FSL_JSON_E_DENIED, NULL, 1 );
958 fossil_exit(0);
959 }else{
960 const char *zUrl = PD("REQUEST_URI", "index");
961 cgi_redirect(mprintf("login?g=%T", zUrl));
962 /* NOTREACHED */
963 assert(0);
964 }
965 }
966
967 /*
968 ** Call this routine if the user lacks okHistory permission. If
969 ** the anonymous user has okHistory permission, then paint a mesage
970
+2 -1
--- src/main.c
+++ src/main.c
@@ -180,10 +180,11 @@
180180
*/
181181
int resultCode; /* used for passing back specific codes from /json callbacks. */
182182
int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
183183
cson_output_opt outOpt; /* formatting options for JSON mode. */
184184
cson_value * authToken; /* authentication token */
185
+ char const * jsonp; /* Name of JSONP function wrapper. */
185186
struct { /* "garbage collector" */
186187
cson_value * v;
187188
cson_object * o;
188189
} gc;
189190
struct { /* JSON POST data. */
@@ -309,11 +310,11 @@
309310
#else
310311
g.json.errorDetailParanoia = 0;
311312
#endif
312313
g.json.outOpt = cson_output_opt_empty;
313314
g.json.outOpt.addNewline = 1;
314
- g.json.outOpt.indentation = 1 /* FIXME: make configurable */;
315
+ g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
315316
for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]);
316317
if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
317318
zCmdName = "cgi";
318319
g.isCGI = 1;
319320
}else if( argc<2 ){
320321
--- src/main.c
+++ src/main.c
@@ -180,10 +180,11 @@
180 */
181 int resultCode; /* used for passing back specific codes from /json callbacks. */
182 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
183 cson_output_opt outOpt; /* formatting options for JSON mode. */
184 cson_value * authToken; /* authentication token */
 
185 struct { /* "garbage collector" */
186 cson_value * v;
187 cson_object * o;
188 } gc;
189 struct { /* JSON POST data. */
@@ -309,11 +310,11 @@
309 #else
310 g.json.errorDetailParanoia = 0;
311 #endif
312 g.json.outOpt = cson_output_opt_empty;
313 g.json.outOpt.addNewline = 1;
314 g.json.outOpt.indentation = 1 /* FIXME: make configurable */;
315 for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]);
316 if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
317 zCmdName = "cgi";
318 g.isCGI = 1;
319 }else if( argc<2 ){
320
--- src/main.c
+++ src/main.c
@@ -180,10 +180,11 @@
180 */
181 int resultCode; /* used for passing back specific codes from /json callbacks. */
182 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
183 cson_output_opt outOpt; /* formatting options for JSON mode. */
184 cson_value * authToken; /* authentication token */
185 char const * jsonp; /* Name of JSONP function wrapper. */
186 struct { /* "garbage collector" */
187 cson_value * v;
188 cson_object * o;
189 } gc;
190 struct { /* JSON POST data. */
@@ -309,11 +310,11 @@
310 #else
311 g.json.errorDetailParanoia = 0;
312 #endif
313 g.json.outOpt = cson_output_opt_empty;
314 g.json.outOpt.addNewline = 1;
315 g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */;
316 for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]);
317 if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
318 zCmdName = "cgi";
319 g.isCGI = 1;
320 }else if( argc<2 ){
321

Keyboard Shortcuts

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