Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Changed fossil_json_f() callback interface. Refactored json command dispatching a bit. Fixed an ordering problem in the json timelines. Pulled in latest cson_amalgamation. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | json |
| Files: | files | file ages | folders |
| SHA1: |
c24b44501290ad103bd73ed3ee8b0f36 |
| User & Date: | stephan 2011-09-23 10:52:41.812 |
Context
|
2011-09-23
| ||
| 12:29 | Implemented /json/timeline/ticket, cleaned up timeline/ci|wiki. ... (check-in: 42900f3029 user: stephan tags: json) | |
| 10:52 | Changed fossil_json_f() callback interface. Refactored json command dispatching a bit. Fixed an ordering problem in the json timelines. Pulled in latest cson_amalgamation. ... (check-in: c24b445012 user: stephan tags: json) | |
| 01:00 | Corrected /json/wiki/save|create to honor the proper g.perm.WrWiki/NewWiki perm, instead of just WrWiki. ... (check-in: d3759cd40f user: stephan tags: json) | |
Changes
Changes to src/cson_amalgamation.c.
| ︙ | ︙ | |||
2433 2434 2435 2436 2437 2438 2439 |
else
{
#if CSON_VOID_PTR_IS_BIG
cson_value_clean( val );
val->value = (void *)v;
#else
cson_int_t * iv = NULL;
| < > | 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 |
else
{
#if CSON_VOID_PTR_IS_BIG
cson_value_clean( val );
val->value = (void *)v;
#else
cson_int_t * iv = NULL;
iv = (cson_int_t*)cson_malloc(sizeof(cson_int_t), "cson_int_t");
if( ! iv ) return cson_rc.AllocError;
cson_value_clean( val );
*iv = v;
val->value = iv;
#endif
val->api = &cson_value_api_integer;
return 0;
}
}
|
| ︙ | ︙ |
Changes to src/json.c.
| ︙ | ︙ | |||
56 57 58 59 60 61 62 | ** either their payload object or NULL. Note that NULL is a legal ** success value - it simply means the response will contain no ** payload. If g.json.resultCode is non-zero when this function ** returns then the top-level dispatcher will destroy any payload ** returned by this function and will output a JSON error response ** instead. ** | < < < < < < < < < < < | | | 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
** either their payload object or NULL. Note that NULL is a legal
** success value - it simply means the response will contain no
** payload. If g.json.resultCode is non-zero when this function
** returns then the top-level dispatcher will destroy any payload
** returned by this function and will output a JSON error response
** instead.
**
** All of the setup/response code is handled by the top dispatcher
** functions and the callbacks concern themselves only with generating
** the payload.
**
** It is imperitive that NO callback functions EVER output ANYTHING to
** stdout, as that will effectively corrupt any JSON output, and
** almost certainly will corrupt any HTTP response headers. Output
** sent to stderr ends up in my apache log, so that might be useful
** for debuggering in some cases, but so such code should be left
** enabled for non-debuggering builds.
*/
typedef cson_value * (*fossil_json_f)();
/*
** Internal helpers to manipulate a byte array as a bitset. The B
** argument must be-a array at least (BIT/8+1) bytes long.
** The BIT argument is the bit number to query/set/clear/toggle.
*/
#define BITSET_BYTEFOR(B,BIT) ((B)[ BIT / 8 ])
#define BITSET_SET(B,BIT) ((BITSET_BYTEFOR(B,BIT) |= (0x01 << (BIT%8))),0x01)
#define BITSET_UNSET(B,BIT) ((BITSET_BYTEFOR(B,BIT) &= ~(0x01 << (BIT%8))),0x00)
#define BITSET_GET(B,BIT) ((BITSET_BYTEFOR(B,BIT) & (0x01 << (BIT%8))) ? 0x01 : 0x00)
#define BITSET_TOGGLE(B,BIT) (BITSET_GET(B,BIT) ? (BITSET_UNSET(B,BIT)) : (BITSET_SET(B,BIT)))
/*
** Placeholder /json/XXX page impl for NYI (Not Yet Implemented)
** (but planned) pages/commands.
*/
static cson_value * json_page_nyi(){
g.json.resultCode = FSL_JSON_E_NYI;
return NULL;
}
/*
** Holds keys used for various JSON API properties.
*/
|
| ︙ | ︙ | |||
1187 1188 1189 1190 1191 1192 1193 | } /* ** /json/version implementation. ** ** Returns the payload object (owned by the caller). */ | | | 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 |
}
/*
** /json/version implementation.
**
** Returns the payload object (owned by the caller).
*/
cson_value * json_page_version(){
cson_value * jval = NULL;
cson_object * jobj = NULL;
jval = cson_value_new_object();
jobj = cson_value_get_object(jval);
#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X)))
FSET(MANIFEST_UUID,"manifestUuid");
FSET(MANIFEST_VERSION,"manifestVersion");
|
| ︙ | ︙ | |||
1216 1217 1218 1219 1220 1221 1222 | ** ** Returned object contains details about the "capabilities" of the ** current user (what he may/may not do). ** ** This is primarily intended for debuggering, but may have ** a use in client code. (?) */ | | | 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 |
**
** Returned object contains details about the "capabilities" of the
** current user (what he may/may not do).
**
** This is primarily intended for debuggering, but may have
** a use in client code. (?)
*/
cson_value * json_page_cap(){
cson_value * payload = cson_value_new_object();
cson_value * sub = cson_value_new_object();
Stmt q;
cson_object * obj = cson_value_get_object(payload);
db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid);
if( db_step(&q)==SQLITE_ROW ){
/* reminder: we don't use g.zLogin because it's 0 for the guest
|
| ︙ | ︙ | |||
1272 1273 1274 1275 1276 1277 1278 | return payload; } /* ** Implementation of the /json/login page. ** */ | | | 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 |
return payload;
}
/*
** Implementation of the /json/login page.
**
*/
cson_value * json_page_login(){
static char preciseErrors = /* if true, "complete" JSON error codes are used,
else they are "dumbed down" to a generic login
error code.
*/
#if 0
g.json.errorDetailParanoia ? 0 : 1
#else
|
| ︙ | ︙ | |||
1402 1403 1404 1405 1406 1407 1408 | #endif } /* ** Impl of /json/logout. ** */ | | | 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 |
#endif
}
/*
** Impl of /json/logout.
**
*/
cson_value * json_page_logout(){
cson_value const *token = g.json.authToken;
/* Remember that json_mode_bootstrap() replaces the login cookie
with the JSON auth token if the request contains it. If the
reqest is missing the auth token then this will fetch fossil's
original cookie. Either way, it's what we want :).
We require the auth token to avoid someone maliciously
|
| ︙ | ︙ | |||
1427 1428 1429 1430 1431 1432 1433 | } return NULL; } /* ** Implementation of the /json/anonymousPassword page. */ | | | | 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 |
}
return NULL;
}
/*
** Implementation of the /json/anonymousPassword page.
*/
cson_value * json_page_anon_password(){
cson_value * v = cson_value_new_object();
cson_object * o = cson_value_get_object(v);
unsigned const int seed = captcha_seed();
char const * zCaptcha = captcha_decode(seed);
cson_object_set(o, "seed",
cson_value_new_integer( (cson_int_t)seed )
);
cson_object_set(o, "password",
cson_value_new_string( zCaptcha, strlen(zCaptcha) )
);
return v;
}
/*
** Implementation of the /json/stat page/command.
**
*/
cson_value * json_page_stat(){
i64 t, fsize;
int n, m;
const char *zDb;
enum { BufLen = 1000 };
char zBuf[BufLen];
cson_value * jv = NULL;
cson_object * jo = NULL;
|
| ︙ | ︙ | |||
1541 1542 1543 1544 1545 1546 1547 | sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb)); cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null()); return jv; #undef SETBUF } | | | | | | | | > | < | > | | | | | 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 |
sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA %s.journal_mode", zDb));
cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null());
return jv;
#undef SETBUF
}
static cson_value * json_wiki_list();
static cson_value * json_wiki_get();
static cson_value * json_wiki_save();
static cson_value * json_wiki_create();
/*
** Mapping of /json/wiki/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Wiki[] = {
{"create", json_wiki_create, 1},
{"get", json_wiki_get, 0},
{"list", json_wiki_list, 0},
{"save", json_wiki_save, 1},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** A page/command dispatch helper for fossil_json_f() implementations.
** depth should be the depth parameter passed to the fossil_json_f().
** pages must be an array of JsonPageDef commands which we can
** dispatch. The final item in the array MUST have a NULL name
** element.
**
** This function takes json_comand_arg(1+g.json.dispatchDepth) and
** searches pages for a matching name. If found then that page's
** func() is called to fetch the payload, which is returned to the
** caller.
**
** On error, g.json.resultCode is set to one of the FossilJsonCodes
** values.
*/
static cson_value * json_page_dispatch_helper(JsonPageDef const * pages){
JsonPageDef const * def;
char const * cmd = json_command_arg(1+g.json.dispatchDepth);
assert( NULL != pages );
if( ! cmd ){
g.json.resultCode = FSL_JSON_E_MISSING_ARGS;
return NULL;
}
def = json_handler_for_name( cmd, pages );
if(!def){
g.json.resultCode = FSL_JSON_E_UNKNOWN_COMMAND;
return NULL;
}
else{
++g.json.dispatchDepth;
return (*def->func)();
}
}
/*
** Implements the /json/wiki family of pages/commands. Far from
** complete.
**
*/
static cson_value * json_page_wiki(){
return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]);
}
/*
** Implementation of /json/wiki/get.
**
** TODO: add option to parse wiki output. It is currently
** unparsed.
*/
static cson_value * json_wiki_get(){
int rid;
Manifest *pWiki = 0;
char const * zBody = NULL;
char const * zPageName;
char doParse = 0/*not yet implemented*/;
if( !g.perm.RdWiki ){
|
| ︙ | ︙ | |||
1762 1763 1764 1765 1766 1767 1768 | return payV; } /* ** Implementation of /json/wiki/create. */ | | | | | 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 |
return payV;
}
/*
** Implementation of /json/wiki/create.
*/
static cson_value * json_wiki_create(){
return json_wiki_create_or_save(1);
}
/*
** Implementation of /json/wiki/save.
*/
static cson_value * json_wiki_save(){
/* FIXME: add GET/POST.payload bool option createIfNotExists. */
return json_wiki_create_or_save(0);
}
/*
** Implementation of /json/wiki/list.
*/
static cson_value * json_wiki_list(){
cson_value * listV = NULL;
cson_array * list = NULL;
Stmt q;
if( !g.perm.RdWiki ){
g.json.resultCode = FSL_JSON_E_DENIED;
return NULL;
}
|
| ︙ | ︙ | |||
1811 1812 1813 1814 1815 1816 1817 | listV = NULL; end: db_finalize(&q); return listV; } | | | | | | 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 |
listV = NULL;
end:
db_finalize(&q);
return listV;
}
static cson_value * json_branch_list();
/*
** Mapping of /json/branch/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Branch[] = {
{"list", json_branch_list, 0},
{"create", json_page_nyi, 1},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implements the /json/branch family of pages/commands. Far from
** complete.
**
*/
static cson_value * json_page_branch(){
return json_page_dispatch_helper(&JsonPageDefs_Branch[0]);
}
/*
** Impl for /json/branch/list
**
**
** CLI mode options:
**
** --range X | -r X, where X is one of (open,closed,all)
** (only the first letter is significant, default=open).
** -a (same as --range a)
** -c (same as --range c)
**
** HTTP mode options:
**
** "range" GET/POST.payload parameter. FIXME: currently we also use
** POST, but really want to restrict this to POST.payload.
*/
static cson_value * json_branch_list(){
cson_value * payV;
cson_object * pay;
cson_value * listV;
cson_array * list;
char const * range = NULL;
int which = 0;
Stmt q;
|
| ︙ | ︙ | |||
1928 1929 1930 1931 1932 1933 1934 |
json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,msg);
free(msg);
}
}
return payV;
}
| | | | | | 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 |
json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,msg);
free(msg);
}
}
return payV;
}
static cson_value * json_timeline_ci();
static cson_value * json_timeline_wiki();
/*
** Mapping of /json/timeline/XXX commands/paths to callbacks.
*/
static const JsonPageDef JsonPageDefs_Timeline[] = {
{"ci", json_timeline_ci, 0},
{"wiki", json_timeline_wiki, 0},
/* Last entry MUST have a NULL name. */
{NULL,NULL,0}
};
/*
** Implements the /json/timeline family of pages/commands. Far from
** complete.
**
*/
static cson_value * json_page_timeline(){
return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]);
}
/*
** Create a temporary table suitable for storing timeline data.
*/
static void json_timeline_temp_table(void){
/* Field order MUST match that from json_timeline_query()!!! */
|
| ︙ | ︙ | |||
2011 2012 2013 2014 2015 2016 2017 |
** Helper for the timeline family of functions. Possibly appends 1
** AND clause and an ORDER BY clause to pSql, depending on the state
** of the "after" ("a") or "before" ("b") environment parameters.
** This function gives "after" precedence over "before", and only
** applies one of them.
**
** Returns -1 if it adds a "before" clause, 1 if it adds
| | | 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 |
** Helper for the timeline family of functions. Possibly appends 1
** AND clause and an ORDER BY clause to pSql, depending on the state
** of the "after" ("a") or "before" ("b") environment parameters.
** This function gives "after" precedence over "before", and only
** applies one of them.
**
** Returns -1 if it adds a "before" clause, 1 if it adds
** an "after" clause, and 0 if adds only an order-by clause.
*/
static char json_timeline_add_time_clause(Blob *pSql){
char const * zAfter = NULL;
char const * zBefore = NULL;
if( g.isHTTP ){
/**
FIXME: we are only honoring STRING values here, not int (for
|
| ︙ | ︙ | |||
2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 |
while( fossil_isspace(*zBefore) ) ++zBefore;
blob_appendf(pSql,
" AND event.mtime<=(SELECT julianday(%Q,'utc')) "
" ORDER BY event.mtime DESC ",
zBefore);
return -1;
}else{
return 0;
}
}
/*
** Tries to figure out a timeline query length limit base on
** environment parameters. If it can it returns that value,
| > | 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 |
while( fossil_isspace(*zBefore) ) ++zBefore;
blob_appendf(pSql,
" AND event.mtime<=(SELECT julianday(%Q,'utc')) "
" ORDER BY event.mtime DESC ",
zBefore);
return -1;
}else{
blob_append(pSql," ORDER BY event.mtime DESC ", -1);
return 0;
}
}
/*
** Tries to figure out a timeline query length limit base on
** environment parameters. If it can it returns that value,
|
| ︙ | ︙ | |||
2083 2084 2085 2086 2087 2088 2089 | } /* ** Implementation of /json/timeline/ci. ** ** Still a few TODOs (like figuring out how to structure ** inheritance info). */ | | | 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 |
}
/*
** Implementation of /json/timeline/ci.
**
** Still a few TODOs (like figuring out how to structure
** inheritance info).
*/
static cson_value * json_timeline_ci(){
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_value * tmp = NULL;
cson_value * listV = NULL;
cson_array * list = NULL;
int limit;
int check = 0;
|
| ︙ | ︙ | |||
2199 2200 2201 2202 2203 2204 2205 | return payV; } /* ** Implementation of /json/timeline/wiki. ** */ | | | 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 |
return payV;
}
/*
** Implementation of /json/timeline/wiki.
**
*/
static cson_value * json_timeline_wiki(){
/* This code is 95% the same as json_timeline_ci(), by the way. */
cson_value * payV = NULL;
cson_object * pay = NULL;
cson_value * tmp = NULL;
cson_value * listV = NULL;
cson_array * list = NULL;
int limit;
|
| ︙ | ︙ | |||
2307 2308 2309 2310 2311 2312 2313 | return payV; } /* ** Implements the /json/whoami page/command. */ | | | 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 |
return payV;
}
/*
** Implements the /json/whoami page/command.
*/
static cson_value * json_page_whoami(){
cson_value * payload = NULL;
cson_object * obj = NULL;
Stmt q;
db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid);
if( db_step(&q)==SQLITE_ROW ){
/* reminder: we don't use g.zLogin because it's 0 for the guest
|
| ︙ | ︙ | |||
2410 2411 2412 2413 2414 2415 2416 |
** This function dispatches them, and is the HTTP equivalent of
** json_cmd_top().
*/
void json_page_top(void){
int rc = FSL_JSON_E_UNKNOWN_COMMAND;
char const * cmd;
cson_value * payload = NULL;
| < > | | | | 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 |
** This function dispatches them, and is the HTTP equivalent of
** json_cmd_top().
*/
void json_page_top(void){
int rc = FSL_JSON_E_UNKNOWN_COMMAND;
char const * cmd;
cson_value * payload = NULL;
JsonPageDef const * pageDef = NULL;
json_mode_bootstrap();
cmd = json_command_arg(1);
/*cgi_printf("{\"cmd\":\"%s\"}\n",cmd); return;*/
pageDef = json_handler_for_name(cmd,&JsonPageDefs[0]);
if( ! pageDef ){
json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 0 );
return;
}else if( pageDef->runMode < 0 /*CLI only*/){
rc = FSL_JSON_E_WRONG_MODE;
}else{
rc = 0;
g.json.dispatchDepth = 1;
payload = (*pageDef->func)();
}
if( g.json.resultCode ){
json_err(g.json.resultCode, NULL, 0);
}else{
cson_value * root = json_create_response(rc, NULL, payload);
json_send_response(root);
cson_value_free(root);
}
}
/*
** This function dispatches json commands and is the CLI equivalent of
** json_page_top().
|
| ︙ | ︙ | |||
2495 2496 2497 2498 2499 2500 2501 |
if( ! pageDef ){
json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
return;
}else if( pageDef->runMode > 0 /*HTTP only*/){
rc = FSL_JSON_E_WRONG_MODE;
}else{
rc = 0;
| > | | 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 |
if( ! pageDef ){
json_err( FSL_JSON_E_UNKNOWN_COMMAND, NULL, 1 );
return;
}else if( pageDef->runMode > 0 /*HTTP only*/){
rc = FSL_JSON_E_WRONG_MODE;
}else{
rc = 0;
g.json.dispatchDepth = 1;
payload = (*pageDef->func)();
}
if( g.json.resultCode ){
json_err(g.json.resultCode, NULL, 1);
}else{
payload = json_create_response(rc, NULL, payload);
json_send_response(payload);
cson_value_free( payload );
|
| ︙ | ︙ |
Changes to src/main.c.
| ︙ | ︙ | |||
180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
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;
| > > > > > | 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
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. */
unsigned char dispatchDepth /* Tells JSON command dispatching
which argument we are currently
working on. For this purpose, arg#0
is the "json" path/CLI arg.
*/;
struct { /* "garbage collector" */
cson_value * v;
cson_object * o;
} gc;
struct { /* JSON POST data. */
cson_value * v;
cson_array * a;
|
| ︙ | ︙ |