Fossil SCM
more cleanups in the json arg/path handling.
Commit
35b9edba835d0adf255af2069f99cba414bf62c1
Parent
2dcc2397b580261…
1 file changed
+63
-37
+63
-37
| --- src/json.c | ||
| +++ src/json.c | ||
| @@ -219,12 +219,13 @@ | ||
| 219 | 219 | if( !g.json.authToken ){ |
| 220 | 220 | /* Try to get an authorization token from GET parameter, POSTed |
| 221 | 221 | JSON, or fossil cookie (in that order). */ |
| 222 | 222 | g.json.authToken = cson_cgi_getenv(&g.json.cgiCx, "gp", FossilJsonKeys.authToken) |
| 223 | 223 | /* reminder to self: cson_cgi does not have access to the cookies |
| 224 | - because fossil's core consumes them. Thus we cannot use "gpc" | |
| 225 | - here. | |
| 224 | + because fossil's core consumes them. Thus we cannot use "agpc" | |
| 225 | + here. We use "a" (App-specific) as a place to store fossil's | |
| 226 | + cookie value. | |
| 226 | 227 | */; |
| 227 | 228 | if( g.json.authToken){ |
| 228 | 229 | /* tell fossil to use this login info. |
| 229 | 230 | |
| 230 | 231 | FIXME: because the JSON bits don't carry around login_cookie_name(), |
| @@ -250,26 +251,34 @@ | ||
| 250 | 251 | return g.json.authToken; |
| 251 | 252 | } |
| 252 | 253 | |
| 253 | 254 | /* |
| 254 | 255 | ** Performs some common initialization of JSON-related state. |
| 255 | -** Implicitly sets up the login information state in CGI mode, | |
| 256 | -** but does not perform any authentication here. It _might_ | |
| 257 | -** (haven't tested this) die with an error if an auth cookie | |
| 258 | -** is malformed. | |
| 256 | +** Implicitly sets up the login information state in CGI mode, but | |
| 257 | +** does not perform any authentication here. It _might_ (haven't | |
| 258 | +** tested this) die with an error if an auth cookie is malformed. | |
| 259 | +** | |
| 260 | +** This must be called by the top-level JSON command dispatching code | |
| 261 | +** before they do any work. | |
| 262 | +** | |
| 263 | +** This must only be called once, or an assertion may be triggered. | |
| 259 | 264 | */ |
| 260 | 265 | static void json_mode_bootstrap(){ |
| 266 | + static char once = 0 /* guard against multiple runs */; | |
| 261 | 267 | char const * zPath = P("PATH_INFO"); |
| 262 | 268 | cson_value * pathSplit = |
| 263 | 269 | cson_cgi_getenv(&g.json.cgiCx,"e","PATH_INFO_SPLIT"); |
| 270 | + assert( (0==once) && "json_mode_bootstrap() called too many times!"); | |
| 271 | + if( once ) return; | |
| 272 | + else once = 1; | |
| 264 | 273 | g.json.isJsonMode = 1; |
| 265 | 274 | g.json.resultCode = 0; |
| 266 | 275 | g.json.cmdOffset = -1; |
| 267 | 276 | if( !g.isCGI && g.fullHttpReply ){ |
| 277 | + /* workaround for server mode, so we see it as CGI mode. */ | |
| 268 | 278 | g.isCGI = 1; |
| 269 | 279 | } |
| 270 | - /*json_err( 1000, zPath, 1 ); exit(0);*/ | |
| 271 | 280 | #if defined(NDEBUG) |
| 272 | 281 | /* avoids debug messages on stderr in JSON mode */ |
| 273 | 282 | sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0); |
| 274 | 283 | #endif |
| 275 | 284 | |
| @@ -280,40 +289,58 @@ | ||
| 280 | 289 | */ |
| 281 | 290 | if( pathSplit ){ |
| 282 | 291 | /* cson_cgi already did this, so let's just re-use it. This does |
| 283 | 292 | not happen in "plain server" mode, but does in CGI mode. |
| 284 | 293 | */ |
| 294 | + assert( g.isCGI && "g.isCGI should have been set by now." ); | |
| 285 | 295 | cson_cgi_setenv( &g.json.cgiCx, |
| 286 | 296 | FossilJsonKeys.commandPath, |
| 287 | 297 | pathSplit ); |
| 288 | - }else 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 | - cson_value * arV = cson_value_new_array(); /* value to store path in */ | |
| 297 | - cson_array * ar = cson_value_get_array(arV); /* the real array object */ | |
| 298 | + }else{ /* either CLI or server mode... */ | |
| 299 | + cson_value * arV /* value to store path in */; | |
| 300 | + cson_array * ar /* the "real" array object */; | |
| 301 | + arV = cson_value_new_array(); | |
| 302 | + ar = cson_value_get_array(arV); | |
| 298 | 303 | cson_cgi_setenv( &g.json.cgiCx, |
| 299 | 304 | FossilJsonKeys.commandPath, |
| 300 | 305 | arV ); |
| 301 | - for( ;*p!='?'; ++p){ | |
| 302 | - if( !*p || ('/' == *p) ){ | |
| 303 | - if( len ) { | |
| 304 | - cson_value * part; | |
| 305 | - assert( head != p ); | |
| 306 | - part = cson_value_new_string(head, len); | |
| 307 | - cson_array_append( ar, part ); | |
| 308 | - len = 0; | |
| 309 | - } | |
| 310 | - if( !*p ) break; | |
| 311 | - head = p+1; | |
| 312 | - continue; | |
| 313 | - } | |
| 314 | - ++len; | |
| 306 | + if( zPath ){ | |
| 307 | + /* Translate fossil's PATH_INFO into cson_cgi for later | |
| 308 | + convenience, to help consolidate how we handle CGI/server | |
| 309 | + modes. This block is hit when running in plain server mode. | |
| 310 | + */ | |
| 311 | + char const * p = zPath /* current byte */; | |
| 312 | + char const * head = p /* current start-of-token */; | |
| 313 | + unsigned int len = 0 /* current token's lengh */; | |
| 314 | + assert( g.isCGI && "g.isCGI should have been set by now." ); | |
| 315 | + for( ;*p!='?'; ++p){ | |
| 316 | + if( !*p || ('/' == *p) ){ | |
| 317 | + if( len ) { | |
| 318 | + cson_value * part; | |
| 319 | + assert( head != p ); | |
| 320 | + part = cson_value_new_string(head, len); | |
| 321 | + cson_array_append( ar, part ); | |
| 322 | + len = 0; | |
| 323 | + } | |
| 324 | + if( !*p ) break; | |
| 325 | + head = p+1; | |
| 326 | + continue; | |
| 327 | + } | |
| 328 | + ++len; | |
| 329 | + } | |
| 330 | + }else{ | |
| 331 | + /* assume CLI mode */ | |
| 332 | + int i; | |
| 333 | + char const * arg; | |
| 334 | + cson_value * part; | |
| 335 | + assert( (!g.isCGI) && "g.isCGI set and we do not expect that to be the case here." ); | |
| 336 | + for(i = 1; i < g.argc; ++i ){ | |
| 337 | + arg = g.argv[i]; | |
| 338 | + if( !arg || !*arg ) continue; | |
| 339 | + part = cson_value_new_string(arg,strlen(arg)); | |
| 340 | + cson_array_append(ar, part); | |
| 341 | + } | |
| 315 | 342 | } |
| 316 | 343 | } |
| 317 | 344 | /* g.json.reqPayload exists only to simplify some of our access to |
| 318 | 345 | the request payload. We currently only use this in the context of |
| 319 | 346 | Object payloads, not Arrays, strings, etc. */ |
| @@ -324,25 +351,24 @@ | ||
| 324 | 351 | g.json.reqPayload.v is-not-a Object. |
| 325 | 352 | */; |
| 326 | 353 | } |
| 327 | 354 | json_auth_token()/* will copy our auth token, if any, to fossil's core. */; |
| 328 | 355 | if( g.isCGI ){ |
| 329 | - login_check_credentials(); | |
| 356 | + login_check_credentials()/* populates g.perm */; | |
| 330 | 357 | } |
| 331 | 358 | else{ |
| 332 | 359 | db_find_and_open_repository(OPEN_ANY_SCHEMA,0); |
| 333 | 360 | } |
| 334 | - | |
| 335 | 361 | } |
| 336 | 362 | |
| 337 | 363 | /* |
| 338 | 364 | ** Returns the ndx'th item in the "command path", where index 0 is the |
| 339 | 365 | ** position of the "json" part of the path. Returns NULL if ndx is out |
| 340 | 366 | ** of bounds or there is no "json" path element. |
| 341 | 367 | ** |
| 342 | 368 | ** In CLI mode the "path" is the list of arguments (skipping argv[0]). |
| 343 | -** In server/CGI modes the path is the PATH_INFO. | |
| 369 | +** In server/CGI modes the path is taken from PATH_INFO. | |
| 344 | 370 | */ |
| 345 | 371 | static char const * json_path_part(unsigned char ndx){ |
| 346 | 372 | cson_array * ar = g.isCGI |
| 347 | 373 | ? cson_value_get_array(cson_cgi_getenv(&g.json.cgiCx, |
| 348 | 374 | "a", |
| @@ -351,11 +377,11 @@ | ||
| 351 | 377 | if( g.isCGI ){ |
| 352 | 378 | assert((NULL!=ar) && "Internal error."); |
| 353 | 379 | } |
| 354 | 380 | if( g.json.cmdOffset < 0 ){ |
| 355 | 381 | /* first-time setup. */ |
| 356 | - short i = g.isCGI ? 0 : 1; | |
| 382 | + short i = g.isCGI ? 0 : 1/*skip argv[0] in CLI mode*/; | |
| 357 | 383 | #define PARTAT cson_string_cstr( \ |
| 358 | 384 | cson_value_get_string( \ |
| 359 | 385 | cson_array_get(ar,i) \ |
| 360 | 386 | )) |
| 361 | 387 | #define NEXT (g.isCGI \ |
| @@ -367,14 +393,14 @@ | ||
| 367 | 393 | g.json.cmdOffset = i; |
| 368 | 394 | break; |
| 369 | 395 | } |
| 370 | 396 | ++i; |
| 371 | 397 | tok = NEXT; |
| 398 | + } | |
| 399 | + } | |
| 372 | 400 | #undef NEXT |
| 373 | 401 | #undef PARTAT |
| 374 | - } | |
| 375 | - } | |
| 376 | 402 | if( g.json.cmdOffset < 0 ){ |
| 377 | 403 | return NULL; |
| 378 | 404 | }else{ |
| 379 | 405 | ndx = g.json.cmdOffset + ndx; |
| 380 | 406 | return g.isCGI |
| 381 | 407 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -219,12 +219,13 @@ | |
| 219 | if( !g.json.authToken ){ |
| 220 | /* Try to get an authorization token from GET parameter, POSTed |
| 221 | JSON, or fossil cookie (in that order). */ |
| 222 | g.json.authToken = cson_cgi_getenv(&g.json.cgiCx, "gp", FossilJsonKeys.authToken) |
| 223 | /* reminder to self: cson_cgi does not have access to the cookies |
| 224 | because fossil's core consumes them. Thus we cannot use "gpc" |
| 225 | here. |
| 226 | */; |
| 227 | if( g.json.authToken){ |
| 228 | /* tell fossil to use this login info. |
| 229 | |
| 230 | FIXME: because the JSON bits don't carry around login_cookie_name(), |
| @@ -250,26 +251,34 @@ | |
| 250 | return g.json.authToken; |
| 251 | } |
| 252 | |
| 253 | /* |
| 254 | ** Performs some common initialization of JSON-related state. |
| 255 | ** Implicitly sets up the login information state in CGI mode, |
| 256 | ** but does not perform any authentication here. It _might_ |
| 257 | ** (haven't tested this) die with an error if an auth cookie |
| 258 | ** is malformed. |
| 259 | */ |
| 260 | static void json_mode_bootstrap(){ |
| 261 | char const * zPath = P("PATH_INFO"); |
| 262 | cson_value * pathSplit = |
| 263 | cson_cgi_getenv(&g.json.cgiCx,"e","PATH_INFO_SPLIT"); |
| 264 | g.json.isJsonMode = 1; |
| 265 | g.json.resultCode = 0; |
| 266 | g.json.cmdOffset = -1; |
| 267 | if( !g.isCGI && g.fullHttpReply ){ |
| 268 | g.isCGI = 1; |
| 269 | } |
| 270 | /*json_err( 1000, zPath, 1 ); exit(0);*/ |
| 271 | #if defined(NDEBUG) |
| 272 | /* avoids debug messages on stderr in JSON mode */ |
| 273 | sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0); |
| 274 | #endif |
| 275 | |
| @@ -280,40 +289,58 @@ | |
| 280 | */ |
| 281 | if( pathSplit ){ |
| 282 | /* cson_cgi already did this, so let's just re-use it. This does |
| 283 | not happen in "plain server" mode, but does in CGI mode. |
| 284 | */ |
| 285 | cson_cgi_setenv( &g.json.cgiCx, |
| 286 | FossilJsonKeys.commandPath, |
| 287 | pathSplit ); |
| 288 | }else 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 | cson_value * arV = cson_value_new_array(); /* value to store path in */ |
| 297 | cson_array * ar = cson_value_get_array(arV); /* the real array object */ |
| 298 | cson_cgi_setenv( &g.json.cgiCx, |
| 299 | FossilJsonKeys.commandPath, |
| 300 | arV ); |
| 301 | for( ;*p!='?'; ++p){ |
| 302 | if( !*p || ('/' == *p) ){ |
| 303 | if( len ) { |
| 304 | cson_value * part; |
| 305 | assert( head != p ); |
| 306 | part = cson_value_new_string(head, len); |
| 307 | cson_array_append( ar, part ); |
| 308 | len = 0; |
| 309 | } |
| 310 | if( !*p ) break; |
| 311 | head = p+1; |
| 312 | continue; |
| 313 | } |
| 314 | ++len; |
| 315 | } |
| 316 | } |
| 317 | /* g.json.reqPayload exists only to simplify some of our access to |
| 318 | the request payload. We currently only use this in the context of |
| 319 | Object payloads, not Arrays, strings, etc. */ |
| @@ -324,25 +351,24 @@ | |
| 324 | g.json.reqPayload.v is-not-a Object. |
| 325 | */; |
| 326 | } |
| 327 | json_auth_token()/* will copy our auth token, if any, to fossil's core. */; |
| 328 | if( g.isCGI ){ |
| 329 | login_check_credentials(); |
| 330 | } |
| 331 | else{ |
| 332 | db_find_and_open_repository(OPEN_ANY_SCHEMA,0); |
| 333 | } |
| 334 | |
| 335 | } |
| 336 | |
| 337 | /* |
| 338 | ** Returns the ndx'th item in the "command path", where index 0 is the |
| 339 | ** position of the "json" part of the path. Returns NULL if ndx is out |
| 340 | ** of bounds or there is no "json" path element. |
| 341 | ** |
| 342 | ** In CLI mode the "path" is the list of arguments (skipping argv[0]). |
| 343 | ** In server/CGI modes the path is the PATH_INFO. |
| 344 | */ |
| 345 | static char const * json_path_part(unsigned char ndx){ |
| 346 | cson_array * ar = g.isCGI |
| 347 | ? cson_value_get_array(cson_cgi_getenv(&g.json.cgiCx, |
| 348 | "a", |
| @@ -351,11 +377,11 @@ | |
| 351 | if( g.isCGI ){ |
| 352 | assert((NULL!=ar) && "Internal error."); |
| 353 | } |
| 354 | if( g.json.cmdOffset < 0 ){ |
| 355 | /* first-time setup. */ |
| 356 | short i = g.isCGI ? 0 : 1; |
| 357 | #define PARTAT cson_string_cstr( \ |
| 358 | cson_value_get_string( \ |
| 359 | cson_array_get(ar,i) \ |
| 360 | )) |
| 361 | #define NEXT (g.isCGI \ |
| @@ -367,14 +393,14 @@ | |
| 367 | g.json.cmdOffset = i; |
| 368 | break; |
| 369 | } |
| 370 | ++i; |
| 371 | tok = NEXT; |
| 372 | #undef NEXT |
| 373 | #undef PARTAT |
| 374 | } |
| 375 | } |
| 376 | if( g.json.cmdOffset < 0 ){ |
| 377 | return NULL; |
| 378 | }else{ |
| 379 | ndx = g.json.cmdOffset + ndx; |
| 380 | return g.isCGI |
| 381 |
| --- src/json.c | |
| +++ src/json.c | |
| @@ -219,12 +219,13 @@ | |
| 219 | if( !g.json.authToken ){ |
| 220 | /* Try to get an authorization token from GET parameter, POSTed |
| 221 | JSON, or fossil cookie (in that order). */ |
| 222 | g.json.authToken = cson_cgi_getenv(&g.json.cgiCx, "gp", FossilJsonKeys.authToken) |
| 223 | /* reminder to self: cson_cgi does not have access to the cookies |
| 224 | because fossil's core consumes them. Thus we cannot use "agpc" |
| 225 | here. We use "a" (App-specific) as a place to store fossil's |
| 226 | cookie value. |
| 227 | */; |
| 228 | if( g.json.authToken){ |
| 229 | /* tell fossil to use this login info. |
| 230 | |
| 231 | FIXME: because the JSON bits don't carry around login_cookie_name(), |
| @@ -250,26 +251,34 @@ | |
| 251 | return g.json.authToken; |
| 252 | } |
| 253 | |
| 254 | /* |
| 255 | ** Performs some common initialization of JSON-related state. |
| 256 | ** Implicitly sets up the login information state in CGI mode, but |
| 257 | ** does not perform any authentication here. It _might_ (haven't |
| 258 | ** tested this) die with an error if an auth cookie is malformed. |
| 259 | ** |
| 260 | ** This must be called by the top-level JSON command dispatching code |
| 261 | ** before they do any work. |
| 262 | ** |
| 263 | ** This must only be called once, or an assertion may be triggered. |
| 264 | */ |
| 265 | static void json_mode_bootstrap(){ |
| 266 | static char once = 0 /* guard against multiple runs */; |
| 267 | char const * zPath = P("PATH_INFO"); |
| 268 | cson_value * pathSplit = |
| 269 | cson_cgi_getenv(&g.json.cgiCx,"e","PATH_INFO_SPLIT"); |
| 270 | assert( (0==once) && "json_mode_bootstrap() called too many times!"); |
| 271 | if( once ) return; |
| 272 | else once = 1; |
| 273 | g.json.isJsonMode = 1; |
| 274 | g.json.resultCode = 0; |
| 275 | g.json.cmdOffset = -1; |
| 276 | if( !g.isCGI && g.fullHttpReply ){ |
| 277 | /* workaround for server mode, so we see it as CGI mode. */ |
| 278 | g.isCGI = 1; |
| 279 | } |
| 280 | #if defined(NDEBUG) |
| 281 | /* avoids debug messages on stderr in JSON mode */ |
| 282 | sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0); |
| 283 | #endif |
| 284 | |
| @@ -280,40 +289,58 @@ | |
| 289 | */ |
| 290 | if( pathSplit ){ |
| 291 | /* cson_cgi already did this, so let's just re-use it. This does |
| 292 | not happen in "plain server" mode, but does in CGI mode. |
| 293 | */ |
| 294 | assert( g.isCGI && "g.isCGI should have been set by now." ); |
| 295 | cson_cgi_setenv( &g.json.cgiCx, |
| 296 | FossilJsonKeys.commandPath, |
| 297 | pathSplit ); |
| 298 | }else{ /* either CLI or server mode... */ |
| 299 | cson_value * arV /* value to store path in */; |
| 300 | cson_array * ar /* the "real" array object */; |
| 301 | arV = cson_value_new_array(); |
| 302 | ar = cson_value_get_array(arV); |
| 303 | cson_cgi_setenv( &g.json.cgiCx, |
| 304 | FossilJsonKeys.commandPath, |
| 305 | arV ); |
| 306 | if( zPath ){ |
| 307 | /* Translate fossil's PATH_INFO into cson_cgi for later |
| 308 | convenience, to help consolidate how we handle CGI/server |
| 309 | modes. This block is hit when running in plain server mode. |
| 310 | */ |
| 311 | char const * p = zPath /* current byte */; |
| 312 | char const * head = p /* current start-of-token */; |
| 313 | unsigned int len = 0 /* current token's lengh */; |
| 314 | assert( g.isCGI && "g.isCGI should have been set by now." ); |
| 315 | for( ;*p!='?'; ++p){ |
| 316 | if( !*p || ('/' == *p) ){ |
| 317 | if( len ) { |
| 318 | cson_value * part; |
| 319 | assert( head != p ); |
| 320 | part = cson_value_new_string(head, len); |
| 321 | cson_array_append( ar, part ); |
| 322 | len = 0; |
| 323 | } |
| 324 | if( !*p ) break; |
| 325 | head = p+1; |
| 326 | continue; |
| 327 | } |
| 328 | ++len; |
| 329 | } |
| 330 | }else{ |
| 331 | /* assume CLI mode */ |
| 332 | int i; |
| 333 | char const * arg; |
| 334 | cson_value * part; |
| 335 | assert( (!g.isCGI) && "g.isCGI set and we do not expect that to be the case here." ); |
| 336 | for(i = 1; i < g.argc; ++i ){ |
| 337 | arg = g.argv[i]; |
| 338 | if( !arg || !*arg ) continue; |
| 339 | part = cson_value_new_string(arg,strlen(arg)); |
| 340 | cson_array_append(ar, part); |
| 341 | } |
| 342 | } |
| 343 | } |
| 344 | /* g.json.reqPayload exists only to simplify some of our access to |
| 345 | the request payload. We currently only use this in the context of |
| 346 | Object payloads, not Arrays, strings, etc. */ |
| @@ -324,25 +351,24 @@ | |
| 351 | g.json.reqPayload.v is-not-a Object. |
| 352 | */; |
| 353 | } |
| 354 | json_auth_token()/* will copy our auth token, if any, to fossil's core. */; |
| 355 | if( g.isCGI ){ |
| 356 | login_check_credentials()/* populates g.perm */; |
| 357 | } |
| 358 | else{ |
| 359 | db_find_and_open_repository(OPEN_ANY_SCHEMA,0); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | /* |
| 364 | ** Returns the ndx'th item in the "command path", where index 0 is the |
| 365 | ** position of the "json" part of the path. Returns NULL if ndx is out |
| 366 | ** of bounds or there is no "json" path element. |
| 367 | ** |
| 368 | ** In CLI mode the "path" is the list of arguments (skipping argv[0]). |
| 369 | ** In server/CGI modes the path is taken from PATH_INFO. |
| 370 | */ |
| 371 | static char const * json_path_part(unsigned char ndx){ |
| 372 | cson_array * ar = g.isCGI |
| 373 | ? cson_value_get_array(cson_cgi_getenv(&g.json.cgiCx, |
| 374 | "a", |
| @@ -351,11 +377,11 @@ | |
| 377 | if( g.isCGI ){ |
| 378 | assert((NULL!=ar) && "Internal error."); |
| 379 | } |
| 380 | if( g.json.cmdOffset < 0 ){ |
| 381 | /* first-time setup. */ |
| 382 | short i = g.isCGI ? 0 : 1/*skip argv[0] in CLI mode*/; |
| 383 | #define PARTAT cson_string_cstr( \ |
| 384 | cson_value_get_string( \ |
| 385 | cson_array_get(ar,i) \ |
| 386 | )) |
| 387 | #define NEXT (g.isCGI \ |
| @@ -367,14 +393,14 @@ | |
| 393 | g.json.cmdOffset = i; |
| 394 | break; |
| 395 | } |
| 396 | ++i; |
| 397 | tok = NEXT; |
| 398 | } |
| 399 | } |
| 400 | #undef NEXT |
| 401 | #undef PARTAT |
| 402 | if( g.json.cmdOffset < 0 ){ |
| 403 | return NULL; |
| 404 | }else{ |
| 405 | ndx = g.json.cmdOffset + ndx; |
| 406 | return g.isCGI |
| 407 |