Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | More cleanups to the cson_cgi removal refactoring. Added common "indent" parameter to control indentation of JSON (uses cson_output_opt.indentation semantics). |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | json |
Files: | files | file ages | folders |
SHA1: |
b3653265d1552d8392c93088ac9f9efd |
User & Date: | stephan 2011-09-18 05:45:43.989 |
Context
2011-09-18
| ||
05:51 | merged trunk [b54b8e751a]. ... (check-in: 76c4ae5e user: stephan tags: json) | |
05:45 | More cleanups to the cson_cgi removal refactoring. Added common "indent" parameter to control indentation of JSON (uses cson_output_opt.indentation semantics). ... (check-in: b3653265 user: stephan tags: json) | |
04:31 | 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. ... (check-in: 4cf96814 user: stephan tags: json) | |
Changes
Changes to src/cgi.c.
︙ | ︙ | |||
721 722 723 724 725 726 727 | return 0; } } } } /* | | | > | | < | | | < < < < | | < | > | > | > > > > > | 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 | return 0; } } } } /* ** Reads a JSON object from the first contentLen bytes of zIn. On ** g.json.post is updated to hold the content. On error a ** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit(0) is ** called. */ static void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){ cson_value * jv = NULL; int rc; CgiPostReadState state; assert( 0 != contentLen ); state.fh = zIn; state.len = contentLen; state.pos = 0; rc = cson_parse( &jv, cson_data_source_FILE_n, &state, NULL, NULL ); if(rc){ goto invalidRequest; }else{ json_gc_add( "POST.JSON", jv, 1 ); g.json.post.v = jv; g.json.post.o = cson_value_get_object( jv ); if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */ goto invalidRequest; } } return; invalidRequest: cgi_set_content_type(json_guess_content_type()); json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 ); fossil_exit(0); } /* ** Initialize the query parameter database. Information is pulled from ** the QUERY_STRING environment variable (if it exists), from standard ** input if there is POST data, and from HTTP_COOKIE. */ void cgi_init(void){ char *z; const char *zType; int len; json_main_bootstrap(); g.isCGI = 1; cgi_destination(CGI_BODY); z = (char*)P("HTTP_COOKIE"); if( z ){ z = mprintf("%s",z); add_param_list(z, ';'); } |
︙ | ︙ | |||
807 808 809 810 811 812 813 | }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ blob_read_from_channel(&g.cgiIn, g.httpIn, len); }else if( fossil_strcmp(zType, "application/json") || fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/ || fossil_strcmp(zType,"application/javascript")){ g.json.isJsonMode = 1; cgi_parse_POST_JSON(g.httpIn, (unsigned int)len); | < | | > > > | > > > | > > > > > > | 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 | }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ blob_read_from_channel(&g.cgiIn, g.httpIn, len); }else if( fossil_strcmp(zType, "application/json") || fossil_strcmp(zType,"text/plain")/*assume this MIGHT be JSON*/ || fossil_strcmp(zType,"application/javascript")){ g.json.isJsonMode = 1; cgi_parse_POST_JSON(g.httpIn, (unsigned int)len); /* FIXMEs: - See if fossil really needs g.cgiIn to be set for this purpose (i don't think it does). If it does then fill g.cgiIn and refactor to parse the JSON from there. - After parsing POST JSON, copy the "first layer" of keys/values to cgi_setenv(), honoring the upper-case distinction used in add_param_list(). However... - If we do that then we might get a disconnect in precedence of GET/POST arguments. i prefer for GET entries to take precedence over like-named POST entries, but in order for that to happen we need to process QUERY_STRING _after_ reading the POST data. */ cgi_set_content_type(json_guess_content_type()); } } } /* ** This is the comparison function used to sort the aParamQP[] array of |
︙ | ︙ |
Changes to src/json.c.
︙ | ︙ | |||
224 225 226 227 228 229 230 231 232 233 234 235 236 237 | ** On succes, transfers ownership of v to g.json.param.o. ** On error ownership of v is not modified. */ int json_setenv( char const * zKey, cson_value * v ){ return cson_object_set( g.json.param.o, zKey, v ); } /* ** Returns the current request's JSON authentication token, or NULL if ** none is found. The token's memory is owned by (or shared with) ** g.json. ** ** If an auth token is found in the GET/POST JSON request data then | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 | ** On succes, transfers ownership of v to g.json.param.o. ** On error ownership of v is not modified. */ int json_setenv( char const * zKey, cson_value * v ){ return cson_object_set( g.json.param.o, zKey, v ); } /* ** Guesses a RESPONSE Content-Type value based (primarily) on the ** HTTP_ACCEPT header. ** ** It will try to figure out if the client can support ** application/json or application/javascript, and will fall back to ** text/plain if it cannot figure out anything more specific. ** ** Returned memory is static and immutable. ** */ char const * json_guess_content_type(){ char const * cset; char doUtf8; cset = PD("HTTP_ACCEPT_CHARSET",NULL); doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset))) ? 1 : 0; if( g.json.jsonp ){ return doUtf8 ? "application/javascript; charset=utf-8" : "application/javascript"; }else{ /* Content-type If the browser does not sent an ACCEPT for application/json then we fall back to text/plain. */ char const * cstr; cstr = PD("HTTP_ACCEPT",NULL); if( NULL == cstr ){ return doUtf8 ? "application/json; charset=utf-8" : "application/json"; }else{ if( strstr( cstr, "application/json" ) || strstr( cstr, "*/*" ) ){ return doUtf8 ? "application/json; charset=utf-8" : "application/json"; }else{ return "text/plain"; } } } } /* ** Returns the current request's JSON authentication token, or NULL if ** none is found. The token's memory is owned by (or shared with) ** g.json. ** ** If an auth token is found in the GET/POST JSON request data then |
︙ | ︙ | |||
318 319 320 321 322 323 324 | ** ** This must only be called once, or an assertion may be triggered. */ static void json_mode_bootstrap(){ static char once = 0 /* guard against multiple runs */; char const * zPath = P("PATH_INFO"); cson_value * pathSplit = NULL; | < > > > > > > > | | | < < < | | | > > > > > > > > > > > > > > > > > > > > > | 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 | ** ** This must only be called once, or an assertion may be triggered. */ static void json_mode_bootstrap(){ static char once = 0 /* guard against multiple runs */; char const * zPath = P("PATH_INFO"); cson_value * pathSplit = NULL; assert( (0==once) && "json_mode_bootstrap() called too many times!"); if( once ){ return; }else{ once = 1; } g.json.isJsonMode = 1; g.json.resultCode = 0; g.json.cmd.offset = -1; if( !g.isCGI && g.fullHttpReply ){ /* workaround for server mode, so we see it as CGI mode. */ g.isCGI = 1; } if(! g.json.post.v ){ /* If cgi_init() reads POSTed JSON then it sets the content type. If it did not then we need to set it. */ cgi_set_content_type(json_guess_content_type()); } #if defined(NDEBUG) /* avoids debug messages on stderr in JSON mode */ sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0); #endif g.json.cmd.v = cson_value_new_array(); g.json.cmd.a = cson_value_get_array(g.json.cmd.v); json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v, 1 ); /* The following if/else block translates the PATH_INFO path (in CLI/server modes) or g.argv (CLI mode) into an internal list so that we can simplify command dispatching later on. Note that translating g.argv this way is overkill but allows us to avoid CLI-only special-case handling in other code, e.g. json_command_arg(). */ if( zPath ){/* Either CGI or server mode... */ /* Translate PATH_INFO into JSON for later convenience. */ char const * p = zPath /* current byte */; char const * head = p /* current start-of-token */; unsigned int len = 0 /* current token's lengh */; assert( g.isCGI && "g.isCGI should have been set by now." ); for( ; ; ++p){ if( !*p || ('/' == *p) ){ if( len ){ cson_value * part; char * zPart; assert( head != p ); zPart = (char*)malloc(len+1); assert( zPart != NULL ); memcpy(zPart, head, len); zPart[len] = 0; dehttpize(zPart); part = cson_value_new_string(zPart, strlen(zPart)); free(zPart); cson_array_append( g.json.cmd.a, part ); len = 0; } if( !*p ){ break; } head = p+1; continue; } ++len; } }else{/* assume CLI mode */ int i; char const * arg; cson_value * part; for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){ arg = g.argv[i]; if( !arg || !*arg ){ continue; } part = cson_value_new_string(arg,strlen(arg)); cson_array_append(g.json.cmd.a, part); } } /* g.json.reqPayload exists only to simplify some of our access to the request payload. We currently only use this in the context of Object payloads, not Arrays, strings, etc. */ g.json.reqPayload.v = cson_object_get( g.json.post.o, "payload" ); if( g.json.reqPayload.v ){ g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v ) /* g.json.reqPayload.o may legally be NULL, which means only that g.json.reqPayload.v is-not-a Object. */; } do{/* set up JSON out formatting options. */ unsigned char indent = g.isCGI ? 0 : 1; cson_value const * indentV = json_getenv("indent"); if(indentV){ if(cson_value_is_string(indentV)){ int n = atoi(cson_string_cstr(cson_value_get_string(indentV))); indent = (n>0) ? (unsigned char)n : 0; }else if(cson_value_is_number(indentV)){ double n = cson_value_get_integer(indentV); indent = (n>0) ? (unsigned char)n : 0; } } g.json.outOpt.indentation = indent; g.json.outOpt.addNewline = g.isCGI ? 0 : 1; }while(0); json_auth_token()/* will copy our auth token, if any, to fossil's core. */; if( g.isCGI ){ login_check_credentials()/* populates g.perm */; } else{ db_find_and_open_repository(OPEN_ANY_SCHEMA,0); } |
︙ | ︙ | |||
928 929 930 931 932 933 934 | ** Implementation of the /json/stat page/command. ** */ cson_value * json_page_stat(void){ i64 t, fsize; int n, m; const char *zDb; | | < > > | < > | 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 | ** Implementation of the /json/stat page/command. ** */ cson_value * json_page_stat(void){ i64 t, fsize; int n, m; const char *zDb; enum { BufLen = 1000 }; char zBuf[BufLen]; cson_value * jv = NULL; cson_object * jo = NULL; cson_value * jv2 = NULL; cson_object * jo2 = NULL; login_check_credentials(); if( !g.perm.Read ){ g.json.resultCode = FSL_JSON_E_DENIED; return NULL; } #define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf))); jv = cson_value_new_object(); jo = cson_value_get_object(jv); sqlite3_snprintf(BufLen, zBuf, db_get("project-name","")); SETBUF(jo, "projectName"); |
︙ | ︙ | |||
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 | ** */ void json_cmd_top(void){ char const * cmd = NULL; int rc = 1002; cson_value * payload = NULL; JsonPageDef const * pageDef; json_main_bootstrap(); json_mode_bootstrap(); if( g.argc<3 ){ goto usage; } db_find_and_open_repository(0, 0); cmd = json_command_arg(1); | > > > > > > > > > | 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 | ** */ void json_cmd_top(void){ char const * cmd = NULL; int rc = 1002; cson_value * payload = NULL; JsonPageDef const * pageDef; memset( &g.perm, 0xff, sizeof(g.perm) ) /* In CLI mode fossil does not use permissions and they all default to false. We enable them here because (A) fossil doesn't use them in local mode but (B) having them set gives us one less difference in the CLI/CGI/Server-mode JSON handling. */ ; json_main_bootstrap(); json_mode_bootstrap(); if( g.argc<3 ){ goto usage; } db_find_and_open_repository(0, 0); cmd = json_command_arg(1); |
︙ | ︙ |
Changes to src/login.c.
︙ | ︙ | |||
949 950 951 952 953 954 955 | } /* ** Call this routine when the credential check fails. It causes ** a redirect to the "login" page. */ void login_needed(void){ | > > > > | | | | > | 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 | } /* ** Call this routine when the credential check fails. It causes ** a redirect to the "login" page. */ void login_needed(void){ if(g.json.isJsonMode){ json_err( FSL_JSON_E_DENIED, NULL, 1 ); fossil_exit(0); }else{ const char *zUrl = PD("REQUEST_URI", "index"); cgi_redirect(mprintf("login?g=%T", zUrl)); /* NOTREACHED */ assert(0); } } /* ** Call this routine if the user lacks okHistory permission. If ** the anonymous user has okHistory permission, then paint a mesage ** to inform the user that much more information is available by ** logging in as anonymous. |
︙ | ︙ |
Changes to src/main.c.
︙ | ︙ | |||
178 179 180 181 182 183 184 185 186 187 188 189 190 191 | responses and always exit() with code 0 to avoid an HTTP 500 error. */ int resultCode; /* used for passing back specific codes from /json callbacks. */ int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */ cson_output_opt outOpt; /* formatting options for JSON mode. */ cson_value * authToken; /* authentication token */ struct { /* "garbage collector" */ cson_value * v; cson_object * o; } gc; struct { /* JSON POST data. */ cson_value * v; cson_array * a; | > | 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | responses and always exit() with code 0 to avoid an HTTP 500 error. */ int resultCode; /* used for passing back specific codes from /json callbacks. */ int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */ cson_output_opt outOpt; /* formatting options for JSON mode. */ cson_value * authToken; /* authentication token */ char const * jsonp; /* Name of JSONP function wrapper. */ struct { /* "garbage collector" */ cson_value * v; cson_object * o; } gc; struct { /* JSON POST data. */ cson_value * v; cson_array * a; |
︙ | ︙ | |||
307 308 309 310 311 312 313 | code is needed before the db is opened, so we can't sql for it.*/; #else g.json.errorDetailParanoia = 0; #endif g.json.outOpt = cson_output_opt_empty; g.json.outOpt.addNewline = 1; | | | 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | code is needed before the db is opened, so we can't sql for it.*/; #else g.json.errorDetailParanoia = 0; #endif g.json.outOpt = cson_output_opt_empty; g.json.outOpt.addNewline = 1; g.json.outOpt.indentation = 1 /* in CGI/server mode this can be configured */; for(i=0; i<argc; i++) g.argv[i] = fossil_mbcs_to_utf8(argv[i]); if( getenv("GATEWAY_INTERFACE")!=0 && !find_option("nocgi", 0, 0)){ zCmdName = "cgi"; g.isCGI = 1; }else if( argc<2 ){ fossil_fatal("Usage: %s COMMAND ...\n" "\"%s help\" for a list of available commands\n" |
︙ | ︙ |