Fossil SCM

Factored out cson_cgi bits - now using fossil's CGI bits. Removed cson_cgi from cson_amalgamation (cuts its size considerably). Seems to still work, and this removes some discrepancies in how CGI/server modes are handled.

stephan 2011-09-18 04:31 UTC json
Commit 4cf968144093bb856a42e49911f0d04a277abc9c
3 files changed +98 -13 +196 -135 +32 -39
+98 -13
--- src/cgi.c
+++ src/cgi.c
@@ -508,10 +508,11 @@
508508
zValue = "";
509509
}
510510
if( fossil_islower(zName[0]) ){
511511
cgi_set_parameter_nocopy(zName, zValue);
512512
}
513
+ json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
513514
}
514515
}
515516
516517
/*
517518
** *pz is a string that consists of multiple lines of text. This
@@ -680,10 +681,82 @@
680681
}
681682
}
682683
}
683684
}
684685
}
686
+
687
+
688
+/*
689
+** Internal helper for cson_data_source_FILE_n().
690
+*/
691
+typedef struct CgiPostReadState_ {
692
+ FILE * fh;
693
+ unsigned int len;
694
+ unsigned int pos;
695
+} CgiPostReadState;
696
+
697
+/*
698
+** cson_data_source_f() impl which reads only up to
699
+** a specified amount of data from its input FILE.
700
+** state MUST be a full populated (CgiPostReadState*).
701
+*/
702
+static int cson_data_source_FILE_n( void * state,
703
+ void * dest,
704
+ unsigned int * n ){
705
+ if( ! state || !dest || !n ) return cson_rc.ArgError;
706
+ else {
707
+ CgiPostReadState * st = (CgiPostReadState *)state;
708
+ if( st->pos >= st->len ){
709
+ *n = 0;
710
+ return 0;
711
+ } else if( !*n || ((st->pos + *n) > st->len) ){
712
+ return cson_rc.RangeError;
713
+ }else{
714
+ unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
715
+ if( ! rsz ){
716
+ *n = rsz;
717
+ return feof(st->fh) ? 0 : cson_rc.IOError;
718
+ }else{
719
+ *n = rsz;
720
+ st->pos += *n;
721
+ return 0;
722
+ }
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
+
685758
686759
/*
687760
** Initialize the query parameter database. Information is pulled from
688761
** the QUERY_STRING environment variable (if it exists), from standard
689762
** input if there is POST data, and from HTTP_COOKIE.
@@ -690,23 +763,33 @@
690763
*/
691764
void cgi_init(void){
692765
char *z;
693766
const char *zType;
694767
int len;
768
+ json_main_bootstrap();
695769
cgi_destination(CGI_BODY);
770
+
771
+ z = (char*)P("HTTP_COOKIE");
772
+ if( z ){
773
+ z = mprintf("%s",z);
774
+ add_param_list(z, ';');
775
+ }
776
+
696777
z = (char*)P("QUERY_STRING");
697778
if( z ){
698779
z = mprintf("%s",z);
699780
add_param_list(z, '&');
700781
}
701782
702783
z = (char*)P("REMOTE_ADDR");
703
- if( z ) g.zIpAddr = mprintf("%s", z);
784
+ if( z ){
785
+ g.zIpAddr = mprintf("%s", z);
786
+ }
704787
705788
len = atoi(PD("CONTENT_LENGTH", "0"));
706789
g.zContentType = zType = P("CONTENT_TYPE");
707
- if( !g.json.isJsonMode && (len>0 && zType) ){/* in JSON mode this is delegated to the cson_cgi API.*/
790
+ if( len>0 && zType ){
708791
blob_zero(&g.cgiIn);
709792
if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
710793
|| strncmp(zType,"multipart/form-data",19)==0 ){
711794
z = fossil_malloc( len+1 );
712795
len = fread(z, 1, len, g.httpIn);
@@ -721,21 +804,23 @@
721804
blob_uncompress(&g.cgiIn, &g.cgiIn);
722805
}else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
723806
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
724807
}else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
725808
blob_read_from_channel(&g.cgiIn, g.httpIn, len);
726
- }
727
- /* FIXME: treat application/json and text/plain as unencoded
728
- JSON data.
729
- */
730
- }
731
-
732
- z = (char*)P("HTTP_COOKIE");
733
- if( z ){
734
- z = mprintf("%s",z);
735
- add_param_list(z, ';');
736
- }
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
+
737822
}
738823
739824
/*
740825
** This is the comparison function used to sort the aParamQP[] array of
741826
** query parameters and cookies.
742827
--- src/cgi.c
+++ src/cgi.c
@@ -508,10 +508,11 @@
508 zValue = "";
509 }
510 if( fossil_islower(zName[0]) ){
511 cgi_set_parameter_nocopy(zName, zValue);
512 }
 
513 }
514 }
515
516 /*
517 ** *pz is a string that consists of multiple lines of text. This
@@ -680,10 +681,82 @@
680 }
681 }
682 }
683 }
684 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
686 /*
687 ** Initialize the query parameter database. Information is pulled from
688 ** the QUERY_STRING environment variable (if it exists), from standard
689 ** input if there is POST data, and from HTTP_COOKIE.
@@ -690,23 +763,33 @@
690 */
691 void cgi_init(void){
692 char *z;
693 const char *zType;
694 int len;
 
695 cgi_destination(CGI_BODY);
 
 
 
 
 
 
 
696 z = (char*)P("QUERY_STRING");
697 if( z ){
698 z = mprintf("%s",z);
699 add_param_list(z, '&');
700 }
701
702 z = (char*)P("REMOTE_ADDR");
703 if( z ) g.zIpAddr = mprintf("%s", z);
 
 
704
705 len = atoi(PD("CONTENT_LENGTH", "0"));
706 g.zContentType = zType = P("CONTENT_TYPE");
707 if( !g.json.isJsonMode && (len>0 && zType) ){/* in JSON mode this is delegated to the cson_cgi API.*/
708 blob_zero(&g.cgiIn);
709 if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
710 || strncmp(zType,"multipart/form-data",19)==0 ){
711 z = fossil_malloc( len+1 );
712 len = fread(z, 1, len, g.httpIn);
@@ -721,21 +804,23 @@
721 blob_uncompress(&g.cgiIn, &g.cgiIn);
722 }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
723 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
724 }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
725 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
726 }
727 /* FIXME: treat application/json and text/plain as unencoded
728 JSON data.
729 */
730 }
731
732 z = (char*)P("HTTP_COOKIE");
733 if( z ){
734 z = mprintf("%s",z);
735 add_param_list(z, ';');
736 }
 
 
737 }
738
739 /*
740 ** This is the comparison function used to sort the aParamQP[] array of
741 ** query parameters and cookies.
742
--- src/cgi.c
+++ src/cgi.c
@@ -508,10 +508,11 @@
508 zValue = "";
509 }
510 if( fossil_islower(zName[0]) ){
511 cgi_set_parameter_nocopy(zName, zValue);
512 }
513 json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
514 }
515 }
516
517 /*
518 ** *pz is a string that consists of multiple lines of text. This
@@ -680,10 +681,82 @@
681 }
682 }
683 }
684 }
685 }
686
687
688 /*
689 ** Internal helper for cson_data_source_FILE_n().
690 */
691 typedef struct CgiPostReadState_ {
692 FILE * fh;
693 unsigned int len;
694 unsigned int pos;
695 } CgiPostReadState;
696
697 /*
698 ** cson_data_source_f() impl which reads only up to
699 ** a specified amount of data from its input FILE.
700 ** state MUST be a full populated (CgiPostReadState*).
701 */
702 static int cson_data_source_FILE_n( void * state,
703 void * dest,
704 unsigned int * n ){
705 if( ! state || !dest || !n ) return cson_rc.ArgError;
706 else {
707 CgiPostReadState * st = (CgiPostReadState *)state;
708 if( st->pos >= st->len ){
709 *n = 0;
710 return 0;
711 } else if( !*n || ((st->pos + *n) > st->len) ){
712 return cson_rc.RangeError;
713 }else{
714 unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh );
715 if( ! rsz ){
716 *n = rsz;
717 return feof(st->fh) ? 0 : cson_rc.IOError;
718 }else{
719 *n = rsz;
720 st->pos += *n;
721 return 0;
722 }
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
761 ** the QUERY_STRING environment variable (if it exists), from standard
762 ** input if there is POST data, and from HTTP_COOKIE.
@@ -690,23 +763,33 @@
763 */
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);
774 add_param_list(z, ';');
775 }
776
777 z = (char*)P("QUERY_STRING");
778 if( z ){
779 z = mprintf("%s",z);
780 add_param_list(z, '&');
781 }
782
783 z = (char*)P("REMOTE_ADDR");
784 if( z ){
785 g.zIpAddr = mprintf("%s", z);
786 }
787
788 len = atoi(PD("CONTENT_LENGTH", "0"));
789 g.zContentType = zType = P("CONTENT_TYPE");
790 if( len>0 && zType ){
791 blob_zero(&g.cgiIn);
792 if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
793 || strncmp(zType,"multipart/form-data",19)==0 ){
794 z = fossil_malloc( len+1 );
795 len = fread(z, 1, len, g.httpIn);
@@ -721,21 +804,23 @@
804 blob_uncompress(&g.cgiIn, &g.cgiIn);
805 }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
806 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
807 }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
808 blob_read_from_channel(&g.cgiIn, g.httpIn, len);
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 /*
825 ** This is the comparison function used to sort the aParamQP[] array of
826 ** query parameters and cookies.
827
+196 -135
--- src/json.c
+++ src/json.c
@@ -20,16 +20,10 @@
2020
** For notes regarding the public JSON interface, please see:
2121
**
2222
** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
2323
**
2424
**
25
-** Notable FIXMEs:
26
-**
27
-** - The overlap between cson_cgi and fossil needs to be gotten rid
28
-** of because cson_cgi cannot get all the environment info it needs
29
-** when fossil is running in server mode. The goal is to remove all
30
-** of the cson_cgi bits.
3125
*/
3226
#include "config.h"
3327
#include "VERSION.h"
3428
#include "json.h"
3529
#include <assert.h>
@@ -156,24 +150,91 @@
156150
static char buf[BufSize] = {'F','O','S','S','I','L','-',0};
157151
assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
158152
sprintf(buf+7,"%04d", code);
159153
return buf;
160154
}
155
+
156
+/*
157
+** Adds v to the API-internal cleanup mechanism. key must be a unique
158
+** key for the given element. Adding another item with that key may
159
+** free the previous one. If freeOnError is true then v is passed to
160
+** cson_value_free() if the key cannot be inserted, otherweise
161
+** ownership of v is not changed on error.
162
+**
163
+** Returns 0 on success.
164
+**
165
+*** On success, ownership of v is transfered to (or shared with)
166
+*** g.json.gc, and v will be valid until that object is cleaned up or
167
+*** its key is replaced via another call to this function.
168
+*/
169
+int json_gc_add( char const * key, cson_value * v, char freeOnError ){
170
+ int const rc = cson_object_set( g.json.gc.o, key, v );
171
+ assert( NULL != g.json.gc.o );
172
+ if( (0 != rc) && freeOnError ){
173
+ cson_value_free( v );
174
+ }
175
+ return rc;
176
+}
177
+
161178
162179
/*
163180
** Returns the value of json_rc_cstr(code) as a new JSON
164181
** string, which is owned by the caller and must eventually
165182
** be cson_value_free()d or transfered to a JSON container.
166183
*/
167184
cson_value * json_rc_string( int code ){
168185
return cson_value_new_string( json_rc_cstr(code), 11 );
169186
}
187
+
188
+
189
+/*
190
+** Gets a POST/GET/COOKIE value. The returned memory is owned by the
191
+** g.json object (one of its sub-objects). Returns NULL if no match is
192
+** found.
193
+**
194
+** Precedence: GET, COOKIE, POST. COOKIE _should_ be last
195
+** but currently is not for internal order-of-init reasons.
196
+** Since fossil only uses one cookie, this is not a high-prio
197
+** problem.
198
+*/
199
+cson_value * json_getenv( char const * zKey ){
200
+ cson_value * rc;
201
+ rc = cson_object_get( g.json.param.o, zKey );
202
+ if( rc ){
203
+ return rc;
204
+ }else{
205
+ rc = cson_object_get( g.json.post.o, zKey );
206
+ if(rc){
207
+ return rc;
208
+ }else{
209
+ char const * cv = PD(zKey,NULL);
210
+ if(cv){/*transform it to JSON for later use.*/
211
+ rc = cson_value_new_string(cv,strlen(cv));
212
+ cson_object_set( g.json.param.o, zKey, rc );
213
+ return rc;
214
+ }
215
+ }
216
+ }
217
+ return NULL;
218
+}
219
+
220
+/*
221
+** Adds v to g.json.param.o using the given key. May cause
222
+** any prior item with that key to be destroyed (depends on
223
+** current reference count for that value).
224
+** On succes, transfers ownership of v to g.json.param.o.
225
+** On error ownership of v is not modified.
226
+*/
227
+int json_setenv( char const * zKey, cson_value * v ){
228
+ return cson_object_set( g.json.param.o, zKey, v );
229
+}
230
+
170231
171232
/*
172233
** Returns the current request's JSON authentication token, or NULL if
173234
** none is found. The token's memory is owned by (or shared with)
174
-** g.json.cgiCx.
235
+** g.json.
175236
**
176237
** If an auth token is found in the GET/POST JSON request data then
177238
** fossil is given that data for use in authentication for this
178239
** session.
179240
**
@@ -186,52 +247,72 @@
186247
*/
187248
static cson_value * json_auth_token(){
188249
if( !g.json.authToken ){
189250
/* Try to get an authorization token from GET parameter, POSTed
190251
JSON, or fossil cookie (in that order). */
191
- g.json.authToken = cson_cgi_getenv(&g.json.cgiCx, "gp", FossilJsonKeys.authToken)
192
- /* reminder to self: cson_cgi does not have access to the cookies
193
- because fossil's core consumes them. Thus we cannot use "agpc"
194
- here. We use "a" (App-specific) as a place to store fossil's
195
- cookie value. Reminder #2: in server mode cson_cgi also doesn't
196
- have access to the GET parameters because of how the QUERY_STRING
197
- is set. That's on my to-fix list for cson_cgi (feeding it our own
198
- query string).
199
- */;
200
- if(g.json.authToken && cson_value_is_string(g.json.authToken)){
252
+ g.json.authToken = json_getenv(FossilJsonKeys.authToken);
253
+ if(g.json.authToken
254
+ && cson_value_is_string(g.json.authToken)
255
+ && !PD(login_cookie_name(),NULL)){
201256
/* tell fossil to use this login info.
202
-
203
- FIXME: because the JSON bits don't carry around
204
- login_cookie_name(), there is a potential login hijacking
205
- window here. We may need to change the JSON auth token to be
206
- in the form: login_cookie_name()=...
207
-
208
- Then again, the hardened cookie value helps ensure that
209
- only a proper key/value match is valid.
257
+
258
+ FIXME?: because the JSON bits don't carry around
259
+ login_cookie_name(), there is a potential login hijacking
260
+ window here. We may need to change the JSON auth token to be
261
+ in the form: login_cookie_name()=...
262
+
263
+ Then again, the hardened cookie value helps ensure that
264
+ only a proper key/value match is valid.
210265
*/
211266
cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) );
212267
}else if( g.isCGI ){
213268
/* try fossil's conventional cookie. */
214269
/* Reminder: chicken/egg scenario regarding db access in CLI
215
- mode because login_cookie_name() needs the db. */
270
+ mode because login_cookie_name() needs the db. CLI
271
+ mode does not use any authentication, so we don't need
272
+ to support it here.
273
+ */
216274
char const * zCookie = P(login_cookie_name());
217275
if( zCookie && *zCookie ){
218276
/* Transfer fossil's cookie to JSON for downstream convenience... */
219277
cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
220
- if( 0 == cson_cgi_gc_add( &g.json.cgiCx, FossilJsonKeys.authToken, v, 1 ) ){
221
- g.json.authToken = v;
222
- }
278
+ json_setenv( FossilJsonKeys.authToken, v );
279
+ g.json.authToken = v;
223280
}
224281
}
225282
}
226283
return g.json.authToken;
227284
}
228285
229286
/*
230
-** Performs some common initialization of JSON-related state.
287
+** Initializes some JSON bits which need to be initialized relatively
288
+** early on. It should only be called from cgi_init() or
289
+** json_cmd_top() (early on in those functions).
290
+**
291
+** Initializes g.json.gc and g.json.param.
292
+*/
293
+void json_main_bootstrap(){
294
+ cson_value * v;
295
+ assert( (NULL == g.json.gc.v) && "cgi_json_bootstrap() was called twice!" );
296
+ v = cson_value_new_object();
297
+ g.json.gc.v = v;
298
+ g.json.gc.o = cson_value_get_object(v);
299
+
300
+ v = cson_value_new_object();
301
+ g.json.param.v = v;
302
+ g.json.param.o = cson_value_get_object(v);
303
+ json_gc_add("$PARAMS", v, 1);
304
+}
305
+
306
+
307
+/*
308
+** Performs some common initialization of JSON-related state. Must be
309
+** called by the json_page_top() and json_cmd_top() dispatching
310
+** functions to set up the JSON stat used by the dispatched functions.
311
+**
231312
** Implicitly sets up the login information state in CGI mode, but
232
-** does not perform any authentication here. It _might_ (haven't
313
+** does not perform any permissions checking. It _might_ (haven't
233314
** tested this) die with an error if an auth cookie is malformed.
234315
**
235316
** This must be called by the top-level JSON command dispatching code
236317
** before they do any work.
237318
**
@@ -238,101 +319,93 @@
238319
** This must only be called once, or an assertion may be triggered.
239320
*/
240321
static void json_mode_bootstrap(){
241322
static char once = 0 /* guard against multiple runs */;
242323
char const * zPath = P("PATH_INFO");
243
- cson_value * pathSplit =
244
- cson_cgi_getenv(&g.json.cgiCx,"e","PATH_INFO_SPLIT");
324
+ cson_value * pathSplit = NULL;
325
+ cson_array * ar = NULL;
245326
assert( (0==once) && "json_mode_bootstrap() called too many times!");
246327
if( once ){
247328
return;
248329
}else{
249330
once = 1;
250331
}
251332
g.json.isJsonMode = 1;
252333
g.json.resultCode = 0;
253
- g.json.cmdOffset = -1;
334
+ g.json.cmd.offset = -1;
254335
if( !g.isCGI && g.fullHttpReply ){
255336
/* workaround for server mode, so we see it as CGI mode. */
256337
g.isCGI = 1;
257338
}
258339
#if defined(NDEBUG)
259340
/* avoids debug messages on stderr in JSON mode */
260341
sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0);
261342
#endif
262343
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 );
263347
/*
264348
The following if/else block translates the PATH_INFO path (in
265349
CLI/server modes) or g.argv (CLI mode) into an internal list so
266350
that we can simplify command dispatching later on.
267351
268352
Note that translating g.argv this way is overkill but allows us to
269353
avoid CLI-only special-case handling in other code, e.g.
270354
json_command_arg().
271355
*/
272
- if( pathSplit ){
273
- /* cson_cgi already did this, so let's just re-use it. This does
274
- not happen in "plain server" mode, but does in CGI mode.
275
- */
276
- assert( g.isCGI && "g.isCGI should have been set by now." );
277
- cson_cgi_setenv( &g.json.cgiCx,
278
- FossilJsonKeys.commandPath,
279
- pathSplit );
280
- }else{ /* either CLI or server mode... */
281
- cson_value * arV /* value to store path in */;
282
- cson_array * ar /* the "real" array object */;
283
- arV = cson_value_new_array();
284
- ar = cson_value_get_array(arV);
285
- cson_cgi_setenv( &g.json.cgiCx,
286
- FossilJsonKeys.commandPath,
287
- arV );
288
- if( zPath ){
289
- /* Translate fossil's PATH_INFO into cson_cgi for later
290
- convenience, to help consolidate how we handle CGI/server
291
- modes. This block is hit when running in plain server mode.
292
- */
293
- char const * p = zPath /* current byte */;
294
- char const * head = p /* current start-of-token */;
295
- unsigned int len = 0 /* current token's lengh */;
296
- assert( g.isCGI && "g.isCGI should have been set by now." );
297
- for( ;*p!='?'; ++p){
298
- if( !*p || ('/' == *p) ){
299
- if( len ){
300
- cson_value * part;
301
- assert( head != p );
302
- part = cson_value_new_string(head, len);
303
- cson_array_append( ar, part );
304
- len = 0;
305
- }
306
- if( !*p ){
307
- break;
308
- }
309
- head = p+1;
310
- continue;
311
- }
312
- ++len;
313
- }
314
- }else{
315
- /* assume CLI mode */
316
- int i;
317
- char const * arg;
318
- cson_value * part;
319
- assert( (!g.isCGI) && "g.isCGI set and we do not expect that to be the case here." );
320
- for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
321
- arg = g.argv[i];
322
- if( !arg || !*arg ){
323
- continue;
324
- }
325
- part = cson_value_new_string(arg,strlen(arg));
326
- cson_array_append(ar, part);
327
- }
328
- }
329
- }
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 );
371
+ zPart = (char*)malloc(len+1);
372
+ assert( zPart != NULL );
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
+ }
384
+ head = p+1;
385
+ continue;
386
+ }
387
+ ++len;
388
+ }
389
+ }else{/* assume CLI mode */
390
+ int i;
391
+ char const * arg;
392
+ cson_value * part;
393
+ for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
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
+
330403
/* g.json.reqPayload exists only to simplify some of our access to
331404
the request payload. We currently only use this in the context of
332405
Object payloads, not Arrays, strings, etc. */
333
- g.json.reqPayload.v = cson_cgi_getenv( &g.json.cgiCx, "p", "payload" );
406
+ g.json.reqPayload.v = cson_object_get( g.json.post.o, "payload" );
334407
if( g.json.reqPayload.v ){
335408
g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v )
336409
/* g.json.reqPayload.o may legally be NULL, which means only that
337410
g.json.reqPayload.v is-not-a Object.
338411
*/;
@@ -359,46 +432,43 @@
359432
** abbreviated name because we rely on the full name "json" here. The
360433
** g object probably has the short form which we can use for our
361434
** purposes, but i haven't yet looked for it.
362435
*/
363436
static char const * json_command_arg(unsigned char ndx){
364
- cson_array * ar = cson_value_get_array(
365
- cson_cgi_getenv(&g.json.cgiCx,
366
- "a",
367
- FossilJsonKeys.commandPath));
437
+ cson_array * ar = g.json.cmd.a;
368438
assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?");
369
- if( g.json.cmdOffset < 0 ){
439
+ if( g.json.cmd.offset < 0 ){
370440
/* first-time setup. */
371441
short i = 0;
372
-#define NEXT cson_string_cstr( \
373
- cson_value_get_string( \
374
- cson_array_get(ar,i) \
442
+#define NEXT cson_string_cstr( \
443
+ cson_value_get_string( \
444
+ cson_array_get(ar,i) \
375445
))
376446
char const * tok = NEXT;
377447
while( tok ){
378448
if( 0==strncmp("json",tok,4) ){
379
- g.json.cmdOffset = i;
449
+ g.json.cmd.offset = i;
380450
break;
381451
}
382452
++i;
383453
tok = NEXT;
384454
}
385455
}
386456
#undef NEXT
387
- if( g.json.cmdOffset < 0 ){
457
+ if(g.json.cmd.offset < 0){
388458
return NULL;
389459
}else{
390
- ndx = g.json.cmdOffset + ndx;
391
- return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmdOffset + ndx )));
460
+ ndx = g.json.cmd.offset + ndx;
461
+ return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx )));
392462
}
393463
}
394464
395465
/*
396466
** If g.json.reqPayload.o is NULL then NULL is returned, else the
397467
** given property is searched for in the request payload. If found it
398468
** is returned. The returned value is owned by (or shares ownership
399
-** with) g.json.cgiCx, and must NOT be cson_value_free()'d by the
469
+** with) g.json, and must NOT be cson_value_free()'d by the
400470
** caller.
401471
*/
402472
static cson_value * json_payload_property( char const * key ){
403473
return g.json.reqPayload.o ?
404474
cson_object_get( g.json.reqPayload.o, key )
@@ -426,12 +496,14 @@
426496
** Alternately, we can create different JsonPageDef arrays for each
427497
** subset.
428498
*/
429499
char const * name;
430500
/*
431
- ** Returns a payload object for the response.
432
- ** If it returns a non-NULL value, the caller owns it.
501
+ ** Returns a payload object for the response. If it returns a
502
+ ** non-NULL value, the caller owns it. To trigger an error this
503
+ ** function should set g.json.resultCode to a value from the
504
+ ** FossilJsonCodes enum.
433505
*/
434506
cson_value * (*func)();
435507
/*
436508
** Which mode(s) of execution does func() support:
437509
**
@@ -486,10 +558,11 @@
486558
#if 0
487559
static unsigned int json_timestamp(){
488560
489561
}
490562
#endif
563
+
491564
492565
/*
493566
** Creates a new Fossil/JSON response envelope skeleton. It is owned
494567
** by the caller, who must eventually free it using cson_value_free(),
495568
** or add it to a cson container to transfer ownership. Returns NULL
@@ -568,11 +641,11 @@
568641
}
569642
if( pMsg && *pMsg ){
570643
tmp = cson_value_new_string(pMsg,strlen(pMsg));
571644
SET("resultText");
572645
}
573
- tmp = cson_cgi_getenv(&g.json.cgiCx, "gp", "requestId");
646
+ tmp = json_getenv("requestId");
574647
if( tmp ) cson_object_set( o, "requestId", tmp );
575648
if( NULL != payload ){
576649
if( resultCode ){
577650
cson_value_free(payload);
578651
payload = NULL;
@@ -582,16 +655,12 @@
582655
}
583656
}
584657
#undef SET
585658
586659
if(0){/*Only for debuggering, add some info to the response.*/
587
- tmp = cson_cgi_env_get_val(&g.json.cgiCx,'a', 0);
588
- if(tmp){
589
- cson_object_set( o, "$APP", tmp );
590
- }
591
- tmp = cson_value_new_integer( g.json.cmdOffset );
592
- cson_object_set( o, "cmdOffset", tmp );
660
+ tmp = cson_value_new_integer( g.json.cmd.offset );
661
+ cson_object_set( o, "cmd.offset", tmp );
593662
cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
594663
}
595664
596665
goto ok;
597666
cleanup:
@@ -665,25 +734,20 @@
665734
cson_object_set( jobj, "resultCodeParanoiaLevel",
666735
cson_value_new_integer(g.json.errorDetailParanoia) );
667736
return jval;
668737
}
669738
670
-#if 0
671
-/* we have a disconnect here between fossil's server-mode QUERY_STRING
672
- handling and cson_cgi's.
673
-*/
674
-static cson_value * json_getenv( char const *zWhichEnv, char const * zKey ){
675
- return cson_cgi_getenv(&g.json.cgiCx, zWhichEnv, zKey);
676
-}
677
-static char const * json_getenv_cstr( char const *zWhichEnv, char const * zKey ){
678
- char const * rc = cson_value_get_cstr( json_getenv(zWhichEnv, zKey) );
679
- if( !rc && zWhichEnv && (NULL!=strstr(zWhichEnv,"g")) ){
680
- rc = PD(zKey,NULL);
681
- }
682
- return rc;
683
-}
684
-#endif
739
+/*
740
+** Returns the string form of a json_getenv() value, but ONLY
741
+** If that value is-a String. Non-strings are not converted
742
+** to strings for this purpose. Returned memory is owned by
743
+** g.json or fossil..
744
+*/
745
+static char const * json_getenv_cstr( char const * zKey ){
746
+ return cson_value_get_cstr( json_getenv(zKey) );
747
+}
748
+
685749
686750
/*
687751
** Implementation for /json/cap
688752
**
689753
** Returned object contains details about the "capabilities" of the
@@ -853,13 +917,11 @@
853917
;
854918
if(!token){
855919
g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
856920
}else{
857921
login_clear_login_data();
858
- g.json.authToken = NULL /* memory is owned by g.json.cgiCx, but
859
- now lives only in the cson_cgi garbage
860
- collector.*/;
922
+ g.json.authToken = NULL /* memory is owned elsewhere.*/;
861923
}
862924
return NULL;
863925
}
864926
865927
/*
@@ -1006,11 +1068,10 @@
10061068
Blob buf = empty_blob;
10071069
char const * cmd;
10081070
cson_value * payload = NULL;
10091071
cson_value * root = NULL;
10101072
JsonPageDef const * pageDef = NULL;
1011
- cgi_set_content_type( cson_cgi_guess_content_type(&g.json.cgiCx) );
10121073
json_mode_bootstrap();
10131074
cmd = json_command_arg(1);
10141075
/*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/
10151076
pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
10161077
if( ! pageDef ){
@@ -1055,20 +1116,20 @@
10551116
void json_cmd_top(void){
10561117
char const * cmd = NULL;
10571118
int rc = 1002;
10581119
cson_value * payload = NULL;
10591120
JsonPageDef const * pageDef;
1121
+ json_main_bootstrap();
10601122
json_mode_bootstrap();
10611123
if( g.argc<3 ){
10621124
goto usage;
10631125
}
10641126
db_find_and_open_repository(0, 0);
10651127
cmd = json_command_arg(1);
10661128
if( !cmd || !*cmd ){
10671129
goto usage;
10681130
}
1069
- cgi_set_content_type( cson_cgi_guess_content_type(&g.json.cgiCx) );
10701131
pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
10711132
if( ! pageDef ){
10721133
json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
10731134
return;
10741135
}else if( pageDef->runMode > 0 /*HTTP only*/){
10751136
--- src/json.c
+++ src/json.c
@@ -20,16 +20,10 @@
20 ** For notes regarding the public JSON interface, please see:
21 **
22 ** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
23 **
24 **
25 ** Notable FIXMEs:
26 **
27 ** - The overlap between cson_cgi and fossil needs to be gotten rid
28 ** of because cson_cgi cannot get all the environment info it needs
29 ** when fossil is running in server mode. The goal is to remove all
30 ** of the cson_cgi bits.
31 */
32 #include "config.h"
33 #include "VERSION.h"
34 #include "json.h"
35 #include <assert.h>
@@ -156,24 +150,91 @@
156 static char buf[BufSize] = {'F','O','S','S','I','L','-',0};
157 assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
158 sprintf(buf+7,"%04d", code);
159 return buf;
160 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
162 /*
163 ** Returns the value of json_rc_cstr(code) as a new JSON
164 ** string, which is owned by the caller and must eventually
165 ** be cson_value_free()d or transfered to a JSON container.
166 */
167 cson_value * json_rc_string( int code ){
168 return cson_value_new_string( json_rc_cstr(code), 11 );
169 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
171 /*
172 ** Returns the current request's JSON authentication token, or NULL if
173 ** none is found. The token's memory is owned by (or shared with)
174 ** g.json.cgiCx.
175 **
176 ** If an auth token is found in the GET/POST JSON request data then
177 ** fossil is given that data for use in authentication for this
178 ** session.
179 **
@@ -186,52 +247,72 @@
186 */
187 static cson_value * json_auth_token(){
188 if( !g.json.authToken ){
189 /* Try to get an authorization token from GET parameter, POSTed
190 JSON, or fossil cookie (in that order). */
191 g.json.authToken = cson_cgi_getenv(&g.json.cgiCx, "gp", FossilJsonKeys.authToken)
192 /* reminder to self: cson_cgi does not have access to the cookies
193 because fossil's core consumes them. Thus we cannot use "agpc"
194 here. We use "a" (App-specific) as a place to store fossil's
195 cookie value. Reminder #2: in server mode cson_cgi also doesn't
196 have access to the GET parameters because of how the QUERY_STRING
197 is set. That's on my to-fix list for cson_cgi (feeding it our own
198 query string).
199 */;
200 if(g.json.authToken && cson_value_is_string(g.json.authToken)){
201 /* tell fossil to use this login info.
202
203 FIXME: because the JSON bits don't carry around
204 login_cookie_name(), there is a potential login hijacking
205 window here. We may need to change the JSON auth token to be
206 in the form: login_cookie_name()=...
207
208 Then again, the hardened cookie value helps ensure that
209 only a proper key/value match is valid.
210 */
211 cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) );
212 }else if( g.isCGI ){
213 /* try fossil's conventional cookie. */
214 /* Reminder: chicken/egg scenario regarding db access in CLI
215 mode because login_cookie_name() needs the db. */
 
 
 
216 char const * zCookie = P(login_cookie_name());
217 if( zCookie && *zCookie ){
218 /* Transfer fossil's cookie to JSON for downstream convenience... */
219 cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
220 if( 0 == cson_cgi_gc_add( &g.json.cgiCx, FossilJsonKeys.authToken, v, 1 ) ){
221 g.json.authToken = v;
222 }
223 }
224 }
225 }
226 return g.json.authToken;
227 }
228
229 /*
230 ** Performs some common initialization of JSON-related state.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231 ** Implicitly sets up the login information state in CGI mode, but
232 ** does not perform any authentication here. It _might_ (haven't
233 ** tested this) die with an error if an auth cookie is malformed.
234 **
235 ** This must be called by the top-level JSON command dispatching code
236 ** before they do any work.
237 **
@@ -238,101 +319,93 @@
238 ** This must only be called once, or an assertion may be triggered.
239 */
240 static void json_mode_bootstrap(){
241 static char once = 0 /* guard against multiple runs */;
242 char const * zPath = P("PATH_INFO");
243 cson_value * pathSplit =
244 cson_cgi_getenv(&g.json.cgiCx,"e","PATH_INFO_SPLIT");
245 assert( (0==once) && "json_mode_bootstrap() called too many times!");
246 if( once ){
247 return;
248 }else{
249 once = 1;
250 }
251 g.json.isJsonMode = 1;
252 g.json.resultCode = 0;
253 g.json.cmdOffset = -1;
254 if( !g.isCGI && g.fullHttpReply ){
255 /* workaround for server mode, so we see it as CGI mode. */
256 g.isCGI = 1;
257 }
258 #if defined(NDEBUG)
259 /* avoids debug messages on stderr in JSON mode */
260 sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0);
261 #endif
262
 
 
 
263 /*
264 The following if/else block translates the PATH_INFO path (in
265 CLI/server modes) or g.argv (CLI mode) into an internal list so
266 that we can simplify command dispatching later on.
267
268 Note that translating g.argv this way is overkill but allows us to
269 avoid CLI-only special-case handling in other code, e.g.
270 json_command_arg().
271 */
272 if( pathSplit ){
273 /* cson_cgi already did this, so let's just re-use it. This does
274 not happen in "plain server" mode, but does in CGI mode.
275 */
276 assert( g.isCGI && "g.isCGI should have been set by now." );
277 cson_cgi_setenv( &g.json.cgiCx,
278 FossilJsonKeys.commandPath,
279 pathSplit );
280 }else{ /* either CLI or server mode... */
281 cson_value * arV /* value to store path in */;
282 cson_array * ar /* the "real" array object */;
283 arV = cson_value_new_array();
284 ar = cson_value_get_array(arV);
285 cson_cgi_setenv( &g.json.cgiCx,
286 FossilJsonKeys.commandPath,
287 arV );
288 if( zPath ){
289 /* Translate fossil's PATH_INFO into cson_cgi for later
290 convenience, to help consolidate how we handle CGI/server
291 modes. This block is hit when running in plain server mode.
292 */
293 char const * p = zPath /* current byte */;
294 char const * head = p /* current start-of-token */;
295 unsigned int len = 0 /* current token's lengh */;
296 assert( g.isCGI && "g.isCGI should have been set by now." );
297 for( ;*p!='?'; ++p){
298 if( !*p || ('/' == *p) ){
299 if( len ){
300 cson_value * part;
301 assert( head != p );
302 part = cson_value_new_string(head, len);
303 cson_array_append( ar, part );
304 len = 0;
305 }
306 if( !*p ){
307 break;
308 }
309 head = p+1;
310 continue;
311 }
312 ++len;
313 }
314 }else{
315 /* assume CLI mode */
316 int i;
317 char const * arg;
318 cson_value * part;
319 assert( (!g.isCGI) && "g.isCGI set and we do not expect that to be the case here." );
320 for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
321 arg = g.argv[i];
322 if( !arg || !*arg ){
323 continue;
324 }
325 part = cson_value_new_string(arg,strlen(arg));
326 cson_array_append(ar, part);
327 }
328 }
329 }
330 /* g.json.reqPayload exists only to simplify some of our access to
331 the request payload. We currently only use this in the context of
332 Object payloads, not Arrays, strings, etc. */
333 g.json.reqPayload.v = cson_cgi_getenv( &g.json.cgiCx, "p", "payload" );
334 if( g.json.reqPayload.v ){
335 g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v )
336 /* g.json.reqPayload.o may legally be NULL, which means only that
337 g.json.reqPayload.v is-not-a Object.
338 */;
@@ -359,46 +432,43 @@
359 ** abbreviated name because we rely on the full name "json" here. The
360 ** g object probably has the short form which we can use for our
361 ** purposes, but i haven't yet looked for it.
362 */
363 static char const * json_command_arg(unsigned char ndx){
364 cson_array * ar = cson_value_get_array(
365 cson_cgi_getenv(&g.json.cgiCx,
366 "a",
367 FossilJsonKeys.commandPath));
368 assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?");
369 if( g.json.cmdOffset < 0 ){
370 /* first-time setup. */
371 short i = 0;
372 #define NEXT cson_string_cstr( \
373 cson_value_get_string( \
374 cson_array_get(ar,i) \
375 ))
376 char const * tok = NEXT;
377 while( tok ){
378 if( 0==strncmp("json",tok,4) ){
379 g.json.cmdOffset = i;
380 break;
381 }
382 ++i;
383 tok = NEXT;
384 }
385 }
386 #undef NEXT
387 if( g.json.cmdOffset < 0 ){
388 return NULL;
389 }else{
390 ndx = g.json.cmdOffset + ndx;
391 return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmdOffset + ndx )));
392 }
393 }
394
395 /*
396 ** If g.json.reqPayload.o is NULL then NULL is returned, else the
397 ** given property is searched for in the request payload. If found it
398 ** is returned. The returned value is owned by (or shares ownership
399 ** with) g.json.cgiCx, and must NOT be cson_value_free()'d by the
400 ** caller.
401 */
402 static cson_value * json_payload_property( char const * key ){
403 return g.json.reqPayload.o ?
404 cson_object_get( g.json.reqPayload.o, key )
@@ -426,12 +496,14 @@
426 ** Alternately, we can create different JsonPageDef arrays for each
427 ** subset.
428 */
429 char const * name;
430 /*
431 ** Returns a payload object for the response.
432 ** If it returns a non-NULL value, the caller owns it.
 
 
433 */
434 cson_value * (*func)();
435 /*
436 ** Which mode(s) of execution does func() support:
437 **
@@ -486,10 +558,11 @@
486 #if 0
487 static unsigned int json_timestamp(){
488
489 }
490 #endif
 
491
492 /*
493 ** Creates a new Fossil/JSON response envelope skeleton. It is owned
494 ** by the caller, who must eventually free it using cson_value_free(),
495 ** or add it to a cson container to transfer ownership. Returns NULL
@@ -568,11 +641,11 @@
568 }
569 if( pMsg && *pMsg ){
570 tmp = cson_value_new_string(pMsg,strlen(pMsg));
571 SET("resultText");
572 }
573 tmp = cson_cgi_getenv(&g.json.cgiCx, "gp", "requestId");
574 if( tmp ) cson_object_set( o, "requestId", tmp );
575 if( NULL != payload ){
576 if( resultCode ){
577 cson_value_free(payload);
578 payload = NULL;
@@ -582,16 +655,12 @@
582 }
583 }
584 #undef SET
585
586 if(0){/*Only for debuggering, add some info to the response.*/
587 tmp = cson_cgi_env_get_val(&g.json.cgiCx,'a', 0);
588 if(tmp){
589 cson_object_set( o, "$APP", tmp );
590 }
591 tmp = cson_value_new_integer( g.json.cmdOffset );
592 cson_object_set( o, "cmdOffset", tmp );
593 cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
594 }
595
596 goto ok;
597 cleanup:
@@ -665,25 +734,20 @@
665 cson_object_set( jobj, "resultCodeParanoiaLevel",
666 cson_value_new_integer(g.json.errorDetailParanoia) );
667 return jval;
668 }
669
670 #if 0
671 /* we have a disconnect here between fossil's server-mode QUERY_STRING
672 handling and cson_cgi's.
673 */
674 static cson_value * json_getenv( char const *zWhichEnv, char const * zKey ){
675 return cson_cgi_getenv(&g.json.cgiCx, zWhichEnv, zKey);
676 }
677 static char const * json_getenv_cstr( char const *zWhichEnv, char const * zKey ){
678 char const * rc = cson_value_get_cstr( json_getenv(zWhichEnv, zKey) );
679 if( !rc && zWhichEnv && (NULL!=strstr(zWhichEnv,"g")) ){
680 rc = PD(zKey,NULL);
681 }
682 return rc;
683 }
684 #endif
685
686 /*
687 ** Implementation for /json/cap
688 **
689 ** Returned object contains details about the "capabilities" of the
@@ -853,13 +917,11 @@
853 ;
854 if(!token){
855 g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
856 }else{
857 login_clear_login_data();
858 g.json.authToken = NULL /* memory is owned by g.json.cgiCx, but
859 now lives only in the cson_cgi garbage
860 collector.*/;
861 }
862 return NULL;
863 }
864
865 /*
@@ -1006,11 +1068,10 @@
1006 Blob buf = empty_blob;
1007 char const * cmd;
1008 cson_value * payload = NULL;
1009 cson_value * root = NULL;
1010 JsonPageDef const * pageDef = NULL;
1011 cgi_set_content_type( cson_cgi_guess_content_type(&g.json.cgiCx) );
1012 json_mode_bootstrap();
1013 cmd = json_command_arg(1);
1014 /*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/
1015 pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
1016 if( ! pageDef ){
@@ -1055,20 +1116,20 @@
1055 void json_cmd_top(void){
1056 char const * cmd = NULL;
1057 int rc = 1002;
1058 cson_value * payload = NULL;
1059 JsonPageDef const * pageDef;
 
1060 json_mode_bootstrap();
1061 if( g.argc<3 ){
1062 goto usage;
1063 }
1064 db_find_and_open_repository(0, 0);
1065 cmd = json_command_arg(1);
1066 if( !cmd || !*cmd ){
1067 goto usage;
1068 }
1069 cgi_set_content_type( cson_cgi_guess_content_type(&g.json.cgiCx) );
1070 pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
1071 if( ! pageDef ){
1072 json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
1073 return;
1074 }else if( pageDef->runMode > 0 /*HTTP only*/){
1075
--- src/json.c
+++ src/json.c
@@ -20,16 +20,10 @@
20 ** For notes regarding the public JSON interface, please see:
21 **
22 ** https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit
23 **
24 **
 
 
 
 
 
 
25 */
26 #include "config.h"
27 #include "VERSION.h"
28 #include "json.h"
29 #include <assert.h>
@@ -156,24 +150,91 @@
150 static char buf[BufSize] = {'F','O','S','S','I','L','-',0};
151 assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code.");
152 sprintf(buf+7,"%04d", code);
153 return buf;
154 }
155
156 /*
157 ** Adds v to the API-internal cleanup mechanism. key must be a unique
158 ** key for the given element. Adding another item with that key may
159 ** free the previous one. If freeOnError is true then v is passed to
160 ** cson_value_free() if the key cannot be inserted, otherweise
161 ** ownership of v is not changed on error.
162 **
163 ** Returns 0 on success.
164 **
165 *** On success, ownership of v is transfered to (or shared with)
166 *** g.json.gc, and v will be valid until that object is cleaned up or
167 *** its key is replaced via another call to this function.
168 */
169 int json_gc_add( char const * key, cson_value * v, char freeOnError ){
170 int const rc = cson_object_set( g.json.gc.o, key, v );
171 assert( NULL != g.json.gc.o );
172 if( (0 != rc) && freeOnError ){
173 cson_value_free( v );
174 }
175 return rc;
176 }
177
178
179 /*
180 ** Returns the value of json_rc_cstr(code) as a new JSON
181 ** string, which is owned by the caller and must eventually
182 ** be cson_value_free()d or transfered to a JSON container.
183 */
184 cson_value * json_rc_string( int code ){
185 return cson_value_new_string( json_rc_cstr(code), 11 );
186 }
187
188
189 /*
190 ** Gets a POST/GET/COOKIE value. The returned memory is owned by the
191 ** g.json object (one of its sub-objects). Returns NULL if no match is
192 ** found.
193 **
194 ** Precedence: GET, COOKIE, POST. COOKIE _should_ be last
195 ** but currently is not for internal order-of-init reasons.
196 ** Since fossil only uses one cookie, this is not a high-prio
197 ** problem.
198 */
199 cson_value * json_getenv( char const * zKey ){
200 cson_value * rc;
201 rc = cson_object_get( g.json.param.o, zKey );
202 if( rc ){
203 return rc;
204 }else{
205 rc = cson_object_get( g.json.post.o, zKey );
206 if(rc){
207 return rc;
208 }else{
209 char const * cv = PD(zKey,NULL);
210 if(cv){/*transform it to JSON for later use.*/
211 rc = cson_value_new_string(cv,strlen(cv));
212 cson_object_set( g.json.param.o, zKey, rc );
213 return rc;
214 }
215 }
216 }
217 return NULL;
218 }
219
220 /*
221 ** Adds v to g.json.param.o using the given key. May cause
222 ** any prior item with that key to be destroyed (depends on
223 ** current reference count for that value).
224 ** On succes, transfers ownership of v to g.json.param.o.
225 ** On error ownership of v is not modified.
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.
236 **
237 ** If an auth token is found in the GET/POST JSON request data then
238 ** fossil is given that data for use in authentication for this
239 ** session.
240 **
@@ -186,52 +247,72 @@
247 */
248 static cson_value * json_auth_token(){
249 if( !g.json.authToken ){
250 /* Try to get an authorization token from GET parameter, POSTed
251 JSON, or fossil cookie (in that order). */
252 g.json.authToken = json_getenv(FossilJsonKeys.authToken);
253 if(g.json.authToken
254 && cson_value_is_string(g.json.authToken)
255 && !PD(login_cookie_name(),NULL)){
 
 
 
 
 
 
256 /* tell fossil to use this login info.
257
258 FIXME?: because the JSON bits don't carry around
259 login_cookie_name(), there is a potential login hijacking
260 window here. We may need to change the JSON auth token to be
261 in the form: login_cookie_name()=...
262
263 Then again, the hardened cookie value helps ensure that
264 only a proper key/value match is valid.
265 */
266 cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) );
267 }else if( g.isCGI ){
268 /* try fossil's conventional cookie. */
269 /* Reminder: chicken/egg scenario regarding db access in CLI
270 mode because login_cookie_name() needs the db. CLI
271 mode does not use any authentication, so we don't need
272 to support it here.
273 */
274 char const * zCookie = P(login_cookie_name());
275 if( zCookie && *zCookie ){
276 /* Transfer fossil's cookie to JSON for downstream convenience... */
277 cson_value * v = cson_value_new_string(zCookie, strlen(zCookie));
278 json_setenv( FossilJsonKeys.authToken, v );
279 g.json.authToken = v;
 
280 }
281 }
282 }
283 return g.json.authToken;
284 }
285
286 /*
287 ** Initializes some JSON bits which need to be initialized relatively
288 ** early on. It should only be called from cgi_init() or
289 ** json_cmd_top() (early on in those functions).
290 **
291 ** Initializes g.json.gc and g.json.param.
292 */
293 void json_main_bootstrap(){
294 cson_value * v;
295 assert( (NULL == g.json.gc.v) && "cgi_json_bootstrap() was called twice!" );
296 v = cson_value_new_object();
297 g.json.gc.v = v;
298 g.json.gc.o = cson_value_get_object(v);
299
300 v = cson_value_new_object();
301 g.json.param.v = v;
302 g.json.param.o = cson_value_get_object(v);
303 json_gc_add("$PARAMS", v, 1);
304 }
305
306
307 /*
308 ** Performs some common initialization of JSON-related state. Must be
309 ** called by the json_page_top() and json_cmd_top() dispatching
310 ** functions to set up the JSON stat used by the dispatched functions.
311 **
312 ** Implicitly sets up the login information state in CGI mode, but
313 ** does not perform any permissions checking. It _might_ (haven't
314 ** tested this) die with an error if an auth cookie is malformed.
315 **
316 ** This must be called by the top-level JSON command dispatching code
317 ** before they do any work.
318 **
@@ -238,101 +319,93 @@
319 ** This must only be called once, or an assertion may be triggered.
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;
331 }
332 g.json.isJsonMode = 1;
333 g.json.resultCode = 0;
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
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 );
371 zPart = (char*)malloc(len+1);
372 assert( zPart != NULL );
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 }
384 head = p+1;
385 continue;
386 }
387 ++len;
388 }
389 }else{/* assume CLI mode */
390 int i;
391 char const * arg;
392 cson_value * part;
393 for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){
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
405 Object payloads, not Arrays, strings, etc. */
406 g.json.reqPayload.v = cson_object_get( g.json.post.o, "payload" );
407 if( g.json.reqPayload.v ){
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 */;
@@ -359,46 +432,43 @@
432 ** abbreviated name because we rely on the full name "json" here. The
433 ** g object probably has the short form which we can use for our
434 ** purposes, but i haven't yet looked for it.
435 */
436 static char const * json_command_arg(unsigned char ndx){
437 cson_array * ar = g.json.cmd.a;
 
 
 
438 assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?");
439 if( g.json.cmd.offset < 0 ){
440 /* first-time setup. */
441 short i = 0;
442 #define NEXT cson_string_cstr( \
443 cson_value_get_string( \
444 cson_array_get(ar,i) \
445 ))
446 char const * tok = NEXT;
447 while( tok ){
448 if( 0==strncmp("json",tok,4) ){
449 g.json.cmd.offset = i;
450 break;
451 }
452 ++i;
453 tok = NEXT;
454 }
455 }
456 #undef NEXT
457 if(g.json.cmd.offset < 0){
458 return NULL;
459 }else{
460 ndx = g.json.cmd.offset + ndx;
461 return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx )));
462 }
463 }
464
465 /*
466 ** If g.json.reqPayload.o is NULL then NULL is returned, else the
467 ** given property is searched for in the request payload. If found it
468 ** is returned. The returned value is owned by (or shares ownership
469 ** with) g.json, and must NOT be cson_value_free()'d by the
470 ** caller.
471 */
472 static cson_value * json_payload_property( char const * key ){
473 return g.json.reqPayload.o ?
474 cson_object_get( g.json.reqPayload.o, key )
@@ -426,12 +496,14 @@
496 ** Alternately, we can create different JsonPageDef arrays for each
497 ** subset.
498 */
499 char const * name;
500 /*
501 ** Returns a payload object for the response. If it returns a
502 ** non-NULL value, the caller owns it. To trigger an error this
503 ** function should set g.json.resultCode to a value from the
504 ** FossilJsonCodes enum.
505 */
506 cson_value * (*func)();
507 /*
508 ** Which mode(s) of execution does func() support:
509 **
@@ -486,10 +558,11 @@
558 #if 0
559 static unsigned int json_timestamp(){
560
561 }
562 #endif
563
564
565 /*
566 ** Creates a new Fossil/JSON response envelope skeleton. It is owned
567 ** by the caller, who must eventually free it using cson_value_free(),
568 ** or add it to a cson container to transfer ownership. Returns NULL
@@ -568,11 +641,11 @@
641 }
642 if( pMsg && *pMsg ){
643 tmp = cson_value_new_string(pMsg,strlen(pMsg));
644 SET("resultText");
645 }
646 tmp = json_getenv("requestId");
647 if( tmp ) cson_object_set( o, "requestId", tmp );
648 if( NULL != payload ){
649 if( resultCode ){
650 cson_value_free(payload);
651 payload = NULL;
@@ -582,16 +655,12 @@
655 }
656 }
657 #undef SET
658
659 if(0){/*Only for debuggering, add some info to the response.*/
660 tmp = cson_value_new_integer( g.json.cmd.offset );
661 cson_object_set( o, "cmd.offset", tmp );
 
 
 
 
662 cson_object_set( o, "isCGI", cson_value_new_bool( g.isCGI ) );
663 }
664
665 goto ok;
666 cleanup:
@@ -665,25 +734,20 @@
734 cson_object_set( jobj, "resultCodeParanoiaLevel",
735 cson_value_new_integer(g.json.errorDetailParanoia) );
736 return jval;
737 }
738
739 /*
740 ** Returns the string form of a json_getenv() value, but ONLY
741 ** If that value is-a String. Non-strings are not converted
742 ** to strings for this purpose. Returned memory is owned by
743 ** g.json or fossil..
744 */
745 static char const * json_getenv_cstr( char const * zKey ){
746 return cson_value_get_cstr( json_getenv(zKey) );
747 }
748
 
 
 
 
 
749
750 /*
751 ** Implementation for /json/cap
752 **
753 ** Returned object contains details about the "capabilities" of the
@@ -853,13 +917,11 @@
917 ;
918 if(!token){
919 g.json.resultCode = FSL_JSON_E_MISSING_AUTH;
920 }else{
921 login_clear_login_data();
922 g.json.authToken = NULL /* memory is owned elsewhere.*/;
 
 
923 }
924 return NULL;
925 }
926
927 /*
@@ -1006,11 +1068,10 @@
1068 Blob buf = empty_blob;
1069 char const * cmd;
1070 cson_value * payload = NULL;
1071 cson_value * root = NULL;
1072 JsonPageDef const * pageDef = NULL;
 
1073 json_mode_bootstrap();
1074 cmd = json_command_arg(1);
1075 /*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/
1076 pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
1077 if( ! pageDef ){
@@ -1055,20 +1116,20 @@
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 db_find_and_open_repository(0, 0);
1127 cmd = json_command_arg(1);
1128 if( !cmd || !*cmd ){
1129 goto usage;
1130 }
 
1131 pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
1132 if( ! pageDef ){
1133 json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
1134 return;
1135 }else if( pageDef->runMode > 0 /*HTTP only*/){
1136
+32 -39
--- src/main.c
+++ src/main.c
@@ -169,25 +169,43 @@
169169
int anAuxCols[MX_AUX]; /* Number of columns for option() values */
170170
171171
int allowSymlinks; /* Cached "allow-symlinks" option */
172172
173173
struct FossilJsonBits {
174
- int isJsonMode; /* True if running in JSON mode, else false. This changes
175
- how errors are reported. In JSON mode we try to always
176
- output JSON-form error responses.
174
+ int isJsonMode; /* True if running in JSON mode, else
175
+ false. This changes how errors are
176
+ reported. In JSON mode we try to
177
+ always output JSON-form error
178
+ responses and always exit() with
179
+ code 0 to avoid an HTTP 500 error.
177180
*/
178
- int cmdOffset; /* Tells us which PATH_INFO/CLI args
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. */
190
+ cson_value * v;
191
+ cson_array * a;
192
+ int offset; /* Tells us which PATH_INFO/CLI args
179193
part holds the "json" command, so
180194
that we can account for sub-repos
181195
and path prefixes. This is handled
182196
differently for CLI and CGI modes.
183197
*/
184
- int resultCode; /* used for passing back specific codes from /json callbacks. */
185
- int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
186
- cson_cgi_cx cgiCx; /* cson_cgi context */
187
- cson_output_opt outOpt; /* formatting options for JSON mode. */
188
- cson_value * authToken; /* authentication token */
198
+ } cmd;
199
+ struct { /* JSON POST data. */
200
+ cson_value * v;
201
+ cson_object * o;
202
+ } post;
203
+ struct { /* GET/COOKIE params in JSON form. */
204
+ cson_value * v;
205
+ cson_object * o;
206
+ } param;
189207
struct {
190208
cson_value * v;
191209
cson_object * o;
192210
} reqPayload; /* request payload object (if any) */
193211
} json;
@@ -259,11 +277,13 @@
259277
/*
260278
** atexit() handler which frees up "some" of the resources
261279
** used by fossil.
262280
*/
263281
void fossil_atexit() {
264
- cson_cgi_cx_clean(&g.json.cgiCx);
282
+
283
+ cson_value_free(g.json.gc.v);
284
+ memset(&g.json, 0, sizeof(g.json));
265285
if(g.db){
266286
db_close(0);
267287
}
268288
}
269289
@@ -287,11 +307,10 @@
287307
code is needed before the db is opened,
288308
so we can't sql for it.*/;
289309
#else
290310
g.json.errorDetailParanoia = 0;
291311
#endif
292
- g.json.cgiCx = cson_cgi_cx_empty;
293312
g.json.outOpt = cson_output_opt_empty;
294313
g.json.outOpt.addNewline = 1;
295314
g.json.outOpt.indentation = 1 /* FIXME: make configurable */;
296315
for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]);
297316
if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
@@ -345,42 +364,16 @@
345364
fossil_fatal("%s: ambiguous command prefix: %s\n"
346365
"%s: could be any of:%s\n"
347366
"%s: use \"help\" for more information\n",
348367
argv[0], zCmdName, argv[0], blob_str(&couldbe), argv[0]);
349368
}
350
- rc = cson_cgi_init(&g.json.cgiCx, g.argc, (char const * const *)g.argv, NULL)
351
- /* Reminder: cson_cgi_init() may process the POST data before
352
- fossil gets to, but it is configured to only read
353
- application/[json/javascript] and text/plain. form-urlencoded
354
- and x-fossil-* data will be consumed by fossil's cgi_init().
355
-
356
- Note that we set up the CGI bits even when not running in CGI
357
- mode because some of cson_cgi's facilities are useful in
358
- non-CGI contexts and we use those in the CLI variants of the
359
- JSON commands.
360
-
361
- FIXME: do some analysis of the request path (HTTP mode) or
362
- CLI args (CLI mode) and only call this if the command is
363
- a JSON-mode command. We can only do that easily from here
364
- if we use e.g. /json/foo instead of /foo.json, since we
365
- have a common prefix.
366
- */
367
- ;
368369
if(rc){
369370
fossil_fatal("%s: unrecoverable error while initializing JSON CGI bits: "
370371
"cson error code #%d (%s)\n",
371372
argv[0], rc, cson_rc_string(rc));
372
- }else{
373
- if( NULL != cson_cgi_env_get_obj( &g.json.cgiCx, 'p', 0 ) ){
374
- /* if cson_cgi read the POST data then we're certainly in JSON
375
- mode. If it didn't then we have to delay this decision until
376
- the JSON family of callbacks is called.
377
- */
378
- g.json.isJsonMode = 1;
379
- }
380
- atexit( fossil_atexit );
381
- }
373
+ }
374
+ atexit( fossil_atexit );
382375
aCommand[idx].xFunc();
383376
fossil_exit(0);
384377
/*NOT_REACHED*/
385378
return 0;
386379
}
387380
--- src/main.c
+++ src/main.c
@@ -169,25 +169,43 @@
169 int anAuxCols[MX_AUX]; /* Number of columns for option() values */
170
171 int allowSymlinks; /* Cached "allow-symlinks" option */
172
173 struct FossilJsonBits {
174 int isJsonMode; /* True if running in JSON mode, else false. This changes
175 how errors are reported. In JSON mode we try to always
176 output JSON-form error responses.
 
 
 
177 */
178 int cmdOffset; /* Tells us which PATH_INFO/CLI args
 
 
 
 
 
 
 
 
 
 
 
179 part holds the "json" command, so
180 that we can account for sub-repos
181 and path prefixes. This is handled
182 differently for CLI and CGI modes.
183 */
184 int resultCode; /* used for passing back specific codes from /json callbacks. */
185 int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */
186 cson_cgi_cx cgiCx; /* cson_cgi context */
187 cson_output_opt outOpt; /* formatting options for JSON mode. */
188 cson_value * authToken; /* authentication token */
 
 
 
 
189 struct {
190 cson_value * v;
191 cson_object * o;
192 } reqPayload; /* request payload object (if any) */
193 } json;
@@ -259,11 +277,13 @@
259 /*
260 ** atexit() handler which frees up "some" of the resources
261 ** used by fossil.
262 */
263 void fossil_atexit() {
264 cson_cgi_cx_clean(&g.json.cgiCx);
 
 
265 if(g.db){
266 db_close(0);
267 }
268 }
269
@@ -287,11 +307,10 @@
287 code is needed before the db is opened,
288 so we can't sql for it.*/;
289 #else
290 g.json.errorDetailParanoia = 0;
291 #endif
292 g.json.cgiCx = cson_cgi_cx_empty;
293 g.json.outOpt = cson_output_opt_empty;
294 g.json.outOpt.addNewline = 1;
295 g.json.outOpt.indentation = 1 /* FIXME: make configurable */;
296 for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]);
297 if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){
@@ -345,42 +364,16 @@
345 fossil_fatal("%s: ambiguous command prefix: %s\n"
346 "%s: could be any of:%s\n"
347 "%s: use \"help\" for more information\n",
348 argv[0], zCmdName, argv[0], blob_str(&couldbe), argv[0]);
349 }
350 rc = cson_cgi_init(&g.json.cgiCx, g.argc, (char const * const *)g.argv, NULL)
351 /* Reminder: cson_cgi_init() may process the POST data before
352 fossil gets to, but it is configured to only read
353 application/[json/javascript] and text/plain. form-urlencoded
354 and x-fossil-* data will be consumed by fossil's cgi_init().
355
356 Note that we set up the CGI bits even when not running in CGI
357 mode because some of cson_cgi's facilities are useful in
358 non-CGI contexts and we use those in the CLI variants of the
359 JSON commands.
360
361 FIXME: do some analysis of the request path (HTTP mode) or
362 CLI args (CLI mode) and only call this if the command is
363 a JSON-mode command. We can only do that easily from here
364 if we use e.g. /json/foo instead of /foo.json, since we
365 have a common prefix.
366 */
367 ;
368 if(rc){
369 fossil_fatal("%s: unrecoverable error while initializing JSON CGI bits: "
370 "cson error code #%d (%s)\n",
371 argv[0], rc, cson_rc_string(rc));
372 }else{
373 if( NULL != cson_cgi_env_get_obj( &g.json.cgiCx, 'p', 0 ) ){
374 /* if cson_cgi read the POST data then we're certainly in JSON
375 mode. If it didn't then we have to delay this decision until
376 the JSON family of callbacks is called.
377 */
378 g.json.isJsonMode = 1;
379 }
380 atexit( fossil_atexit );
381 }
382 aCommand[idx].xFunc();
383 fossil_exit(0);
384 /*NOT_REACHED*/
385 return 0;
386 }
387
--- src/main.c
+++ src/main.c
@@ -169,25 +169,43 @@
169 int anAuxCols[MX_AUX]; /* Number of columns for option() values */
170
171 int allowSymlinks; /* Cached "allow-symlinks" option */
172
173 struct FossilJsonBits {
174 int isJsonMode; /* True if running in JSON mode, else
175 false. This changes how errors are
176 reported. In JSON mode we try to
177 always output JSON-form error
178 responses and always exit() with
179 code 0 to avoid an HTTP 500 error.
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. */
190 cson_value * v;
191 cson_array * a;
192 int offset; /* Tells us which PATH_INFO/CLI args
193 part holds the "json" command, so
194 that we can account for sub-repos
195 and path prefixes. This is handled
196 differently for CLI and CGI modes.
197 */
198 } cmd;
199 struct { /* JSON POST data. */
200 cson_value * v;
201 cson_object * o;
202 } post;
203 struct { /* GET/COOKIE params in JSON form. */
204 cson_value * v;
205 cson_object * o;
206 } param;
207 struct {
208 cson_value * v;
209 cson_object * o;
210 } reqPayload; /* request payload object (if any) */
211 } json;
@@ -259,11 +277,13 @@
277 /*
278 ** atexit() handler which frees up "some" of the resources
279 ** used by fossil.
280 */
281 void fossil_atexit() {
282
283 cson_value_free(g.json.gc.v);
284 memset(&g.json, 0, sizeof(g.json));
285 if(g.db){
286 db_close(0);
287 }
288 }
289
@@ -287,11 +307,10 @@
307 code is needed before the db is opened,
308 so we can't sql for it.*/;
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)){
@@ -345,42 +364,16 @@
364 fossil_fatal("%s: ambiguous command prefix: %s\n"
365 "%s: could be any of:%s\n"
366 "%s: use \"help\" for more information\n",
367 argv[0], zCmdName, argv[0], blob_str(&couldbe), argv[0]);
368 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369 if(rc){
370 fossil_fatal("%s: unrecoverable error while initializing JSON CGI bits: "
371 "cson error code #%d (%s)\n",
372 argv[0], rc, cson_rc_string(rc));
373 }
374 atexit( fossil_atexit );
 
 
 
 
 
 
 
 
375 aCommand[idx].xFunc();
376 fossil_exit(0);
377 /*NOT_REACHED*/
378 return 0;
379 }
380

Keyboard Shortcuts

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