Fossil SCM

Corrected parsing of /json-mode POST data in TLS mode. Extended /json/wiki/preview to support a mimetype option.

stephan 2022-01-25 19:36 trunk
Commit 7f5877e8439531f8c0f758db922bf8a0e2fa58d1d3051386812edb4c91292fa7
+19 -72
--- src/cgi.c
+++ src/cgi.c
@@ -1061,71 +1061,25 @@
10611061
}
10621062
10631063
10641064
#ifdef FOSSIL_ENABLE_JSON
10651065
/*
1066
-** Internal helper for cson_data_source_FILE_n().
1067
-*/
1068
-typedef struct CgiPostReadState_ {
1069
- FILE * fh;
1070
- unsigned int len;
1071
- unsigned int pos;
1072
-} CgiPostReadState;
1073
-
1074
-/*
1075
-** cson_data_source_f() impl which reads only up to
1076
-** a specified amount of data from its input FILE.
1077
-** state MUST be a full populated (CgiPostReadState*).
1078
-*/
1079
-static int cson_data_source_FILE_n( void * state,
1080
- void * dest,
1081
- unsigned int * n ){
1082
- if( ! state || !dest || !n ) return cson_rc.ArgError;
1083
- else {
1084
- CgiPostReadState * st = (CgiPostReadState *)state;
1085
- if( st->pos >= st->len ){
1086
- *n = 0;
1087
- return 0;
1088
- }else if( !*n || ((st->pos + *n) > st->len) ){
1089
- return cson_rc.RangeError;
1090
- }else{
1091
- unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
1092
- if( ! rsz ){
1093
- *n = rsz;
1094
- return feof(st->fh) ? 0 : cson_rc.IOError;
1095
- }else{
1096
- *n = rsz;
1097
- st->pos += *n;
1098
- return 0;
1099
- }
1100
- }
1101
- }
1102
-}
1103
-
1104
-/*
1105
-** Reads a JSON object from the first contentLen bytes of zIn. On
1106
-** g.json.post is updated to hold the content. On error a
1107
-** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit() is
1108
-** called (in HTTP mode exit code 0 is used).
1109
-**
1110
-** If contentLen is 0 then the whole file is read.
1111
-*/
1112
-void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
1113
- cson_value * jv = NULL;
1114
- int rc;
1115
- CgiPostReadState state;
1066
+** Reads a JSON object from the given blob, which is assumed to have
1067
+** been populated by the caller from stdin, the SSL API, or a file, as
1068
+** appropriate for the particular use case. On success g.json.post is
1069
+** updated to hold the content. On error a FSL_JSON_E_INVALID_REQUEST
1070
+** response is output and fossil_exit() is called (in HTTP mode exit
1071
+** code 0 is used).
1072
+*/
1073
+void cgi_parse_POST_JSON( Blob * pIn ){
1074
+ cson_value * jv = NULL;
11161075
cson_parse_opt popt = cson_parse_opt_empty;
11171076
cson_parse_info pinfo = cson_parse_info_empty;
11181077
assert(g.json.gc.a && "json_bootstrap_early() was not called!");
11191078
popt.maxDepth = 15;
1120
- state.fh = zIn;
1121
- state.len = contentLen;
1122
- state.pos = 0;
1123
- rc = cson_parse( &jv,
1124
- contentLen ? cson_data_source_FILE_n : cson_data_source_FILE,
1125
- contentLen ? (void *)&state : (void *)zIn, &popt, &pinfo );
1126
- if(rc){
1079
+ jv = cson_parse_Blob(pIn, &pinfo);
1080
+ if( jv==NULL ){
11271081
goto invalidRequest;
11281082
}else{
11291083
json_gc_add( "POST.JSON", jv );
11301084
g.json.post.v = jv;
11311085
g.json.post.o = cson_value_get_object( jv );
@@ -1140,11 +1094,11 @@
11401094
char * msg = mprintf("JSON parse error at line %u, column %u, "
11411095
"byte offset %u: %s",
11421096
pinfo.line, pinfo.col, pinfo.length,
11431097
cson_rc_string(pinfo.errorCode));
11441098
json_err( FSL_JSON_E_INVALID_REQUEST, msg, 1 );
1145
- free(msg);
1099
+ fossil_free(msg);
11461100
}else if(jv && !g.json.post.o){
11471101
json_err( FSL_JSON_E_INVALID_REQUEST,
11481102
"Request envelope must be a JSON Object (not array).", 1 );
11491103
}else{ /* generic error message */
11501104
json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
@@ -1368,28 +1322,21 @@
13681322
if( blob_read_from_cgi(&g.cgiIn, len)!=len ){
13691323
malformed_request("CGI content-length mismatch");
13701324
}
13711325
blob_uncompress(&g.cgiIn, &g.cgiIn);
13721326
}
1373
-#ifdef FOSSIL_ENABLE_JSON
1374
- else if( noJson==0 && g.json.isJsonMode!=0
1375
- && json_can_consume_content_type(zType)!=0 ){
1376
- assert( !g.httpUseSSL );
1377
- cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
1378
- /*
1379
- Potential TODOs:
1380
-
1381
- 1) If parsing fails, immediately return an error response
1382
- without dispatching the ostensibly-upcoming JSON API.
1383
- */
1384
- cgi_set_content_type(json_guess_content_type());
1385
- }
1386
-#endif /* FOSSIL_ENABLE_JSON */
13871327
else{
13881328
if( blob_read_from_cgi(&g.cgiIn, len)!=len ){
13891329
malformed_request("CGI content-length mismatch");
13901330
}
1331
+#ifdef FOSSIL_ENABLE_JSON
1332
+ if( noJson==0 && g.json.isJsonMode!=0
1333
+ && json_can_consume_content_type(zType)!=0 ){
1334
+ cgi_parse_POST_JSON(&g.cgiIn);
1335
+ cgi_set_content_type(json_guess_content_type());
1336
+ }
1337
+#endif /* FOSSIL_ENABLE_JSON */
13911338
}
13921339
}
13931340
}
13941341
13951342
/*
13961343
--- src/cgi.c
+++ src/cgi.c
@@ -1061,71 +1061,25 @@
1061 }
1062
1063
1064 #ifdef FOSSIL_ENABLE_JSON
1065 /*
1066 ** Internal helper for cson_data_source_FILE_n().
1067 */
1068 typedef struct CgiPostReadState_ {
1069 FILE * fh;
1070 unsigned int len;
1071 unsigned int pos;
1072 } CgiPostReadState;
1073
1074 /*
1075 ** cson_data_source_f() impl which reads only up to
1076 ** a specified amount of data from its input FILE.
1077 ** state MUST be a full populated (CgiPostReadState*).
1078 */
1079 static int cson_data_source_FILE_n( void * state,
1080 void * dest,
1081 unsigned int * n ){
1082 if( ! state || !dest || !n ) return cson_rc.ArgError;
1083 else {
1084 CgiPostReadState * st = (CgiPostReadState *)state;
1085 if( st->pos >= st->len ){
1086 *n = 0;
1087 return 0;
1088 }else if( !*n || ((st->pos + *n) > st->len) ){
1089 return cson_rc.RangeError;
1090 }else{
1091 unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
1092 if( ! rsz ){
1093 *n = rsz;
1094 return feof(st->fh) ? 0 : cson_rc.IOError;
1095 }else{
1096 *n = rsz;
1097 st->pos += *n;
1098 return 0;
1099 }
1100 }
1101 }
1102 }
1103
1104 /*
1105 ** Reads a JSON object from the first contentLen bytes of zIn. On
1106 ** g.json.post is updated to hold the content. On error a
1107 ** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit() is
1108 ** called (in HTTP mode exit code 0 is used).
1109 **
1110 ** If contentLen is 0 then the whole file is read.
1111 */
1112 void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
1113 cson_value * jv = NULL;
1114 int rc;
1115 CgiPostReadState state;
1116 cson_parse_opt popt = cson_parse_opt_empty;
1117 cson_parse_info pinfo = cson_parse_info_empty;
1118 assert(g.json.gc.a && "json_bootstrap_early() was not called!");
1119 popt.maxDepth = 15;
1120 state.fh = zIn;
1121 state.len = contentLen;
1122 state.pos = 0;
1123 rc = cson_parse( &jv,
1124 contentLen ? cson_data_source_FILE_n : cson_data_source_FILE,
1125 contentLen ? (void *)&state : (void *)zIn, &popt, &pinfo );
1126 if(rc){
1127 goto invalidRequest;
1128 }else{
1129 json_gc_add( "POST.JSON", jv );
1130 g.json.post.v = jv;
1131 g.json.post.o = cson_value_get_object( jv );
@@ -1140,11 +1094,11 @@
1140 char * msg = mprintf("JSON parse error at line %u, column %u, "
1141 "byte offset %u: %s",
1142 pinfo.line, pinfo.col, pinfo.length,
1143 cson_rc_string(pinfo.errorCode));
1144 json_err( FSL_JSON_E_INVALID_REQUEST, msg, 1 );
1145 free(msg);
1146 }else if(jv && !g.json.post.o){
1147 json_err( FSL_JSON_E_INVALID_REQUEST,
1148 "Request envelope must be a JSON Object (not array).", 1 );
1149 }else{ /* generic error message */
1150 json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
@@ -1368,28 +1322,21 @@
1368 if( blob_read_from_cgi(&g.cgiIn, len)!=len ){
1369 malformed_request("CGI content-length mismatch");
1370 }
1371 blob_uncompress(&g.cgiIn, &g.cgiIn);
1372 }
1373 #ifdef FOSSIL_ENABLE_JSON
1374 else if( noJson==0 && g.json.isJsonMode!=0
1375 && json_can_consume_content_type(zType)!=0 ){
1376 assert( !g.httpUseSSL );
1377 cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
1378 /*
1379 Potential TODOs:
1380
1381 1) If parsing fails, immediately return an error response
1382 without dispatching the ostensibly-upcoming JSON API.
1383 */
1384 cgi_set_content_type(json_guess_content_type());
1385 }
1386 #endif /* FOSSIL_ENABLE_JSON */
1387 else{
1388 if( blob_read_from_cgi(&g.cgiIn, len)!=len ){
1389 malformed_request("CGI content-length mismatch");
1390 }
 
 
 
 
 
 
 
1391 }
1392 }
1393 }
1394
1395 /*
1396
--- src/cgi.c
+++ src/cgi.c
@@ -1061,71 +1061,25 @@
1061 }
1062
1063
1064 #ifdef FOSSIL_ENABLE_JSON
1065 /*
1066 ** Reads a JSON object from the given blob, which is assumed to have
1067 ** been populated by the caller from stdin, the SSL API, or a file, as
1068 ** appropriate for the particular use case. On success g.json.post is
1069 ** updated to hold the content. On error a FSL_JSON_E_INVALID_REQUEST
1070 ** response is output and fossil_exit() is called (in HTTP mode exit
1071 ** code 0 is used).
1072 */
1073 void cgi_parse_POST_JSON( Blob * pIn ){
1074 cson_value * jv = NULL;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1075 cson_parse_opt popt = cson_parse_opt_empty;
1076 cson_parse_info pinfo = cson_parse_info_empty;
1077 assert(g.json.gc.a && "json_bootstrap_early() was not called!");
1078 popt.maxDepth = 15;
1079 jv = cson_parse_Blob(pIn, &pinfo);
1080 if( jv==NULL ){
 
 
 
 
 
1081 goto invalidRequest;
1082 }else{
1083 json_gc_add( "POST.JSON", jv );
1084 g.json.post.v = jv;
1085 g.json.post.o = cson_value_get_object( jv );
@@ -1140,11 +1094,11 @@
1094 char * msg = mprintf("JSON parse error at line %u, column %u, "
1095 "byte offset %u: %s",
1096 pinfo.line, pinfo.col, pinfo.length,
1097 cson_rc_string(pinfo.errorCode));
1098 json_err( FSL_JSON_E_INVALID_REQUEST, msg, 1 );
1099 fossil_free(msg);
1100 }else if(jv && !g.json.post.o){
1101 json_err( FSL_JSON_E_INVALID_REQUEST,
1102 "Request envelope must be a JSON Object (not array).", 1 );
1103 }else{ /* generic error message */
1104 json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 );
@@ -1368,28 +1322,21 @@
1322 if( blob_read_from_cgi(&g.cgiIn, len)!=len ){
1323 malformed_request("CGI content-length mismatch");
1324 }
1325 blob_uncompress(&g.cgiIn, &g.cgiIn);
1326 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1327 else{
1328 if( blob_read_from_cgi(&g.cgiIn, len)!=len ){
1329 malformed_request("CGI content-length mismatch");
1330 }
1331 #ifdef FOSSIL_ENABLE_JSON
1332 if( noJson==0 && g.json.isJsonMode!=0
1333 && json_can_consume_content_type(zType)!=0 ){
1334 cgi_parse_POST_JSON(&g.cgiIn);
1335 cgi_set_content_type(json_guess_content_type());
1336 }
1337 #endif /* FOSSIL_ENABLE_JSON */
1338 }
1339 }
1340 }
1341
1342 /*
1343
+6 -15
--- src/json.c
+++ src/json.c
@@ -1,8 +1,8 @@
11
#ifdef FOSSIL_ENABLE_JSON
22
/*
3
-** Copyright (c) 2011 D. Richard Hipp
3
+** Copyright (c) 2011-2022 D. Richard Hipp
44
**
55
** This program is free software; you can redistribute it and/or
66
** modify it under the terms of the Simplified BSD License (also
77
** known as the "2-Clause License" or "FreeBSD License".)
88
@@ -172,21 +172,10 @@
172172
Blob * b = (Blob*)pState;
173173
blob_append( b, (char const *)src, (int)n ) /* will die on OOM */;
174174
return 0;
175175
}
176176
177
-/*
178
-** Implements the cson_data_source_f() interface and reads input from
179
-** a fossil Blob object. pState must be-a (Blob*) populated with JSON
180
-** data.
181
-*/
182
-int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){
183
- Blob * b = (Blob*)pState;
184
- *n = blob_read( b, dest, *n );
185
- return 0;
186
-}
187
-
188177
/*
189178
** Convenience wrapper around cson_output() which appends the output
190179
** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used.
191180
*/
192181
int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){
@@ -204,12 +193,11 @@
204193
** On success a new JSON Object or Array is returned (owned by the
205194
** caller). On error NULL is returned.
206195
*/
207196
cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){
208197
cson_value * root = NULL;
209
- blob_rewind( pSrc );
210
- cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo );
198
+ cson_parse_string( &root, blob_str(pSrc), blob_size(pSrc), NULL, pInfo );
211199
return root;
212200
}
213201
214202
/*
215203
** Implements the cson_data_dest_f() interface and outputs the data to
@@ -1065,10 +1053,11 @@
10651053
because outOpt cannot (generically) be configured until after
10661054
POST-reading is finished.
10671055
*/
10681056
FILE * inFile = NULL;
10691057
char const * jfile = find_option("json-input",NULL,1);
1058
+ Blob json = BLOB_INITIALIZER;
10701059
if(!jfile || !*jfile){
10711060
break;
10721061
}
10731062
inFile = (0==strcmp("-",jfile))
10741063
? stdin
@@ -1077,12 +1066,14 @@
10771066
g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
10781067
fossil_fatal("Could not open JSON file [%s].",jfile)
10791068
/* Does not return. */
10801069
;
10811070
}
1082
- cgi_parse_POST_JSON(inFile, 0);
1071
+ blob_read_from_channel(&json, inFile, -1);
10831072
fossil_fclose(inFile);
1073
+ cgi_parse_POST_JSON(&json);
1074
+ blob_reset(&json);
10841075
break;
10851076
}
10861077
10871078
/* g.json.reqPayload exists only to simplify some of our access to
10881079
the request payload. We currently only use this in the context of
10891080
--- src/json.c
+++ src/json.c
@@ -1,8 +1,8 @@
1 #ifdef FOSSIL_ENABLE_JSON
2 /*
3 ** Copyright (c) 2011 D. Richard Hipp
4 **
5 ** This program is free software; you can redistribute it and/or
6 ** modify it under the terms of the Simplified BSD License (also
7 ** known as the "2-Clause License" or "FreeBSD License".)
8
@@ -172,21 +172,10 @@
172 Blob * b = (Blob*)pState;
173 blob_append( b, (char const *)src, (int)n ) /* will die on OOM */;
174 return 0;
175 }
176
177 /*
178 ** Implements the cson_data_source_f() interface and reads input from
179 ** a fossil Blob object. pState must be-a (Blob*) populated with JSON
180 ** data.
181 */
182 int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){
183 Blob * b = (Blob*)pState;
184 *n = blob_read( b, dest, *n );
185 return 0;
186 }
187
188 /*
189 ** Convenience wrapper around cson_output() which appends the output
190 ** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used.
191 */
192 int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){
@@ -204,12 +193,11 @@
204 ** On success a new JSON Object or Array is returned (owned by the
205 ** caller). On error NULL is returned.
206 */
207 cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){
208 cson_value * root = NULL;
209 blob_rewind( pSrc );
210 cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo );
211 return root;
212 }
213
214 /*
215 ** Implements the cson_data_dest_f() interface and outputs the data to
@@ -1065,10 +1053,11 @@
1065 because outOpt cannot (generically) be configured until after
1066 POST-reading is finished.
1067 */
1068 FILE * inFile = NULL;
1069 char const * jfile = find_option("json-input",NULL,1);
 
1070 if(!jfile || !*jfile){
1071 break;
1072 }
1073 inFile = (0==strcmp("-",jfile))
1074 ? stdin
@@ -1077,12 +1066,14 @@
1077 g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
1078 fossil_fatal("Could not open JSON file [%s].",jfile)
1079 /* Does not return. */
1080 ;
1081 }
1082 cgi_parse_POST_JSON(inFile, 0);
1083 fossil_fclose(inFile);
 
 
1084 break;
1085 }
1086
1087 /* g.json.reqPayload exists only to simplify some of our access to
1088 the request payload. We currently only use this in the context of
1089
--- src/json.c
+++ src/json.c
@@ -1,8 +1,8 @@
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
@@ -172,21 +172,10 @@
172 Blob * b = (Blob*)pState;
173 blob_append( b, (char const *)src, (int)n ) /* will die on OOM */;
174 return 0;
175 }
176
 
 
 
 
 
 
 
 
 
 
 
177 /*
178 ** Convenience wrapper around cson_output() which appends the output
179 ** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used.
180 */
181 int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){
@@ -204,12 +193,11 @@
193 ** On success a new JSON Object or Array is returned (owned by the
194 ** caller). On error NULL is returned.
195 */
196 cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){
197 cson_value * root = NULL;
198 cson_parse_string( &root, blob_str(pSrc), blob_size(pSrc), NULL, pInfo );
 
199 return root;
200 }
201
202 /*
203 ** Implements the cson_data_dest_f() interface and outputs the data to
@@ -1065,10 +1053,11 @@
1053 because outOpt cannot (generically) be configured until after
1054 POST-reading is finished.
1055 */
1056 FILE * inFile = NULL;
1057 char const * jfile = find_option("json-input",NULL,1);
1058 Blob json = BLOB_INITIALIZER;
1059 if(!jfile || !*jfile){
1060 break;
1061 }
1062 inFile = (0==strcmp("-",jfile))
1063 ? stdin
@@ -1077,12 +1066,14 @@
1066 g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED;
1067 fossil_fatal("Could not open JSON file [%s].",jfile)
1068 /* Does not return. */
1069 ;
1070 }
1071 blob_read_from_channel(&json, inFile, -1);
1072 fossil_fclose(inFile);
1073 cgi_parse_POST_JSON(&json);
1074 blob_reset(&json);
1075 break;
1076 }
1077
1078 /* g.json.reqPayload exists only to simplify some of our access to
1079 the request payload. We currently only use this in the context of
1080
+27 -6
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -273,30 +273,51 @@
273273
return json_wiki_get_by_name_or_symname( zPageName, zSymName, contentFormat );
274274
}
275275
276276
/*
277277
** Implementation of /json/wiki/preview.
278
-**
279278
*/
280279
static cson_value * json_wiki_preview(){
281280
char const * zContent = NULL;
281
+ char const * zMime = NULL;
282
+ cson_string * sContent = NULL;
282283
cson_value * pay = NULL;
283284
Blob contentOrig = empty_blob;
284285
Blob contentHtml = empty_blob;
285286
if( !g.perm.WrWiki ){
286287
json_set_err(FSL_JSON_E_DENIED,
287288
"Requires 'k' access.");
288289
return NULL;
289290
}
290
- zContent = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
291
- if(!zContent) {
291
+
292
+ if(g.json.reqPayload.o){
293
+ sContent = cson_value_get_string(
294
+ cson_object_get(g.json.reqPayload.o, "body"));
295
+ zMime = cson_value_get_cstr(cson_object_get(g.json.reqPayload.o,
296
+ "mimetype"));
297
+ }else{
298
+ sContent = cson_value_get_string(g.json.reqPayload.v);
299
+ }
300
+ if(!sContent) {
292301
json_set_err(FSL_JSON_E_MISSING_ARGS,
293
- "The 'payload' property must be a string containing the wiki code to preview.");
302
+ "The 'payload' property must be either a string containing the "
303
+ "Fossil wiki code to preview or an object with body + mimetype "
304
+ "properties.");
294305
return NULL;
295306
}
296
- blob_append( &contentOrig, zContent, (int)cson_string_length_bytes(cson_value_get_string(g.json.reqPayload.v)) );
297
- wiki_convert( &contentOrig, &contentHtml, 0 );
307
+ zContent = cson_string_cstr(sContent);
308
+ blob_append( &contentOrig, zContent, (int)cson_string_length_bytes(sContent) );
309
+ zMime = wiki_filter_mimetypes(zMime);
310
+ if( 0==fossil_strcmp(zMime, "text/x-markdown") ){
311
+ markdown_to_html(&contentOrig, 0, &contentHtml);
312
+ }else if( 0==fossil_strcmp(zMime, "text/plain") ){
313
+ blob_append(&contentHtml, "<pre class='textPlain'>", -1);
314
+ blob_append(&contentHtml, blob_str(&contentOrig), blob_size(&contentOrig));
315
+ blob_append(&contentHtml, "</pre>", -1);
316
+ }else{
317
+ wiki_convert( &contentOrig, &contentHtml, 0 );
318
+ }
298319
blob_reset( &contentOrig );
299320
pay = cson_value_new_string( blob_str(&contentHtml), (unsigned int)blob_size(&contentHtml));
300321
blob_reset( &contentHtml );
301322
return pay;
302323
}
303324
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -273,30 +273,51 @@
273 return json_wiki_get_by_name_or_symname( zPageName, zSymName, contentFormat );
274 }
275
276 /*
277 ** Implementation of /json/wiki/preview.
278 **
279 */
280 static cson_value * json_wiki_preview(){
281 char const * zContent = NULL;
 
 
282 cson_value * pay = NULL;
283 Blob contentOrig = empty_blob;
284 Blob contentHtml = empty_blob;
285 if( !g.perm.WrWiki ){
286 json_set_err(FSL_JSON_E_DENIED,
287 "Requires 'k' access.");
288 return NULL;
289 }
290 zContent = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v));
291 if(!zContent) {
 
 
 
 
 
 
 
 
292 json_set_err(FSL_JSON_E_MISSING_ARGS,
293 "The 'payload' property must be a string containing the wiki code to preview.");
 
 
294 return NULL;
295 }
296 blob_append( &contentOrig, zContent, (int)cson_string_length_bytes(cson_value_get_string(g.json.reqPayload.v)) );
297 wiki_convert( &contentOrig, &contentHtml, 0 );
 
 
 
 
 
 
 
 
 
 
298 blob_reset( &contentOrig );
299 pay = cson_value_new_string( blob_str(&contentHtml), (unsigned int)blob_size(&contentHtml));
300 blob_reset( &contentHtml );
301 return pay;
302 }
303
--- src/json_wiki.c
+++ src/json_wiki.c
@@ -273,30 +273,51 @@
273 return json_wiki_get_by_name_or_symname( zPageName, zSymName, contentFormat );
274 }
275
276 /*
277 ** Implementation of /json/wiki/preview.
 
278 */
279 static cson_value * json_wiki_preview(){
280 char const * zContent = NULL;
281 char const * zMime = NULL;
282 cson_string * sContent = NULL;
283 cson_value * pay = NULL;
284 Blob contentOrig = empty_blob;
285 Blob contentHtml = empty_blob;
286 if( !g.perm.WrWiki ){
287 json_set_err(FSL_JSON_E_DENIED,
288 "Requires 'k' access.");
289 return NULL;
290 }
291
292 if(g.json.reqPayload.o){
293 sContent = cson_value_get_string(
294 cson_object_get(g.json.reqPayload.o, "body"));
295 zMime = cson_value_get_cstr(cson_object_get(g.json.reqPayload.o,
296 "mimetype"));
297 }else{
298 sContent = cson_value_get_string(g.json.reqPayload.v);
299 }
300 if(!sContent) {
301 json_set_err(FSL_JSON_E_MISSING_ARGS,
302 "The 'payload' property must be either a string containing the "
303 "Fossil wiki code to preview or an object with body + mimetype "
304 "properties.");
305 return NULL;
306 }
307 zContent = cson_string_cstr(sContent);
308 blob_append( &contentOrig, zContent, (int)cson_string_length_bytes(sContent) );
309 zMime = wiki_filter_mimetypes(zMime);
310 if( 0==fossil_strcmp(zMime, "text/x-markdown") ){
311 markdown_to_html(&contentOrig, 0, &contentHtml);
312 }else if( 0==fossil_strcmp(zMime, "text/plain") ){
313 blob_append(&contentHtml, "<pre class='textPlain'>", -1);
314 blob_append(&contentHtml, blob_str(&contentOrig), blob_size(&contentOrig));
315 blob_append(&contentHtml, "</pre>", -1);
316 }else{
317 wiki_convert( &contentOrig, &contentHtml, 0 );
318 }
319 blob_reset( &contentOrig );
320 pay = cson_value_new_string( blob_str(&contentHtml), (unsigned int)blob_size(&contentHtml));
321 blob_reset( &contentHtml );
322 return pay;
323 }
324
--- www/json-api/api-wiki.md
+++ www/json-api/api-wiki.md
@@ -166,10 +166,13 @@
166166
- `name=string` specifies the page name.
167167
- `content=string` is the body text.\
168168
Content is required for save (unless `createIfNotExists` is true *and*
169169
the page does not exist), optional for create. It *may* be an empty
170170
string.
171
+- `mimetype=string` specifies the mimetype for the body, noting any any
172
+ unrecognized/unsupported mimetype is silently treated as
173
+ `text/x-fossil-wiki`.
171174
- Save (not create) supports a `createIfNotExists` boolean option which
172175
makes it a functional superset of the create/save features. i.e. it
173176
will create if needed, else it will update. If createIfNotExists is
174177
false (the default) then save will fail if given a page name which
175178
does not refer to an existing page.
@@ -179,10 +182,11 @@
179182
high-priority addition. See:\
180183
[](/doc/trunk/www/fileformat.wiki#wikichng)
181184
- **Potential TODO:** we *could* optionally also support
182185
multi-page saving using an array of pages in the request payload:\
183186
`[… page objects … ]`
187
+
184188
185189
186190
<a id="diffs"></a>
187191
# Wiki Diffs
188192
@@ -238,28 +242,29 @@
238242
239243
This command wiki-processes arbitrary text sent from the client. To help
240244
curb potential abuse, its use is restricted to those with "k" access
241245
rights.
242246
243
-The `POST.payload` property must be a string containing Fossil wiki
244
-markup. The response payload is also a string, but contains the
245
-HTML-processed form of the string. Whether or not "all HTML" is allowed
246
-depends on site-level configuration options, and that changes how the
247
-input is processed.
247
+The `POST.payload` property must be either:
248
+
249
+1) A string containing Fossil wiki markup.
250
+
251
+2) An Object with a `body` property holding the text to render and a
252
+ `mimetype` property describing the wiki format:
253
+ `text/x-fossil-wiki` (the default), `text/x-markdown`, or
254
+ `text/plain`. Any unknown type is treated as `text/x-fossil-wiki`.
255
+
256
+The response payload is a string containing the rendered page. Whether
257
+or not "all HTML" is allowed depends on site-level configuration
258
+options, and that changes how the input is processed.
248259
249260
Note that the links in the generated page are for the HTML interface,
250261
and will not work as-is for arbitrary JSON clients. In order to
251262
integrate the parsed content with JSON-based clients the HTML will
252263
probably need to be post-processed, e.g. using jQuery to fish out the
253264
links and re-map wiki page links to a JSON-capable page handler.
254265
255
-**TODO**: Update this to accept the other two wiki formats (which
256
-didn't exist when this API was implemented): markdown and plain text
257
-(which gets HTML-ized for preview purposes). That requires changing
258
-the payload to an object, perhaps simply submitting the same thing as
259
-`/json/save`. There's no reason we can't support both call forms.
260
-
261266
262267
<a id="todo"></a>
263268
# Notes and TODOs
264269
265270
- When server-parsing the wiki content, the generated
266271
--- www/json-api/api-wiki.md
+++ www/json-api/api-wiki.md
@@ -166,10 +166,13 @@
166 - `name=string` specifies the page name.
167 - `content=string` is the body text.\
168 Content is required for save (unless `createIfNotExists` is true *and*
169 the page does not exist), optional for create. It *may* be an empty
170 string.
 
 
 
171 - Save (not create) supports a `createIfNotExists` boolean option which
172 makes it a functional superset of the create/save features. i.e. it
173 will create if needed, else it will update. If createIfNotExists is
174 false (the default) then save will fail if given a page name which
175 does not refer to an existing page.
@@ -179,10 +182,11 @@
179 high-priority addition. See:\
180 [](/doc/trunk/www/fileformat.wiki#wikichng)
181 - **Potential TODO:** we *could* optionally also support
182 multi-page saving using an array of pages in the request payload:\
183 `[… page objects … ]`
 
184
185
186 <a id="diffs"></a>
187 # Wiki Diffs
188
@@ -238,28 +242,29 @@
238
239 This command wiki-processes arbitrary text sent from the client. To help
240 curb potential abuse, its use is restricted to those with "k" access
241 rights.
242
243 The `POST.payload` property must be a string containing Fossil wiki
244 markup. The response payload is also a string, but contains the
245 HTML-processed form of the string. Whether or not "all HTML" is allowed
246 depends on site-level configuration options, and that changes how the
247 input is processed.
 
 
 
 
 
 
 
248
249 Note that the links in the generated page are for the HTML interface,
250 and will not work as-is for arbitrary JSON clients. In order to
251 integrate the parsed content with JSON-based clients the HTML will
252 probably need to be post-processed, e.g. using jQuery to fish out the
253 links and re-map wiki page links to a JSON-capable page handler.
254
255 **TODO**: Update this to accept the other two wiki formats (which
256 didn't exist when this API was implemented): markdown and plain text
257 (which gets HTML-ized for preview purposes). That requires changing
258 the payload to an object, perhaps simply submitting the same thing as
259 `/json/save`. There's no reason we can't support both call forms.
260
261
262 <a id="todo"></a>
263 # Notes and TODOs
264
265 - When server-parsing the wiki content, the generated
266
--- www/json-api/api-wiki.md
+++ www/json-api/api-wiki.md
@@ -166,10 +166,13 @@
166 - `name=string` specifies the page name.
167 - `content=string` is the body text.\
168 Content is required for save (unless `createIfNotExists` is true *and*
169 the page does not exist), optional for create. It *may* be an empty
170 string.
171 - `mimetype=string` specifies the mimetype for the body, noting any any
172 unrecognized/unsupported mimetype is silently treated as
173 `text/x-fossil-wiki`.
174 - Save (not create) supports a `createIfNotExists` boolean option which
175 makes it a functional superset of the create/save features. i.e. it
176 will create if needed, else it will update. If createIfNotExists is
177 false (the default) then save will fail if given a page name which
178 does not refer to an existing page.
@@ -179,10 +182,11 @@
182 high-priority addition. See:\
183 [](/doc/trunk/www/fileformat.wiki#wikichng)
184 - **Potential TODO:** we *could* optionally also support
185 multi-page saving using an array of pages in the request payload:\
186 `[… page objects … ]`
187
188
189
190 <a id="diffs"></a>
191 # Wiki Diffs
192
@@ -238,28 +242,29 @@
242
243 This command wiki-processes arbitrary text sent from the client. To help
244 curb potential abuse, its use is restricted to those with "k" access
245 rights.
246
247 The `POST.payload` property must be either:
248
249 1) A string containing Fossil wiki markup.
250
251 2) An Object with a `body` property holding the text to render and a
252 `mimetype` property describing the wiki format:
253 `text/x-fossil-wiki` (the default), `text/x-markdown`, or
254 `text/plain`. Any unknown type is treated as `text/x-fossil-wiki`.
255
256 The response payload is a string containing the rendered page. Whether
257 or not "all HTML" is allowed depends on site-level configuration
258 options, and that changes how the input is processed.
259
260 Note that the links in the generated page are for the HTML interface,
261 and will not work as-is for arbitrary JSON clients. In order to
262 integrate the parsed content with JSON-based clients the HTML will
263 probably need to be post-processed, e.g. using jQuery to fish out the
264 links and re-map wiki page links to a JSON-capable page handler.
265
 
 
 
 
 
 
266
267 <a id="todo"></a>
268 # Notes and TODOs
269
270 - When server-parsing the wiki content, the generated
271

Keyboard Shortcuts

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