Fossil SCM

Several minor internal cleanups in the /json bits, most notably how g.json.isJsonMode gets initialized (based strictly on the path/command, not guessing based on POST input).

stephan 2020-01-29 12:34 trunk
Commit 39bef9274530c454c2368c442f7b6a54e2d39a5319db8b59e8f7ccda3cb4df5c
+24 -20
--- src/cgi.c
+++ src/cgi.c
@@ -1035,13 +1035,27 @@
10351035
}
10361036
if( zPathInfo==0 ){
10371037
int i, j;
10381038
for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){}
10391039
for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){}
1040
- cgi_set_parameter("PATH_INFO", mprintf("%.*s", j-i, zRequestUri+i));
1040
+ zPathInfo = mprintf("%.*s", j-i, zRequestUri+i);
1041
+ cgi_set_parameter("PATH_INFO", zPathInfo);
1042
+ }
1043
+#ifdef FOSSIL_ENABLE_JSON
1044
+ if(strncmp("/json",zPathInfo,5)==0
1045
+ && (zPathInfo[5]==0 || zPathInfo[5]=='/')){
1046
+ /* We need to change some following behaviour depending on whether
1047
+ ** we are operating in JSON mode or not. We cannot, however, be
1048
+ ** certain whether we should/need to be in JSON mode until the
1049
+ ** PATH_INFO is set up.
1050
+ */
1051
+ g.json.isJsonMode = 1;
1052
+ }else{
1053
+ assert(!g.json.isJsonMode &&
1054
+ "Internal misconfiguration of g.json.isJsonMode");
10411055
}
1042
-
1056
+#endif
10431057
z = (char*)P("HTTP_COOKIE");
10441058
if( z ){
10451059
z = mprintf("%s",z);
10461060
add_param_list(z, ';');
10471061
}
@@ -1071,29 +1085,18 @@
10711085
if( fossil_strcmp(zType, "application/x-fossil")==0 ){
10721086
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
10731087
blob_uncompress(&g.cgiIn, &g.cgiIn);
10741088
}
10751089
#ifdef FOSSIL_ENABLE_JSON
1076
- else if( noJson==0 && (fossil_strcmp(zType, "application/json")==0
1077
- || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/
1078
- || fossil_strcmp(zType,"application/javascript")==0) ){
1079
- g.json.isJsonMode = 1;
1090
+ else if( noJson==0 && g.json.isJsonMode!=0
1091
+ && json_can_consume_content_type(zType)!=0 ){
10801092
cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
1081
- /* FIXMEs:
1082
-
1083
- - See if fossil really needs g.cgiIn to be set for this purpose
1084
- (i don't think it does). If it does then fill g.cgiIn and
1085
- refactor to parse the JSON from there.
1086
-
1087
- - After parsing POST JSON, copy the "first layer" of keys/values
1088
- to cgi_setenv(), honoring the upper-case distinction used
1089
- in add_param_list(). However...
1090
-
1091
- - If we do that then we might get a disconnect in precedence of
1092
- GET/POST arguments. i prefer for GET entries to take precedence
1093
- over like-named POST entries, but in order for that to happen we
1094
- need to process QUERY_STRING _after_ reading the POST data.
1093
+ /*
1094
+ Potential TODOs:
1095
+
1096
+ 1) If parsing fails, immediately return an error response
1097
+ without dispatching the ostensibly-upcoming JSON API.
10951098
*/
10961099
cgi_set_content_type(json_guess_content_type());
10971100
}
10981101
#endif /* FOSSIL_ENABLE_JSON */
10991102
else{
@@ -1603,10 +1606,11 @@
16031606
}
16041607
if( zIpAddr ){
16051608
cgi_setenv("REMOTE_ADDR", zIpAddr);
16061609
g.zIpAddr = mprintf("%s", zIpAddr);
16071610
}
1611
+
16081612
16091613
/* Get all the optional fields that follow the first line.
16101614
*/
16111615
while( fgets(zLine,sizeof(zLine),g.httpIn) ){
16121616
char *zFieldName;
16131617
--- src/cgi.c
+++ src/cgi.c
@@ -1035,13 +1035,27 @@
1035 }
1036 if( zPathInfo==0 ){
1037 int i, j;
1038 for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){}
1039 for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){}
1040 cgi_set_parameter("PATH_INFO", mprintf("%.*s", j-i, zRequestUri+i));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1041 }
1042
1043 z = (char*)P("HTTP_COOKIE");
1044 if( z ){
1045 z = mprintf("%s",z);
1046 add_param_list(z, ';');
1047 }
@@ -1071,29 +1085,18 @@
1071 if( fossil_strcmp(zType, "application/x-fossil")==0 ){
1072 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
1073 blob_uncompress(&g.cgiIn, &g.cgiIn);
1074 }
1075 #ifdef FOSSIL_ENABLE_JSON
1076 else if( noJson==0 && (fossil_strcmp(zType, "application/json")==0
1077 || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/
1078 || fossil_strcmp(zType,"application/javascript")==0) ){
1079 g.json.isJsonMode = 1;
1080 cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
1081 /* FIXMEs:
1082
1083 - See if fossil really needs g.cgiIn to be set for this purpose
1084 (i don't think it does). If it does then fill g.cgiIn and
1085 refactor to parse the JSON from there.
1086
1087 - After parsing POST JSON, copy the "first layer" of keys/values
1088 to cgi_setenv(), honoring the upper-case distinction used
1089 in add_param_list(). However...
1090
1091 - If we do that then we might get a disconnect in precedence of
1092 GET/POST arguments. i prefer for GET entries to take precedence
1093 over like-named POST entries, but in order for that to happen we
1094 need to process QUERY_STRING _after_ reading the POST data.
1095 */
1096 cgi_set_content_type(json_guess_content_type());
1097 }
1098 #endif /* FOSSIL_ENABLE_JSON */
1099 else{
@@ -1603,10 +1606,11 @@
1603 }
1604 if( zIpAddr ){
1605 cgi_setenv("REMOTE_ADDR", zIpAddr);
1606 g.zIpAddr = mprintf("%s", zIpAddr);
1607 }
 
1608
1609 /* Get all the optional fields that follow the first line.
1610 */
1611 while( fgets(zLine,sizeof(zLine),g.httpIn) ){
1612 char *zFieldName;
1613
--- src/cgi.c
+++ src/cgi.c
@@ -1035,13 +1035,27 @@
1035 }
1036 if( zPathInfo==0 ){
1037 int i, j;
1038 for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){}
1039 for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){}
1040 zPathInfo = mprintf("%.*s", j-i, zRequestUri+i);
1041 cgi_set_parameter("PATH_INFO", zPathInfo);
1042 }
1043 #ifdef FOSSIL_ENABLE_JSON
1044 if(strncmp("/json",zPathInfo,5)==0
1045 && (zPathInfo[5]==0 || zPathInfo[5]=='/')){
1046 /* We need to change some following behaviour depending on whether
1047 ** we are operating in JSON mode or not. We cannot, however, be
1048 ** certain whether we should/need to be in JSON mode until the
1049 ** PATH_INFO is set up.
1050 */
1051 g.json.isJsonMode = 1;
1052 }else{
1053 assert(!g.json.isJsonMode &&
1054 "Internal misconfiguration of g.json.isJsonMode");
1055 }
1056 #endif
1057 z = (char*)P("HTTP_COOKIE");
1058 if( z ){
1059 z = mprintf("%s",z);
1060 add_param_list(z, ';');
1061 }
@@ -1071,29 +1085,18 @@
1085 if( fossil_strcmp(zType, "application/x-fossil")==0 ){
1086 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
1087 blob_uncompress(&g.cgiIn, &g.cgiIn);
1088 }
1089 #ifdef FOSSIL_ENABLE_JSON
1090 else if( noJson==0 && g.json.isJsonMode!=0
1091 && json_can_consume_content_type(zType)!=0 ){
 
 
1092 cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
1093 /*
1094 Potential TODOs:
1095
1096 1) If parsing fails, immediately return an error response
1097 without dispatching the ostensibly-upcoming JSON API.
 
 
 
 
 
 
 
 
 
1098 */
1099 cgi_set_content_type(json_guess_content_type());
1100 }
1101 #endif /* FOSSIL_ENABLE_JSON */
1102 else{
@@ -1603,10 +1606,11 @@
1606 }
1607 if( zIpAddr ){
1608 cgi_setenv("REMOTE_ADDR", zIpAddr);
1609 g.zIpAddr = mprintf("%s", zIpAddr);
1610 }
1611
1612
1613 /* Get all the optional fields that follow the first line.
1614 */
1615 while( fgets(zLine,sizeof(zLine),g.httpIn) ){
1616 char *zFieldName;
1617
+43 -22
--- src/json.c
+++ src/json.c
@@ -52,14 +52,15 @@
5252
"resultCode" /*resultCode*/,
5353
"resultText" /*resultText*/,
5454
"timestamp" /*timestamp*/
5555
};
5656
57
-
58
-
5957
/*
6058
** Returns true (non-0) if fossil appears to be running in JSON mode.
59
+** and either has JSON POSTed input awaiting consumption or fossil is
60
+** running in HTTP mode (in which case certain JSON data *might* be
61
+** available via GET parameters).
6162
*/
6263
int fossil_has_json(){
6364
return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
6465
}
6566
@@ -576,10 +577,25 @@
576577
return "text/plain";
577578
}
578579
}
579580
}
580581
}
582
+
583
+/*
584
+ ** Given a request CONTENT_TYPE value, this function returns true
585
+ ** if it is of a type which the JSON API can ostensibly read.
586
+ **
587
+ ** It accepts any of application/json, text/plain, or
588
+ ** application/javascript. The former is preferred, but was not
589
+ ** widespread when this API was initially built, so the latter forms
590
+ ** are permitted as fallbacks.
591
+ */
592
+int json_can_consume_content_type(const char * zType){
593
+ return fossil_strcmp(zType, "application/json")==0
594
+ || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/
595
+ || fossil_strcmp(zType,"application/javascript")==0;
596
+}
581597
582598
/*
583599
** Sends pResponse to the output stream as the response object. This
584600
** function does no validation of pResponse except to assert() that it
585601
** is not NULL. The caller is responsible for ensuring that it meets
@@ -925,11 +941,12 @@
925941
if( once ){
926942
return;
927943
}else{
928944
once = 1;
929945
}
930
- g.json.isJsonMode = 1;
946
+ assert(g.json.isJsonMode
947
+ && "g.json.isJsonMode should have been set up by now.");
931948
g.json.resultCode = 0;
932949
g.json.cmd.offset = -1;
933950
g.json.jsonp = PD("jsonp",NULL)
934951
/* FIXME: do some sanity checking on g.json.jsonp and ignore it
935952
if it is not halfway reasonable.
@@ -1005,18 +1022,16 @@
10051022
inFile = (0==strcmp("-",jfile))
10061023
? stdin
10071024
: fossil_fopen(jfile,"rb");
10081025
if(!inFile){
10091026
g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
1010
- fossil_panic("Could not open JSON file [%s].",jfile)
1027
+ fossil_fatal("Could not open JSON file [%s].",jfile)
10111028
/* Does not return. */
10121029
;
10131030
}
10141031
cgi_parse_POST_JSON(inFile, 0);
1015
- if( stdin != inFile ){
1016
- fclose(inFile);
1017
- }
1032
+ fossil_fclose(inFile);
10181033
break;
10191034
}
10201035
10211036
/* g.json.reqPayload exists only to simplify some of our access to
10221037
the request payload. We currently only use this in the context of
@@ -1110,13 +1125,13 @@
11101125
cson_value_get_string( \
11111126
cson_array_get(ar,i) \
11121127
))
11131128
char const * tok = NEXT;
11141129
while( tok ){
1115
- if( !g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
1116
- ? (0==strcmp(g.argv[1],tok))
1117
- : (0==strncmp("json",tok,4))
1130
+ if( g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
1131
+ ? (0==strncmp("json",tok,4))
1132
+ : (0==strcmp(g.argv[1],tok))
11181133
){
11191134
g.json.cmd.offset = i;
11201135
break;
11211136
}
11221137
++i;
@@ -1383,17 +1398,17 @@
13831398
resultCode = json_dumbdown_rc(resultCode ? resultCode : g.json.resultCode);
13841399
o = cson_new_object();
13851400
v = cson_object_value(o);
13861401
if( ! o ) return NULL;
13871402
#define SET(K) if(!tmp) goto cleanup; \
1388
- rc = cson_object_set( o, K, tmp ); \
1389
- if(rc) do{\
1390
- cson_value_free(tmp); \
1391
- tmp = NULL; \
1392
- goto cleanup; \
1403
+ cson_value_add_reference(tmp); \
1404
+ rc = cson_object_set( o, K, tmp ); \
1405
+ cson_value_free(tmp); \
1406
+ if(rc) do{ \
1407
+ tmp = NULL; \
1408
+ goto cleanup; \
13931409
}while(0)
1394
-
13951410
13961411
tmp = json_new_string(MANIFEST_UUID);
13971412
SET("fossil");
13981413
13991414
tmp = json_new_timestamp(-1);
@@ -1418,10 +1433,13 @@
14181433
if(g.json.cmd.commandStr){
14191434
tmp = json_new_string(g.json.cmd.commandStr);
14201435
}else{
14211436
tmp = json_response_command_path();
14221437
}
1438
+ if(!tmp){
1439
+ tmp = json_new_string("???");
1440
+ }
14231441
SET("command");
14241442
14251443
tmp = json_getenv(FossilJsonKeys.requestId);
14261444
if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
14271445
@@ -1434,12 +1452,13 @@
14341452
tmp = g.json.param.v;
14351453
SET("$params");
14361454
}
14371455
if(0){/*Only for debugging, add some info to the response.*/
14381456
tmp = cson_value_new_integer( g.json.cmd.offset );
1439
- cson_object_set( o, "cmd.offset", tmp );
1440
- cson_object_set( o, "isCGI", cson_value_new_bool( g.isHTTP ) );
1457
+ SET("cmd.offset");
1458
+ tmp = cson_value_new_bool( g.isHTTP );
1459
+ SET("isCGI");
14411460
}
14421461
}
14431462
14441463
if(fossil_timer_is_active(g.json.timerId)){
14451464
/* This is, philosophically speaking, not quite the right place
@@ -1454,11 +1473,10 @@
14541473
cson_object_set(o,"procTimeUs", cson_value_new_integer((cson_int_t)span));
14551474
span /= 1000/*for milliseconds */;
14561475
cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)span));
14571476
assert(!fossil_timer_is_active(g.json.timerId));
14581477
g.json.timerId = -1;
1459
-
14601478
}
14611479
if(g.json.warnings){
14621480
tmp = cson_array_value(g.json.warnings);
14631481
SET("warnings");
14641482
}
@@ -1472,12 +1490,13 @@
14721490
tmp = payload;
14731491
SET(FossilJsonKeys.payload);
14741492
}
14751493
}
14761494
1477
- if(json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
1478
- &&(g.perm.Admin||g.perm.Setup)){
1495
+ if((g.perm.Admin||g.perm.Setup)
1496
+ && json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
1497
+ ){
14791498
tmp = json_g_to_json();
14801499
SET("g");
14811500
}
14821501
14831502
#undef SET
@@ -1514,10 +1533,11 @@
15141533
void json_err( int code, char const * msg, int alsoOutput ){
15151534
int rc = code ? code : (g.json.resultCode
15161535
? g.json.resultCode
15171536
: FSL_JSON_E_UNKNOWN);
15181537
cson_value * resp = NULL;
1538
+ if(g.json.isJsonMode==0) return;
15191539
rc = json_dumbdown_rc(rc);
15201540
if( rc && !msg ){
15211541
msg = g.zErrMsg;
15221542
if(!msg){
15231543
msg = json_err_cstr(rc);
@@ -1524,11 +1544,12 @@
15241544
}
15251545
}
15261546
resp = json_create_response(rc, msg, NULL);
15271547
if(!resp){
15281548
/* about the only error case here is out-of-memory. DO NOT
1529
- call fossil_panic() here because that calls this function.
1549
+ call fossil_panic() or fossil_fatal() here because those
1550
+ allocate.
15301551
*/
15311552
fprintf(stderr, "%s: Fatal error: could not allocate "
15321553
"response object.\n", g.argv[0]);
15331554
fossil_exit(1);
15341555
}
15351556
--- src/json.c
+++ src/json.c
@@ -52,14 +52,15 @@
52 "resultCode" /*resultCode*/,
53 "resultText" /*resultText*/,
54 "timestamp" /*timestamp*/
55 };
56
57
58
59 /*
60 ** Returns true (non-0) if fossil appears to be running in JSON mode.
 
 
 
61 */
62 int fossil_has_json(){
63 return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
64 }
65
@@ -576,10 +577,25 @@
576 return "text/plain";
577 }
578 }
579 }
580 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
582 /*
583 ** Sends pResponse to the output stream as the response object. This
584 ** function does no validation of pResponse except to assert() that it
585 ** is not NULL. The caller is responsible for ensuring that it meets
@@ -925,11 +941,12 @@
925 if( once ){
926 return;
927 }else{
928 once = 1;
929 }
930 g.json.isJsonMode = 1;
 
931 g.json.resultCode = 0;
932 g.json.cmd.offset = -1;
933 g.json.jsonp = PD("jsonp",NULL)
934 /* FIXME: do some sanity checking on g.json.jsonp and ignore it
935 if it is not halfway reasonable.
@@ -1005,18 +1022,16 @@
1005 inFile = (0==strcmp("-",jfile))
1006 ? stdin
1007 : fossil_fopen(jfile,"rb");
1008 if(!inFile){
1009 g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
1010 fossil_panic("Could not open JSON file [%s].",jfile)
1011 /* Does not return. */
1012 ;
1013 }
1014 cgi_parse_POST_JSON(inFile, 0);
1015 if( stdin != inFile ){
1016 fclose(inFile);
1017 }
1018 break;
1019 }
1020
1021 /* g.json.reqPayload exists only to simplify some of our access to
1022 the request payload. We currently only use this in the context of
@@ -1110,13 +1125,13 @@
1110 cson_value_get_string( \
1111 cson_array_get(ar,i) \
1112 ))
1113 char const * tok = NEXT;
1114 while( tok ){
1115 if( !g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
1116 ? (0==strcmp(g.argv[1],tok))
1117 : (0==strncmp("json",tok,4))
1118 ){
1119 g.json.cmd.offset = i;
1120 break;
1121 }
1122 ++i;
@@ -1383,17 +1398,17 @@
1383 resultCode = json_dumbdown_rc(resultCode ? resultCode : g.json.resultCode);
1384 o = cson_new_object();
1385 v = cson_object_value(o);
1386 if( ! o ) return NULL;
1387 #define SET(K) if(!tmp) goto cleanup; \
1388 rc = cson_object_set( o, K, tmp ); \
1389 if(rc) do{\
1390 cson_value_free(tmp); \
1391 tmp = NULL; \
1392 goto cleanup; \
 
1393 }while(0)
1394
1395
1396 tmp = json_new_string(MANIFEST_UUID);
1397 SET("fossil");
1398
1399 tmp = json_new_timestamp(-1);
@@ -1418,10 +1433,13 @@
1418 if(g.json.cmd.commandStr){
1419 tmp = json_new_string(g.json.cmd.commandStr);
1420 }else{
1421 tmp = json_response_command_path();
1422 }
 
 
 
1423 SET("command");
1424
1425 tmp = json_getenv(FossilJsonKeys.requestId);
1426 if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
1427
@@ -1434,12 +1452,13 @@
1434 tmp = g.json.param.v;
1435 SET("$params");
1436 }
1437 if(0){/*Only for debugging, add some info to the response.*/
1438 tmp = cson_value_new_integer( g.json.cmd.offset );
1439 cson_object_set( o, "cmd.offset", tmp );
1440 cson_object_set( o, "isCGI", cson_value_new_bool( g.isHTTP ) );
 
1441 }
1442 }
1443
1444 if(fossil_timer_is_active(g.json.timerId)){
1445 /* This is, philosophically speaking, not quite the right place
@@ -1454,11 +1473,10 @@
1454 cson_object_set(o,"procTimeUs", cson_value_new_integer((cson_int_t)span));
1455 span /= 1000/*for milliseconds */;
1456 cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)span));
1457 assert(!fossil_timer_is_active(g.json.timerId));
1458 g.json.timerId = -1;
1459
1460 }
1461 if(g.json.warnings){
1462 tmp = cson_array_value(g.json.warnings);
1463 SET("warnings");
1464 }
@@ -1472,12 +1490,13 @@
1472 tmp = payload;
1473 SET(FossilJsonKeys.payload);
1474 }
1475 }
1476
1477 if(json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
1478 &&(g.perm.Admin||g.perm.Setup)){
 
1479 tmp = json_g_to_json();
1480 SET("g");
1481 }
1482
1483 #undef SET
@@ -1514,10 +1533,11 @@
1514 void json_err( int code, char const * msg, int alsoOutput ){
1515 int rc = code ? code : (g.json.resultCode
1516 ? g.json.resultCode
1517 : FSL_JSON_E_UNKNOWN);
1518 cson_value * resp = NULL;
 
1519 rc = json_dumbdown_rc(rc);
1520 if( rc && !msg ){
1521 msg = g.zErrMsg;
1522 if(!msg){
1523 msg = json_err_cstr(rc);
@@ -1524,11 +1544,12 @@
1524 }
1525 }
1526 resp = json_create_response(rc, msg, NULL);
1527 if(!resp){
1528 /* about the only error case here is out-of-memory. DO NOT
1529 call fossil_panic() here because that calls this function.
 
1530 */
1531 fprintf(stderr, "%s: Fatal error: could not allocate "
1532 "response object.\n", g.argv[0]);
1533 fossil_exit(1);
1534 }
1535
--- src/json.c
+++ src/json.c
@@ -52,14 +52,15 @@
52 "resultCode" /*resultCode*/,
53 "resultText" /*resultText*/,
54 "timestamp" /*timestamp*/
55 };
56
 
 
57 /*
58 ** Returns true (non-0) if fossil appears to be running in JSON mode.
59 ** and either has JSON POSTed input awaiting consumption or fossil is
60 ** running in HTTP mode (in which case certain JSON data *might* be
61 ** available via GET parameters).
62 */
63 int fossil_has_json(){
64 return g.json.isJsonMode && (g.isHTTP || g.json.post.o);
65 }
66
@@ -576,10 +577,25 @@
577 return "text/plain";
578 }
579 }
580 }
581 }
582
583 /*
584 ** Given a request CONTENT_TYPE value, this function returns true
585 ** if it is of a type which the JSON API can ostensibly read.
586 **
587 ** It accepts any of application/json, text/plain, or
588 ** application/javascript. The former is preferred, but was not
589 ** widespread when this API was initially built, so the latter forms
590 ** are permitted as fallbacks.
591 */
592 int json_can_consume_content_type(const char * zType){
593 return fossil_strcmp(zType, "application/json")==0
594 || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/
595 || fossil_strcmp(zType,"application/javascript")==0;
596 }
597
598 /*
599 ** Sends pResponse to the output stream as the response object. This
600 ** function does no validation of pResponse except to assert() that it
601 ** is not NULL. The caller is responsible for ensuring that it meets
@@ -925,11 +941,12 @@
941 if( once ){
942 return;
943 }else{
944 once = 1;
945 }
946 assert(g.json.isJsonMode
947 && "g.json.isJsonMode should have been set up by now.");
948 g.json.resultCode = 0;
949 g.json.cmd.offset = -1;
950 g.json.jsonp = PD("jsonp",NULL)
951 /* FIXME: do some sanity checking on g.json.jsonp and ignore it
952 if it is not halfway reasonable.
@@ -1005,18 +1022,16 @@
1022 inFile = (0==strcmp("-",jfile))
1023 ? stdin
1024 : fossil_fopen(jfile,"rb");
1025 if(!inFile){
1026 g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
1027 fossil_fatal("Could not open JSON file [%s].",jfile)
1028 /* Does not return. */
1029 ;
1030 }
1031 cgi_parse_POST_JSON(inFile, 0);
1032 fossil_fclose(inFile);
 
 
1033 break;
1034 }
1035
1036 /* g.json.reqPayload exists only to simplify some of our access to
1037 the request payload. We currently only use this in the context of
@@ -1110,13 +1125,13 @@
1125 cson_value_get_string( \
1126 cson_array_get(ar,i) \
1127 ))
1128 char const * tok = NEXT;
1129 while( tok ){
1130 if( g.isHTTP/*workaround for "abbreviated name" in CLI mode*/
1131 ? (0==strncmp("json",tok,4))
1132 : (0==strcmp(g.argv[1],tok))
1133 ){
1134 g.json.cmd.offset = i;
1135 break;
1136 }
1137 ++i;
@@ -1383,17 +1398,17 @@
1398 resultCode = json_dumbdown_rc(resultCode ? resultCode : g.json.resultCode);
1399 o = cson_new_object();
1400 v = cson_object_value(o);
1401 if( ! o ) return NULL;
1402 #define SET(K) if(!tmp) goto cleanup; \
1403 cson_value_add_reference(tmp); \
1404 rc = cson_object_set( o, K, tmp ); \
1405 cson_value_free(tmp); \
1406 if(rc) do{ \
1407 tmp = NULL; \
1408 goto cleanup; \
1409 }while(0)
 
1410
1411 tmp = json_new_string(MANIFEST_UUID);
1412 SET("fossil");
1413
1414 tmp = json_new_timestamp(-1);
@@ -1418,10 +1433,13 @@
1433 if(g.json.cmd.commandStr){
1434 tmp = json_new_string(g.json.cmd.commandStr);
1435 }else{
1436 tmp = json_response_command_path();
1437 }
1438 if(!tmp){
1439 tmp = json_new_string("???");
1440 }
1441 SET("command");
1442
1443 tmp = json_getenv(FossilJsonKeys.requestId);
1444 if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp );
1445
@@ -1434,12 +1452,13 @@
1452 tmp = g.json.param.v;
1453 SET("$params");
1454 }
1455 if(0){/*Only for debugging, add some info to the response.*/
1456 tmp = cson_value_new_integer( g.json.cmd.offset );
1457 SET("cmd.offset");
1458 tmp = cson_value_new_bool( g.isHTTP );
1459 SET("isCGI");
1460 }
1461 }
1462
1463 if(fossil_timer_is_active(g.json.timerId)){
1464 /* This is, philosophically speaking, not quite the right place
@@ -1454,11 +1473,10 @@
1473 cson_object_set(o,"procTimeUs", cson_value_new_integer((cson_int_t)span));
1474 span /= 1000/*for milliseconds */;
1475 cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)span));
1476 assert(!fossil_timer_is_active(g.json.timerId));
1477 g.json.timerId = -1;
 
1478 }
1479 if(g.json.warnings){
1480 tmp = cson_array_value(g.json.warnings);
1481 SET("warnings");
1482 }
@@ -1472,12 +1490,13 @@
1490 tmp = payload;
1491 SET(FossilJsonKeys.payload);
1492 }
1493 }
1494
1495 if((g.perm.Admin||g.perm.Setup)
1496 && json_find_option_bool("debugFossilG","json-debug-g",NULL,0)
1497 ){
1498 tmp = json_g_to_json();
1499 SET("g");
1500 }
1501
1502 #undef SET
@@ -1514,10 +1533,11 @@
1533 void json_err( int code, char const * msg, int alsoOutput ){
1534 int rc = code ? code : (g.json.resultCode
1535 ? g.json.resultCode
1536 : FSL_JSON_E_UNKNOWN);
1537 cson_value * resp = NULL;
1538 if(g.json.isJsonMode==0) return;
1539 rc = json_dumbdown_rc(rc);
1540 if( rc && !msg ){
1541 msg = g.zErrMsg;
1542 if(!msg){
1543 msg = json_err_cstr(rc);
@@ -1524,11 +1544,12 @@
1544 }
1545 }
1546 resp = json_create_response(rc, msg, NULL);
1547 if(!resp){
1548 /* about the only error case here is out-of-memory. DO NOT
1549 call fossil_panic() or fossil_fatal() here because those
1550 allocate.
1551 */
1552 fprintf(stderr, "%s: Fatal error: could not allocate "
1553 "response object.\n", g.argv[0]);
1554 fossil_exit(1);
1555 }
1556
--- src/json_branch.c
+++ src/json_branch.c
@@ -79,11 +79,11 @@
7979
payV = cson_value_new_object();
8080
pay = cson_value_get_object(payV);
8181
listV = cson_value_new_array();
8282
list = cson_value_get_array(listV);
8383
if(fossil_has_json()){
84
- range = json_getenv_cstr("range");
84
+ range = json_getenv_cstr("range");
8585
}
8686
8787
range = json_find_option_cstr("range",NULL,"r");
8888
if((!range||!*range) && !g.isHTTP){
8989
range = find_option("all","a",0);
9090
--- src/json_branch.c
+++ src/json_branch.c
@@ -79,11 +79,11 @@
79 payV = cson_value_new_object();
80 pay = cson_value_get_object(payV);
81 listV = cson_value_new_array();
82 list = cson_value_get_array(listV);
83 if(fossil_has_json()){
84 range = json_getenv_cstr("range");
85 }
86
87 range = json_find_option_cstr("range",NULL,"r");
88 if((!range||!*range) && !g.isHTTP){
89 range = find_option("all","a",0);
90
--- src/json_branch.c
+++ src/json_branch.c
@@ -79,11 +79,11 @@
79 payV = cson_value_new_object();
80 pay = cson_value_get_object(payV);
81 listV = cson_value_new_array();
82 list = cson_value_get_array(listV);
83 if(fossil_has_json()){
84 range = json_getenv_cstr("range");
85 }
86
87 range = json_find_option_cstr("range",NULL,"r");
88 if((!range||!*range) && !g.isHTTP){
89 range = find_option("all","a",0);
90
+26 -15
--- src/main.c
+++ src/main.c
@@ -267,12 +267,13 @@
267267
struct FossilJsonBits {
268268
int isJsonMode; /* True if running in JSON mode, else
269269
false. This changes how errors are
270270
reported. In JSON mode we try to
271271
always output JSON-form error
272
- responses and always exit() with
273
- code 0 to avoid an HTTP 500 error.
272
+ responses and always (in CGI mode)
273
+ exit() with code 0 to avoid an HTTP
274
+ 500 error.
274275
*/
275276
int resultCode; /* used for passing back specific codes
276277
** from /json callbacks. */
277278
int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
278279
cson_output_opt outOpt; /* formatting options for JSON mode. */
@@ -843,10 +844,17 @@
843844
"%s: could be any of:%s\n"
844845
"%s: use \"help\" for more information\n",
845846
g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
846847
fossil_exit(1);
847848
}
849
+#ifdef FOSSIL_ENABLE_JSON
850
+ else if( rc==0 && strcmp("json",pCmd->zName)==0 ){
851
+ g.json.isJsonMode = 1;
852
+ }else{
853
+ assert(!g.json.isJsonMode && "JSON-mode misconfiguration.");
854
+ }
855
+#endif
848856
atexit( fossil_atexit );
849857
#ifdef FOSSIL_ENABLE_TH1_HOOKS
850858
/*
851859
** The TH1 return codes from the hook will be handled as follows:
852860
**
@@ -1510,11 +1518,25 @@
15101518
if( PB("utc") ){
15111519
g.fTimeFormat = 1;
15121520
}else if( PB("localtime") ){
15131521
g.fTimeFormat = 2;
15141522
}
1515
-
1523
+#ifdef FOSSIL_ENABLE_JSON
1524
+ /*
1525
+ ** Ensure that JSON mode is set up if we're visiting /json, to allow
1526
+ ** us to customize some following behaviour (error handling and only
1527
+ ** process JSON-mode POST data if we're actually in a /json
1528
+ ** page). This is normally set up before this routine is called, but
1529
+ ** it looks like the ssh_request_loop() approach to dispatching
1530
+ ** might bypass that.
1531
+ */
1532
+ if( g.json.isJsonMode==0 && zPathInfo!=0
1533
+ && 0==strncmp("/json",zPathInfo,5)
1534
+ && (zPathInfo[5]==0 || zPathInfo[5]=='/')){
1535
+ g.json.isJsonMode = 1;
1536
+ }
1537
+#endif
15161538
/* If the repository has not been opened already, then find the
15171539
** repository based on the first element of PATH_INFO and open it.
15181540
*/
15191541
if( !g.repositoryOpen ){
15201542
char *zRepo; /* Candidate repository name */
@@ -1641,11 +1663,11 @@
16411663
zRepo[j] = '.';
16421664
}
16431665
16441666
/* If we reach this point, it means that the search of the PATH_INFO
16451667
** string is finished. Either zRepo contains the name of the
1646
- ** repository to be used, or else no repository could be found an
1668
+ ** repository to be used, or else no repository could be found and
16471669
** some kind of error response is required.
16481670
*/
16491671
if( szFile<1024 ){
16501672
set_base_url(0);
16511673
if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0)
@@ -1772,21 +1794,10 @@
17721794
}else{
17731795
g.zExtra = 0;
17741796
}
17751797
break;
17761798
}
1777
-#ifdef FOSSIL_ENABLE_JSON
1778
- /*
1779
- ** Workaround to allow us to customize some following behaviour for
1780
- ** JSON mode. The problem is, we don't always know if we're in JSON
1781
- ** mode at this point (namely, for GET mode we don't know but POST
1782
- ** we do), so we snoop g.zPath and cheat a bit.
1783
- */
1784
- if( !g.json.isJsonMode && g.zPath && (0==strncmp("json",g.zPath,4)) ){
1785
- g.json.isJsonMode = 1;
1786
- }
1787
-#endif
17881799
if( g.zExtra ){
17891800
/* CGI parameters get this treatment elsewhere, but places like getfile
17901801
** will use g.zExtra directly.
17911802
** Reminder: the login mechanism uses 'name' differently, and may
17921803
** eventually have a problem/collision with this.
17931804
--- src/main.c
+++ src/main.c
@@ -267,12 +267,13 @@
267 struct FossilJsonBits {
268 int isJsonMode; /* True if running in JSON mode, else
269 false. This changes how errors are
270 reported. In JSON mode we try to
271 always output JSON-form error
272 responses and always exit() with
273 code 0 to avoid an HTTP 500 error.
 
274 */
275 int resultCode; /* used for passing back specific codes
276 ** from /json callbacks. */
277 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
278 cson_output_opt outOpt; /* formatting options for JSON mode. */
@@ -843,10 +844,17 @@
843 "%s: could be any of:%s\n"
844 "%s: use \"help\" for more information\n",
845 g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
846 fossil_exit(1);
847 }
 
 
 
 
 
 
 
848 atexit( fossil_atexit );
849 #ifdef FOSSIL_ENABLE_TH1_HOOKS
850 /*
851 ** The TH1 return codes from the hook will be handled as follows:
852 **
@@ -1510,11 +1518,25 @@
1510 if( PB("utc") ){
1511 g.fTimeFormat = 1;
1512 }else if( PB("localtime") ){
1513 g.fTimeFormat = 2;
1514 }
1515
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1516 /* If the repository has not been opened already, then find the
1517 ** repository based on the first element of PATH_INFO and open it.
1518 */
1519 if( !g.repositoryOpen ){
1520 char *zRepo; /* Candidate repository name */
@@ -1641,11 +1663,11 @@
1641 zRepo[j] = '.';
1642 }
1643
1644 /* If we reach this point, it means that the search of the PATH_INFO
1645 ** string is finished. Either zRepo contains the name of the
1646 ** repository to be used, or else no repository could be found an
1647 ** some kind of error response is required.
1648 */
1649 if( szFile<1024 ){
1650 set_base_url(0);
1651 if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0)
@@ -1772,21 +1794,10 @@
1772 }else{
1773 g.zExtra = 0;
1774 }
1775 break;
1776 }
1777 #ifdef FOSSIL_ENABLE_JSON
1778 /*
1779 ** Workaround to allow us to customize some following behaviour for
1780 ** JSON mode. The problem is, we don't always know if we're in JSON
1781 ** mode at this point (namely, for GET mode we don't know but POST
1782 ** we do), so we snoop g.zPath and cheat a bit.
1783 */
1784 if( !g.json.isJsonMode && g.zPath && (0==strncmp("json",g.zPath,4)) ){
1785 g.json.isJsonMode = 1;
1786 }
1787 #endif
1788 if( g.zExtra ){
1789 /* CGI parameters get this treatment elsewhere, but places like getfile
1790 ** will use g.zExtra directly.
1791 ** Reminder: the login mechanism uses 'name' differently, and may
1792 ** eventually have a problem/collision with this.
1793
--- src/main.c
+++ src/main.c
@@ -267,12 +267,13 @@
267 struct FossilJsonBits {
268 int isJsonMode; /* True if running in JSON mode, else
269 false. This changes how errors are
270 reported. In JSON mode we try to
271 always output JSON-form error
272 responses and always (in CGI mode)
273 exit() with code 0 to avoid an HTTP
274 500 error.
275 */
276 int resultCode; /* used for passing back specific codes
277 ** from /json callbacks. */
278 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
279 cson_output_opt outOpt; /* formatting options for JSON mode. */
@@ -843,10 +844,17 @@
844 "%s: could be any of:%s\n"
845 "%s: use \"help\" for more information\n",
846 g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]);
847 fossil_exit(1);
848 }
849 #ifdef FOSSIL_ENABLE_JSON
850 else if( rc==0 && strcmp("json",pCmd->zName)==0 ){
851 g.json.isJsonMode = 1;
852 }else{
853 assert(!g.json.isJsonMode && "JSON-mode misconfiguration.");
854 }
855 #endif
856 atexit( fossil_atexit );
857 #ifdef FOSSIL_ENABLE_TH1_HOOKS
858 /*
859 ** The TH1 return codes from the hook will be handled as follows:
860 **
@@ -1510,11 +1518,25 @@
1518 if( PB("utc") ){
1519 g.fTimeFormat = 1;
1520 }else if( PB("localtime") ){
1521 g.fTimeFormat = 2;
1522 }
1523 #ifdef FOSSIL_ENABLE_JSON
1524 /*
1525 ** Ensure that JSON mode is set up if we're visiting /json, to allow
1526 ** us to customize some following behaviour (error handling and only
1527 ** process JSON-mode POST data if we're actually in a /json
1528 ** page). This is normally set up before this routine is called, but
1529 ** it looks like the ssh_request_loop() approach to dispatching
1530 ** might bypass that.
1531 */
1532 if( g.json.isJsonMode==0 && zPathInfo!=0
1533 && 0==strncmp("/json",zPathInfo,5)
1534 && (zPathInfo[5]==0 || zPathInfo[5]=='/')){
1535 g.json.isJsonMode = 1;
1536 }
1537 #endif
1538 /* If the repository has not been opened already, then find the
1539 ** repository based on the first element of PATH_INFO and open it.
1540 */
1541 if( !g.repositoryOpen ){
1542 char *zRepo; /* Candidate repository name */
@@ -1641,11 +1663,11 @@
1663 zRepo[j] = '.';
1664 }
1665
1666 /* If we reach this point, it means that the search of the PATH_INFO
1667 ** string is finished. Either zRepo contains the name of the
1668 ** repository to be used, or else no repository could be found and
1669 ** some kind of error response is required.
1670 */
1671 if( szFile<1024 ){
1672 set_base_url(0);
1673 if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0)
@@ -1772,21 +1794,10 @@
1794 }else{
1795 g.zExtra = 0;
1796 }
1797 break;
1798 }
 
 
 
 
 
 
 
 
 
 
 
1799 if( g.zExtra ){
1800 /* CGI parameters get this treatment elsewhere, but places like getfile
1801 ** will use g.zExtra directly.
1802 ** Reminder: the login mechanism uses 'name' differently, and may
1803 ** eventually have a problem/collision with this.
1804
--- www/json-api/conventions.md
+++ www/json-api/conventions.md
@@ -257,12 +257,12 @@
257257
non-fatal warnings in responses. Will be an array but the warning
258258
structure/type is not yet specified. Intended primarily as a
259259
debugging tool, and will "probably not" become part of the public
260260
client interface.
261261
- `g`: Fossil administrators (those with the "a" or "s" permissions)
262
- may use the debugFossilG boolean request parameter (CLI:
263
- --json-debug-g) to enable this property for any given response. It
262
+ may set the `debugFossilG` boolean request parameter (CLI:
263
+ `--json-debug-g`) to enable this property for any given response. It
264264
contains a good deal of the server-side internal state at the time
265265
the response was generated, which is often useful in debuggering
266266
problems. Trivia: it is called "g" because that's the name of
267267
fossil's internal global state object.
268268
- `procTimeMs`: For debugging only - generic clients must not rely on
269269
--- www/json-api/conventions.md
+++ www/json-api/conventions.md
@@ -257,12 +257,12 @@
257 non-fatal warnings in responses. Will be an array but the warning
258 structure/type is not yet specified. Intended primarily as a
259 debugging tool, and will "probably not" become part of the public
260 client interface.
261 - `g`: Fossil administrators (those with the "a" or "s" permissions)
262 may use the debugFossilG boolean request parameter (CLI:
263 --json-debug-g) to enable this property for any given response. It
264 contains a good deal of the server-side internal state at the time
265 the response was generated, which is often useful in debuggering
266 problems. Trivia: it is called "g" because that's the name of
267 fossil's internal global state object.
268 - `procTimeMs`: For debugging only - generic clients must not rely on
269
--- www/json-api/conventions.md
+++ www/json-api/conventions.md
@@ -257,12 +257,12 @@
257 non-fatal warnings in responses. Will be an array but the warning
258 structure/type is not yet specified. Intended primarily as a
259 debugging tool, and will "probably not" become part of the public
260 client interface.
261 - `g`: Fossil administrators (those with the "a" or "s" permissions)
262 may set the `debugFossilG` boolean request parameter (CLI:
263 `--json-debug-g`) to enable this property for any given response. It
264 contains a good deal of the server-side internal state at the time
265 the response was generated, which is often useful in debuggering
266 problems. Trivia: it is called "g" because that's the name of
267 fossil's internal global state object.
268 - `procTimeMs`: For debugging only - generic clients must not rely on
269

Keyboard Shortcuts

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