| | @@ -39,10 +39,21 @@ |
| 39 | 39 | |
| 40 | 40 | #if INTERFACE |
| 41 | 41 | #include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */ |
| 42 | 42 | #endif |
| 43 | 43 | |
| 44 | +const FossilJsonKeys_ FossilJsonKeys = { |
| 45 | + "anonymousSeed" /*anonymousSeed*/, |
| 46 | + "authToken" /*authToken*/, |
| 47 | + "COMMAND_PATH" /*commandPath*/, |
| 48 | + "payload" /* payload */, |
| 49 | + "requestId" /*requestId*/, |
| 50 | + "resultCode" /*resultCode*/, |
| 51 | + "resultText" /*resultText*/, |
| 52 | + "timestamp" /*timestamp*/ |
| 53 | +}; |
| 54 | + |
| 44 | 55 | /* |
| 45 | 56 | ** Internal helpers to manipulate a byte array as a bitset. The B |
| 46 | 57 | ** argument must be-a array at least (BIT/8+1) bytes long. |
| 47 | 58 | ** The BIT argument is the bit number to query/set/clear/toggle. |
| 48 | 59 | */ |
| | @@ -280,11 +291,11 @@ |
| 280 | 291 | ** |
| 281 | 292 | ** If it finds a value and that value is-a JSON number or is a string |
| 282 | 293 | ** which looks like an integer or is-a JSON bool then it is converted |
| 283 | 294 | ** to an int. If none of those apply then dflt is returned. |
| 284 | 295 | */ |
| 285 | | -static int json_getenv_int(char const * pKey, int dflt ){ |
| 296 | +int json_getenv_int(char const * pKey, int dflt ){ |
| 286 | 297 | cson_value const * v = json_getenv(pKey); |
| 287 | 298 | if(!v){ |
| 288 | 299 | return dflt; |
| 289 | 300 | }else if( cson_value_is_number(v) ){ |
| 290 | 301 | return (int)cson_value_get_integer(v); |
| | @@ -306,11 +317,11 @@ |
| 306 | 317 | ** value is-a String. Non-strings are not converted to strings for |
| 307 | 318 | ** this purpose. Returned memory is owned by g.json or fossil and is |
| 308 | 319 | ** valid until end-of-app or the given key is replaced in fossil's |
| 309 | 320 | ** internals via cgi_replace_parameter() and friends or json_setenv(). |
| 310 | 321 | */ |
| 311 | | -static char const * json_getenv_cstr( char const * zKey ){ |
| 322 | +char const * json_getenv_cstr( char const * zKey ){ |
| 312 | 323 | return cson_value_get_cstr( json_getenv(zKey) ); |
| 313 | 324 | } |
| 314 | 325 | |
| 315 | 326 | |
| 316 | 327 | /* |
| | @@ -859,11 +870,10 @@ |
| 859 | 870 | */ |
| 860 | 871 | char const * json_auth_token_cstr(){ |
| 861 | 872 | return cson_value_get_cstr( json_auth_token() ); |
| 862 | 873 | } |
| 863 | 874 | |
| 864 | | - |
| 865 | 875 | /* |
| 866 | 876 | ** Returns the JsonPageDef with the given name, or NULL if no match is |
| 867 | 877 | ** found. |
| 868 | 878 | ** |
| 869 | 879 | ** head must be a pointer to an array of JsonPageDefs in which the |
| | @@ -911,11 +921,11 @@ |
| 911 | 921 | /* |
| 912 | 922 | ** Convenience routine which converts a Julian time value into a Unix |
| 913 | 923 | ** Epoch timestamp. Requires the db, so this cannot be used before the |
| 914 | 924 | ** repo is opened (will trigger a fatal error in db_xxx()). |
| 915 | 925 | */ |
| 916 | | -static cson_value * json_julian_to_timestamp(double j){ |
| 926 | +cson_value * json_julian_to_timestamp(double j){ |
| 917 | 927 | return cson_value_new_integer((cson_int_t) |
| 918 | 928 | db_int64(0,"SELECT strftime('%%s',%lf)",j) |
| 919 | 929 | ); |
| 920 | 930 | } |
| 921 | 931 | |
| | @@ -1300,48 +1310,10 @@ |
| 1300 | 1310 | return jv; |
| 1301 | 1311 | #undef SETBUF |
| 1302 | 1312 | } |
| 1303 | 1313 | |
| 1304 | 1314 | |
| 1305 | | -static cson_value * json_wiki_create(); |
| 1306 | | -static cson_value * json_wiki_get(); |
| 1307 | | -static cson_value * json_wiki_list(); |
| 1308 | | -static cson_value * json_wiki_save(); |
| 1309 | | -static cson_value * json_timeline_ci(); |
| 1310 | | -static cson_value * json_timeline_ticket(); |
| 1311 | | -static cson_value * json_timeline_wiki(); |
| 1312 | | - |
| 1313 | | -/* |
| 1314 | | -** Mapping of /json/wiki/XXX commands/paths to callbacks. |
| 1315 | | -*/ |
| 1316 | | -static const JsonPageDef JsonPageDefs_Wiki[] = { |
| 1317 | | -{"create", json_wiki_create, 1}, |
| 1318 | | -{"get", json_wiki_get, 0}, |
| 1319 | | -{"list", json_wiki_list, 0}, |
| 1320 | | -{"save", json_wiki_save, 1}, |
| 1321 | | -{"timeline", json_timeline_wiki,0}, |
| 1322 | | -/* Last entry MUST have a NULL name. */ |
| 1323 | | -{NULL,NULL,0} |
| 1324 | | -}; |
| 1325 | | - |
| 1326 | | -/* |
| 1327 | | -** Mapping of /json/timeline/XXX commands/paths to callbacks. |
| 1328 | | -*/ |
| 1329 | | -static const JsonPageDef JsonPageDefs_Timeline[] = { |
| 1330 | | -{"c", json_timeline_ci, 0}, |
| 1331 | | -{"ci", json_timeline_ci, 0}, |
| 1332 | | -{"com", json_timeline_ci, 0}, |
| 1333 | | -{"commit", json_timeline_ci, 0}, |
| 1334 | | -{"t", json_timeline_ticket, 0}, |
| 1335 | | -{"ticket", json_timeline_ticket, 0}, |
| 1336 | | -{"w", json_timeline_wiki, 0}, |
| 1337 | | -{"wi", json_timeline_wiki, 0}, |
| 1338 | | -{"wik", json_timeline_wiki, 0}, |
| 1339 | | -{"wiki", json_timeline_wiki, 0}, |
| 1340 | | -/* Last entry MUST have a NULL name. */ |
| 1341 | | -{NULL,NULL,0} |
| 1342 | | -}; |
| 1343 | 1315 | |
| 1344 | 1316 | static cson_value * json_user_list(); |
| 1345 | 1317 | static cson_value * json_user_get(); |
| 1346 | 1318 | #if 0 |
| 1347 | 1319 | static cson_value * json_user_create(); |
| | @@ -1359,26 +1331,11 @@ |
| 1359 | 1331 | /* Last entry MUST have a NULL name. */ |
| 1360 | 1332 | {NULL,NULL,0} |
| 1361 | 1333 | }; |
| 1362 | 1334 | |
| 1363 | 1335 | |
| 1364 | | -/* |
| 1365 | | -** A page/command dispatch helper for fossil_json_f() implementations. |
| 1366 | | -** pages must be an array of JsonPageDef commands which we can |
| 1367 | | -** dispatch. The final item in the array MUST have a NULL name |
| 1368 | | -** element. |
| 1369 | | -** |
| 1370 | | -** This function takes the command specified in |
| 1371 | | -** json_comand_arg(1+g.json.dispatchDepth) and searches pages for a |
| 1372 | | -** matching name. If found then that page's func() is called to fetch |
| 1373 | | -** the payload, which is returned to the caller. |
| 1374 | | -** |
| 1375 | | -** On error, g.json.resultCode is set to one of the FossilJsonCodes |
| 1376 | | -** values and NULL is returned. If non-NULL is returned, ownership is |
| 1377 | | -** transfered to the caller. |
| 1378 | | -*/ |
| 1379 | | -static cson_value * json_page_dispatch_helper(JsonPageDef const * pages){ |
| 1336 | +cson_value * json_page_dispatch_helper(JsonPageDef const * pages){ |
| 1380 | 1337 | JsonPageDef const * def; |
| 1381 | 1338 | char const * cmd = json_command_arg(1+g.json.dispatchDepth); |
| 1382 | 1339 | assert( NULL != pages ); |
| 1383 | 1340 | if( ! cmd ){ |
| 1384 | 1341 | g.json.resultCode = FSL_JSON_E_MISSING_ARGS; |
| | @@ -1400,229 +1357,10 @@ |
| 1400 | 1357 | ** |
| 1401 | 1358 | */ |
| 1402 | 1359 | static cson_value * json_page_user(){ |
| 1403 | 1360 | return json_page_dispatch_helper(&JsonPageDefs_User[0]); |
| 1404 | 1361 | } |
| 1405 | | - |
| 1406 | | -/* |
| 1407 | | -** Implements the /json/wiki family of pages/commands. |
| 1408 | | -** |
| 1409 | | -*/ |
| 1410 | | -static cson_value * json_page_wiki(){ |
| 1411 | | - return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]); |
| 1412 | | -} |
| 1413 | | - |
| 1414 | | - |
| 1415 | | -/* |
| 1416 | | -** Implementation of /json/wiki/get. |
| 1417 | | -** |
| 1418 | | -** TODO: add option to parse wiki output. It is currently |
| 1419 | | -** unparsed. |
| 1420 | | -*/ |
| 1421 | | -static cson_value * json_wiki_get(){ |
| 1422 | | - int rid; |
| 1423 | | - Manifest *pWiki = 0; |
| 1424 | | - char const * zBody = NULL; |
| 1425 | | - char const * zPageName; |
| 1426 | | - char doParse = 0/*not yet implemented*/; |
| 1427 | | - |
| 1428 | | - if( !g.perm.RdWiki ){ |
| 1429 | | - g.json.resultCode = FSL_JSON_E_DENIED; |
| 1430 | | - return NULL; |
| 1431 | | - } |
| 1432 | | - zPageName = g.isHTTP |
| 1433 | | - ? json_getenv_cstr("page") |
| 1434 | | - : find_option("page","p",1); |
| 1435 | | - if(!zPageName||!*zPageName){ |
| 1436 | | - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; |
| 1437 | | - return NULL; |
| 1438 | | - } |
| 1439 | | - rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" |
| 1440 | | - " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" |
| 1441 | | - " ORDER BY x.mtime DESC LIMIT 1", |
| 1442 | | - zPageName |
| 1443 | | - ); |
| 1444 | | - if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ |
| 1445 | | - zBody = pWiki->zWiki; |
| 1446 | | - } |
| 1447 | | - if( zBody==0 ){ |
| 1448 | | - manifest_destroy(pWiki); |
| 1449 | | - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; |
| 1450 | | - return NULL; |
| 1451 | | - }else{ |
| 1452 | | - unsigned int const len = strlen(zBody); |
| 1453 | | - cson_value * payV = cson_value_new_object(); |
| 1454 | | - cson_object * pay = cson_value_get_object(payV); |
| 1455 | | - cson_object_set(pay,"name",json_new_string(zPageName)); |
| 1456 | | - cson_object_set(pay,"version",json_new_string(pWiki->zBaseline)) |
| 1457 | | - /*FIXME: pWiki->zBaseline is NULL. How to get the version number?*/ |
| 1458 | | - ; |
| 1459 | | - cson_object_set(pay,"rid",cson_value_new_integer((cson_int_t)rid)); |
| 1460 | | - cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser)); |
| 1461 | | - cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate)); |
| 1462 | | - cson_object_set(pay,"contentLength",cson_value_new_integer((cson_int_t)len)); |
| 1463 | | - cson_object_set(pay,"contentFormat",json_new_string(doParse?"html":"raw")); |
| 1464 | | - cson_object_set(pay,"content",cson_value_new_string(zBody,len)); |
| 1465 | | - /*TODO: add 'T' (tag) fields*/ |
| 1466 | | - /*TODO: add the 'A' card (file attachment) entries?*/ |
| 1467 | | - manifest_destroy(pWiki); |
| 1468 | | - return payV; |
| 1469 | | - } |
| 1470 | | -} |
| 1471 | | - |
| 1472 | | -/* |
| 1473 | | -** Internal impl of /wiki/save and /wiki/create. If createMode is 0 |
| 1474 | | -** and the page already exists then a |
| 1475 | | -** FSL_JSON_E_RESOURCE_ALREADY_EXISTS error is triggered. If |
| 1476 | | -** createMode is false then the FSL_JSON_E_RESOURCE_NOT_FOUND is |
| 1477 | | -** triggered if the page does not already exists. |
| 1478 | | -** |
| 1479 | | -** Note that the error triggered when createMode==0 and no such page |
| 1480 | | -** exists is rather arbitrary - we could just as well create the entry |
| 1481 | | -** here if it doesn't already exist. With that, save/create would |
| 1482 | | -** become one operation. That said, i expect there are people who |
| 1483 | | -** would categorize such behaviour as "being too clever" or "doing too |
| 1484 | | -** much automatically" (and i would likely agree with them). |
| 1485 | | -*/ |
| 1486 | | -static cson_value * json_wiki_create_or_save(char createMode){ |
| 1487 | | - Blob content = empty_blob; |
| 1488 | | - cson_value * nameV; |
| 1489 | | - cson_value * contentV; |
| 1490 | | - cson_value * emptyContent = NULL; |
| 1491 | | - cson_value * payV = NULL; |
| 1492 | | - cson_object * pay = NULL; |
| 1493 | | - cson_string const * jstr = NULL; |
| 1494 | | - char const * zContent; |
| 1495 | | - char const * zBody = NULL; |
| 1496 | | - char const * zPageName; |
| 1497 | | - unsigned int contentLen = 0; |
| 1498 | | - int rid; |
| 1499 | | - if( (createMode && !g.perm.NewWiki) |
| 1500 | | - || (!createMode && !g.perm.WrWiki)){ |
| 1501 | | - g.json.resultCode = FSL_JSON_E_DENIED; |
| 1502 | | - return NULL; |
| 1503 | | - } |
| 1504 | | - nameV = json_req_payload_get("name"); |
| 1505 | | - if(!nameV){ |
| 1506 | | - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; |
| 1507 | | - goto error; |
| 1508 | | - } |
| 1509 | | - zPageName = cson_string_cstr(cson_value_get_string(nameV)); |
| 1510 | | - rid = db_int(0, |
| 1511 | | - "SELECT x.rid FROM tag t, tagxref x" |
| 1512 | | - " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" |
| 1513 | | - " ORDER BY x.mtime DESC LIMIT 1", |
| 1514 | | - zPageName |
| 1515 | | - ); |
| 1516 | | - |
| 1517 | | - if(rid){ |
| 1518 | | - if(createMode){ |
| 1519 | | - g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS; |
| 1520 | | - goto error; |
| 1521 | | - } |
| 1522 | | - }else{ |
| 1523 | | - if(!createMode){ |
| 1524 | | - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; |
| 1525 | | - goto error; |
| 1526 | | - } |
| 1527 | | - } |
| 1528 | | - |
| 1529 | | - contentV = json_req_payload_get("content"); |
| 1530 | | - if( !contentV ){ |
| 1531 | | - if( createMode ){ |
| 1532 | | - contentV = emptyContent = cson_value_new_string("",0); |
| 1533 | | - }else{ |
| 1534 | | - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; |
| 1535 | | - goto error; |
| 1536 | | - } |
| 1537 | | - } |
| 1538 | | - if( !cson_value_is_string(nameV) |
| 1539 | | - || !cson_value_is_string(contentV)){ |
| 1540 | | - g.json.resultCode = FSL_JSON_E_INVALID_ARGS; |
| 1541 | | - goto error; |
| 1542 | | - } |
| 1543 | | - jstr = cson_value_get_string(contentV); |
| 1544 | | - contentLen = (int)cson_string_length_bytes(jstr); |
| 1545 | | - if(contentLen){ |
| 1546 | | - blob_append(&content, cson_string_cstr(jstr),contentLen); |
| 1547 | | - } |
| 1548 | | - wiki_cmd_commit(zPageName, 0==rid, &content); |
| 1549 | | - blob_reset(&content); |
| 1550 | | - |
| 1551 | | - payV = cson_value_new_object(); |
| 1552 | | - pay = cson_value_get_object(payV); |
| 1553 | | - cson_object_set( pay, "name", nameV ); |
| 1554 | | - cson_object_set( pay, FossilJsonKeys.timestamp, |
| 1555 | | - json_new_timestamp(-1) ); |
| 1556 | | - |
| 1557 | | - goto ok; |
| 1558 | | - error: |
| 1559 | | - assert( 0 != g.json.resultCode ); |
| 1560 | | - cson_value_free(payV); |
| 1561 | | - payV = NULL; |
| 1562 | | - ok: |
| 1563 | | - if( emptyContent ){ |
| 1564 | | - /* We have some potentially tricky memory ownership |
| 1565 | | - here, which is why we handle emptyContent separately. |
| 1566 | | - */ |
| 1567 | | - cson_value_free(emptyContent); |
| 1568 | | - } |
| 1569 | | - return payV; |
| 1570 | | - |
| 1571 | | -} |
| 1572 | | - |
| 1573 | | -/* |
| 1574 | | -** Implementation of /json/wiki/create. |
| 1575 | | -*/ |
| 1576 | | -static cson_value * json_wiki_create(){ |
| 1577 | | - return json_wiki_create_or_save(1); |
| 1578 | | -} |
| 1579 | | - |
| 1580 | | -/* |
| 1581 | | -** Implementation of /json/wiki/save. |
| 1582 | | -*/ |
| 1583 | | -static cson_value * json_wiki_save(){ |
| 1584 | | - /* FIXME: add GET/POST.payload bool option createIfNotExists. */ |
| 1585 | | - return json_wiki_create_or_save(0); |
| 1586 | | -} |
| 1587 | | - |
| 1588 | | -/* |
| 1589 | | -** Implementation of /json/wiki/list. |
| 1590 | | -*/ |
| 1591 | | -static cson_value * json_wiki_list(){ |
| 1592 | | - cson_value * listV = NULL; |
| 1593 | | - cson_array * list = NULL; |
| 1594 | | - Stmt q; |
| 1595 | | - if( !g.perm.RdWiki ){ |
| 1596 | | - g.json.resultCode = FSL_JSON_E_DENIED; |
| 1597 | | - return NULL; |
| 1598 | | - } |
| 1599 | | - db_prepare(&q,"SELECT" |
| 1600 | | - " substr(tagname,6) as name" |
| 1601 | | - " FROM tag WHERE tagname GLOB 'wiki-*'" |
| 1602 | | - " ORDER BY lower(name)"); |
| 1603 | | - listV = cson_value_new_array(); |
| 1604 | | - list = cson_value_get_array(listV); |
| 1605 | | - while( SQLITE_ROW == db_step(&q) ){ |
| 1606 | | - cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); |
| 1607 | | - if(!v){ |
| 1608 | | - goto error; |
| 1609 | | - }else if( 0 != cson_array_append( list, v ) ){ |
| 1610 | | - cson_value_free(v); |
| 1611 | | - goto error; |
| 1612 | | - } |
| 1613 | | - } |
| 1614 | | - goto end; |
| 1615 | | - error: |
| 1616 | | - g.json.resultCode = FSL_JSON_E_UNKNOWN; |
| 1617 | | - cson_value_free(listV); |
| 1618 | | - listV = NULL; |
| 1619 | | - end: |
| 1620 | | - db_finalize(&q); |
| 1621 | | - return listV; |
| 1622 | | -} |
| 1623 | | - |
| 1624 | 1362 | |
| 1625 | 1363 | static cson_value * json_branch_list(); |
| 1626 | 1364 | /* |
| 1627 | 1365 | ** Mapping of /json/branch/XXX commands/paths to callbacks. |
| 1628 | 1366 | */ |
| | @@ -1737,490 +1475,10 @@ |
| 1737 | 1475 | } |
| 1738 | 1476 | } |
| 1739 | 1477 | return payV; |
| 1740 | 1478 | } |
| 1741 | 1479 | |
| 1742 | | -/* |
| 1743 | | -** Implements the /json/timeline family of pages/commands. Far from |
| 1744 | | -** complete. |
| 1745 | | -** |
| 1746 | | -*/ |
| 1747 | | -static cson_value * json_page_timeline(){ |
| 1748 | | - return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]); |
| 1749 | | -} |
| 1750 | | - |
| 1751 | | -/* |
| 1752 | | -** Create a temporary table suitable for storing timeline data. |
| 1753 | | -*/ |
| 1754 | | -static void json_timeline_temp_table(void){ |
| 1755 | | - /* Field order MUST match that from json_timeline_query()!!! */ |
| 1756 | | - static const char zSql[] = |
| 1757 | | - @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( |
| 1758 | | - @ sortId INTEGER PRIMARY KEY, |
| 1759 | | - @ rid INTEGER, |
| 1760 | | - @ uuid TEXT, |
| 1761 | | - @ mtime INTEGER, |
| 1762 | | - @ timestampString TEXT, |
| 1763 | | - @ comment TEXT, |
| 1764 | | - @ user TEXT, |
| 1765 | | - @ isLeaf BOOLEAN, |
| 1766 | | - @ bgColor TEXT, |
| 1767 | | - @ eventType TEXT, |
| 1768 | | - @ tags TEXT, |
| 1769 | | - @ tagId INTEGER, |
| 1770 | | - @ brief TEXT |
| 1771 | | - @ ) |
| 1772 | | - ; |
| 1773 | | - db_multi_exec(zSql); |
| 1774 | | -} |
| 1775 | | - |
| 1776 | | -/* |
| 1777 | | -** Return a pointer to a constant string that forms the basis |
| 1778 | | -** for a timeline query for the JSON interface. |
| 1779 | | -*/ |
| 1780 | | -const char const * json_timeline_query(void){ |
| 1781 | | - /* Field order MUST match that from json_timeline_temp_table()!!! */ |
| 1782 | | - static const char zBaseSql[] = |
| 1783 | | - @ SELECT |
| 1784 | | - @ NULL, |
| 1785 | | - @ blob.rid, |
| 1786 | | - @ uuid, |
| 1787 | | - @ strftime('%%s',event.mtime), |
| 1788 | | - @ datetime(event.mtime,'utc'), |
| 1789 | | - @ coalesce(ecomment, comment), |
| 1790 | | - @ coalesce(euser, user), |
| 1791 | | - @ blob.rid IN leaf, |
| 1792 | | - @ bgcolor, |
| 1793 | | - @ event.type, |
| 1794 | | - @ (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref |
| 1795 | | - @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid |
| 1796 | | - @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0), |
| 1797 | | - @ tagid, |
| 1798 | | - @ brief |
| 1799 | | - @ FROM event JOIN blob |
| 1800 | | - @ WHERE blob.rid=event.objid |
| 1801 | | - ; |
| 1802 | | - return zBaseSql; |
| 1803 | | -} |
| 1804 | | - |
| 1805 | | -/* |
| 1806 | | -** Helper for the timeline family of functions. Possibly appends 1 |
| 1807 | | -** AND clause and an ORDER BY clause to pSql, depending on the state |
| 1808 | | -** of the "after" ("a") or "before" ("b") environment parameters. |
| 1809 | | -** This function gives "after" precedence over "before", and only |
| 1810 | | -** applies one of them. |
| 1811 | | -** |
| 1812 | | -** Returns -1 if it adds a "before" clause, 1 if it adds |
| 1813 | | -** an "after" clause, and 0 if adds only an order-by clause. |
| 1814 | | -*/ |
| 1815 | | -static char json_timeline_add_time_clause(Blob *pSql){ |
| 1816 | | - char const * zAfter = NULL; |
| 1817 | | - char const * zBefore = NULL; |
| 1818 | | - if( g.isHTTP ){ |
| 1819 | | - /** |
| 1820 | | - FIXME: we are only honoring STRING values here, not int (for |
| 1821 | | - passing Unix Epoch times). |
| 1822 | | - */ |
| 1823 | | - zAfter = json_getenv_cstr("after"); |
| 1824 | | - if(!zAfter || !*zAfter){ |
| 1825 | | - zAfter = json_getenv_cstr("a"); |
| 1826 | | - } |
| 1827 | | - if(!zAfter){ |
| 1828 | | - zBefore = json_getenv_cstr("before"); |
| 1829 | | - if(!zBefore||!*zBefore){ |
| 1830 | | - zBefore = json_getenv_cstr("b"); |
| 1831 | | - } |
| 1832 | | - } |
| 1833 | | - }else{ |
| 1834 | | - zAfter = find_option("after","a",1); |
| 1835 | | - zBefore = zAfter ? NULL : find_option("before","b",1); |
| 1836 | | - } |
| 1837 | | - if(zAfter&&*zAfter){ |
| 1838 | | - while( fossil_isspace(*zAfter) ) ++zAfter; |
| 1839 | | - blob_appendf(pSql, |
| 1840 | | - " AND event.mtime>=(SELECT julianday(%Q,'utc')) " |
| 1841 | | - " ORDER BY event.mtime ASC ", |
| 1842 | | - zAfter); |
| 1843 | | - return 1; |
| 1844 | | - }else if(zBefore && *zBefore){ |
| 1845 | | - while( fossil_isspace(*zBefore) ) ++zBefore; |
| 1846 | | - blob_appendf(pSql, |
| 1847 | | - " AND event.mtime<=(SELECT julianday(%Q,'utc')) " |
| 1848 | | - " ORDER BY event.mtime DESC ", |
| 1849 | | - zBefore); |
| 1850 | | - return -1; |
| 1851 | | - }else{ |
| 1852 | | - blob_append(pSql," ORDER BY event.mtime DESC ", -1); |
| 1853 | | - return 0; |
| 1854 | | - } |
| 1855 | | -} |
| 1856 | | - |
| 1857 | | -/* |
| 1858 | | -** Tries to figure out a timeline query length limit base on |
| 1859 | | -** environment parameters. If it can it returns that value, |
| 1860 | | -** else it returns some statically defined default value. |
| 1861 | | -** |
| 1862 | | -** Never returns a negative value. 0 means no limit. |
| 1863 | | -*/ |
| 1864 | | -static int json_timeline_limit(){ |
| 1865 | | - static const int defaultLimit = 20; |
| 1866 | | - int limit = -1; |
| 1867 | | - if( g.isHTTP ){ |
| 1868 | | - limit = json_getenv_int("limit",-1); |
| 1869 | | - if(limit<0){ |
| 1870 | | - limit = json_getenv_int("n",-1); |
| 1871 | | - } |
| 1872 | | - }else{/* CLI mode */ |
| 1873 | | - char const * arg = find_option("limit","n",1); |
| 1874 | | - if(arg && *arg){ |
| 1875 | | - limit = atoi(arg); |
| 1876 | | - } |
| 1877 | | - } |
| 1878 | | - return (limit<0) ? defaultLimit : limit; |
| 1879 | | -} |
| 1880 | | - |
| 1881 | | -/* |
| 1882 | | -** Internal helper for the json_timeline_EVENTTYPE() family of |
| 1883 | | -** functions. zEventType must be one of (ci, w, t). pSql must be a |
| 1884 | | -** cleanly-initialized, empty Blob to store the sql in. If pPayload is |
| 1885 | | -** not NULL it is assumed to be the pending response payload. If |
| 1886 | | -** json_timeline_limit() returns non-0, this function adds a LIMIT |
| 1887 | | -** clause to the generated SQL and (if pPayload is not NULL) adds the |
| 1888 | | -** limit value as the "limit" property of pPayload. |
| 1889 | | -*/ |
| 1890 | | -static void json_timeline_setup_sql( char const * zEventType, |
| 1891 | | - Blob * pSql, |
| 1892 | | - cson_object * pPayload ){ |
| 1893 | | - int limit; |
| 1894 | | - assert( zEventType && *zEventType && pSql ); |
| 1895 | | - json_timeline_temp_table(); |
| 1896 | | - blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); |
| 1897 | | - blob_append(pSql, json_timeline_query(), -1 ); |
| 1898 | | - blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); |
| 1899 | | - json_timeline_add_time_clause(pSql); |
| 1900 | | - limit = json_timeline_limit(); |
| 1901 | | - if(limit){ |
| 1902 | | - blob_appendf(pSql,"LIMIT %d ",limit); |
| 1903 | | - } |
| 1904 | | - if(pPayload){ |
| 1905 | | - cson_object_set(pPayload, "limit",cson_value_new_integer(limit)); |
| 1906 | | - } |
| 1907 | | - |
| 1908 | | -} |
| 1909 | | -/* |
| 1910 | | -** Implementation of /json/timeline/ci. |
| 1911 | | -** |
| 1912 | | -** Still a few TODOs (like figuring out how to structure |
| 1913 | | -** inheritance info). |
| 1914 | | -*/ |
| 1915 | | -static cson_value * json_timeline_ci(){ |
| 1916 | | - cson_value * payV = NULL; |
| 1917 | | - cson_object * pay = NULL; |
| 1918 | | - cson_value * tmp = NULL; |
| 1919 | | - cson_value * listV = NULL; |
| 1920 | | - cson_array * list = NULL; |
| 1921 | | - int check = 0; |
| 1922 | | - Stmt q; |
| 1923 | | - Blob sql = empty_blob; |
| 1924 | | - if( !g.perm.Read/* && !g.perm.RdTkt && !g.perm.RdWiki*/ ){ |
| 1925 | | - g.json.resultCode = FSL_JSON_E_DENIED; |
| 1926 | | - return NULL; |
| 1927 | | - } |
| 1928 | | - payV = cson_value_new_object(); |
| 1929 | | - pay = cson_value_get_object(payV); |
| 1930 | | - json_timeline_setup_sql( "ci", &sql, pay ); |
| 1931 | | -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 1932 | | - g.json.resultCode = (cson_rc.AllocError==check) \ |
| 1933 | | - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 1934 | | - goto error;\ |
| 1935 | | - } |
| 1936 | | - db_multi_exec(blob_buffer(&sql)); |
| 1937 | | - |
| 1938 | | -#if 0 |
| 1939 | | - /* only for testing! */ |
| 1940 | | - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); |
| 1941 | | - SET("timelineSql"); |
| 1942 | | -#endif |
| 1943 | | - |
| 1944 | | - blob_reset(&sql); |
| 1945 | | - blob_append(&sql, "SELECT " |
| 1946 | | - " rid AS rid," |
| 1947 | | - " uuid AS uuid," |
| 1948 | | - " mtime AS timestamp," |
| 1949 | | - " timestampString AS timestampString," |
| 1950 | | - " comment AS comment, " |
| 1951 | | - " user AS user," |
| 1952 | | - " isLeaf AS isLeaf," /*FIXME: convert to JSON bool */ |
| 1953 | | - " bgColor AS bgColor," /* why always null? */ |
| 1954 | | - " eventType AS eventType," |
| 1955 | | - " tags AS tags," /*FIXME: split this into |
| 1956 | | - a JSON array*/ |
| 1957 | | - " tagId AS tagId" |
| 1958 | | - " FROM json_timeline" |
| 1959 | | - " ORDER BY sortId", |
| 1960 | | - -1); |
| 1961 | | - db_prepare(&q,blob_buffer(&sql)); |
| 1962 | | - blob_reset(&sql); |
| 1963 | | - listV = cson_value_new_array(); |
| 1964 | | - list = cson_value_get_array(listV); |
| 1965 | | - tmp = listV; |
| 1966 | | - SET("timeline"); |
| 1967 | | - while( (SQLITE_ROW == db_step(&q) )){ |
| 1968 | | - /* convert each row into a JSON object...*/ |
| 1969 | | - cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); |
| 1970 | | - cson_object * row = cson_value_get_object(rowV); |
| 1971 | | - cson_string const * tagsStr = NULL; |
| 1972 | | - if(!row){ |
| 1973 | | - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, |
| 1974 | | - "Could not convert at least one timeline result row to JSON." ); |
| 1975 | | - continue; |
| 1976 | | - } |
| 1977 | | - /* Split tags string field into JSON Array... */ |
| 1978 | | - cson_array_append(list, rowV); |
| 1979 | | - tagsStr = cson_value_get_string(cson_object_get(row,"tags")); |
| 1980 | | - if(tagsStr){ |
| 1981 | | - cson_value * tags = json_string_split2( cson_string_cstr(tagsStr), |
| 1982 | | - ',', 0); |
| 1983 | | - if( tags ){ |
| 1984 | | - if(0 != cson_object_set(row,"tags",tags)){ |
| 1985 | | - cson_value_free(tags); |
| 1986 | | - }else{ |
| 1987 | | - /*replaced/deleted old tags value, invalidating tagsStr*/; |
| 1988 | | - tagsStr = NULL; |
| 1989 | | - } |
| 1990 | | - }else{ |
| 1991 | | - json_warn(FSL_JSON_W_STRING_TO_ARRAY_FAILED, |
| 1992 | | - "Could not convert tags string to array."); |
| 1993 | | - } |
| 1994 | | - } |
| 1995 | | - |
| 1996 | | - /* replace isLeaf int w/ JSON bool */ |
| 1997 | | - tmp = cson_object_get(row,"isLeaf"); |
| 1998 | | - if(tmp && cson_value_is_integer(tmp)){ |
| 1999 | | - cson_object_set(row,"isLeaf", |
| 2000 | | - cson_value_get_integer(tmp) |
| 2001 | | - ? cson_value_true() |
| 2002 | | - : cson_value_false()); |
| 2003 | | - tmp = NULL; |
| 2004 | | - } |
| 2005 | | - } |
| 2006 | | - db_finalize(&q); |
| 2007 | | -#undef SET |
| 2008 | | - goto ok; |
| 2009 | | - error: |
| 2010 | | - assert( 0 != g.json.resultCode ); |
| 2011 | | - cson_value_free(payV); |
| 2012 | | - payV = NULL; |
| 2013 | | - ok: |
| 2014 | | - return payV; |
| 2015 | | -} |
| 2016 | | - |
| 2017 | | -/* |
| 2018 | | -** Implementation of /json/timeline/wiki. |
| 2019 | | -** |
| 2020 | | -*/ |
| 2021 | | -static cson_value * json_timeline_wiki(){ |
| 2022 | | - /* This code is 95% the same as json_timeline_ci(), by the way. */ |
| 2023 | | - cson_value * payV = NULL; |
| 2024 | | - cson_object * pay = NULL; |
| 2025 | | - cson_value * tmp = NULL; |
| 2026 | | - cson_value * listV = NULL; |
| 2027 | | - cson_array * list = NULL; |
| 2028 | | - int check = 0; |
| 2029 | | - Stmt q; |
| 2030 | | - Blob sql = empty_blob; |
| 2031 | | - if( !g.perm.Read || !g.perm.RdWiki ){ |
| 2032 | | - g.json.resultCode = FSL_JSON_E_DENIED; |
| 2033 | | - return NULL; |
| 2034 | | - } |
| 2035 | | - payV = cson_value_new_object(); |
| 2036 | | - pay = cson_value_get_object(payV); |
| 2037 | | - json_timeline_setup_sql( "w", &sql, pay ); |
| 2038 | | -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 2039 | | - g.json.resultCode = (cson_rc.AllocError==check) \ |
| 2040 | | - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 2041 | | - goto error;\ |
| 2042 | | - } |
| 2043 | | - db_multi_exec(blob_buffer(&sql)); |
| 2044 | | - |
| 2045 | | -#if 0 |
| 2046 | | - /* only for testing! */ |
| 2047 | | - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); |
| 2048 | | - SET("timelineSql"); |
| 2049 | | -#endif |
| 2050 | | - |
| 2051 | | - blob_reset(&sql); |
| 2052 | | - blob_append(&sql, "SELECT rid AS rid," |
| 2053 | | - " uuid AS uuid," |
| 2054 | | - " mtime AS timestamp," |
| 2055 | | - " timestampString AS timestampString," |
| 2056 | | - " comment AS comment, " |
| 2057 | | - " user AS user," |
| 2058 | | - " eventType AS eventType" |
| 2059 | | -#if 0 |
| 2060 | | - /* can wiki pages have tags? */ |
| 2061 | | - " tags AS tags," /*FIXME: split this into |
| 2062 | | - a JSON array*/ |
| 2063 | | - " tagId AS tagId," |
| 2064 | | -#endif |
| 2065 | | - " FROM json_timeline" |
| 2066 | | - " ORDER BY sortId", |
| 2067 | | - -1); |
| 2068 | | - db_prepare(&q, blob_buffer(&sql)); |
| 2069 | | - blob_reset(&sql); |
| 2070 | | - listV = cson_value_new_array(); |
| 2071 | | - list = cson_value_get_array(listV); |
| 2072 | | - tmp = listV; |
| 2073 | | - SET("timeline"); |
| 2074 | | - while( (SQLITE_ROW == db_step(&q) )){ |
| 2075 | | - /* convert each row into a JSON object...*/ |
| 2076 | | - cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); |
| 2077 | | - cson_object * row = cson_value_get_object(rowV); |
| 2078 | | - int rc; |
| 2079 | | - if(!row){ |
| 2080 | | - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, |
| 2081 | | - "Could not convert at least one timeline result row to JSON." ); |
| 2082 | | - continue; |
| 2083 | | - } |
| 2084 | | - rc = cson_array_append( list, rowV ); |
| 2085 | | - if( 0 != rc ){ |
| 2086 | | - cson_value_free(rowV); |
| 2087 | | - g.json.resultCode = (cson_rc.AllocError==rc) |
| 2088 | | - ? FSL_JSON_E_ALLOC |
| 2089 | | - : FSL_JSON_E_UNKNOWN; |
| 2090 | | - goto error; |
| 2091 | | - } |
| 2092 | | - } |
| 2093 | | - db_finalize(&q); |
| 2094 | | -#undef SET |
| 2095 | | - goto ok; |
| 2096 | | - error: |
| 2097 | | - assert( 0 != g.json.resultCode ); |
| 2098 | | - cson_value_free(payV); |
| 2099 | | - payV = NULL; |
| 2100 | | - ok: |
| 2101 | | - return payV; |
| 2102 | | -} |
| 2103 | | - |
| 2104 | | -/* |
| 2105 | | -** Implementation of /json/timeline/ticket. |
| 2106 | | -** |
| 2107 | | -*/ |
| 2108 | | -static cson_value * json_timeline_ticket(){ |
| 2109 | | - /* This code is 95% the same as json_timeline_ci(), by the way. */ |
| 2110 | | - cson_value * payV = NULL; |
| 2111 | | - cson_object * pay = NULL; |
| 2112 | | - cson_value * tmp = NULL; |
| 2113 | | - cson_value * listV = NULL; |
| 2114 | | - cson_array * list = NULL; |
| 2115 | | - int check = 0; |
| 2116 | | - Stmt q; |
| 2117 | | - Blob sql = empty_blob; |
| 2118 | | - if( !g.perm.Read || !g.perm.RdTkt ){ |
| 2119 | | - g.json.resultCode = FSL_JSON_E_DENIED; |
| 2120 | | - return NULL; |
| 2121 | | - } |
| 2122 | | - payV = cson_value_new_object(); |
| 2123 | | - pay = cson_value_get_object(payV); |
| 2124 | | - json_timeline_setup_sql( "t", &sql, pay ); |
| 2125 | | - db_multi_exec(blob_buffer(&sql)); |
| 2126 | | -#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ |
| 2127 | | - g.json.resultCode = (cson_rc.AllocError==check) \ |
| 2128 | | - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ |
| 2129 | | - goto error;\ |
| 2130 | | - } |
| 2131 | | - |
| 2132 | | -#if 0 |
| 2133 | | - /* only for testing! */ |
| 2134 | | - tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); |
| 2135 | | - SET("timelineSql"); |
| 2136 | | -#endif |
| 2137 | | - |
| 2138 | | - blob_reset(&sql); |
| 2139 | | - blob_append(&sql, "SELECT rid AS rid," |
| 2140 | | - " uuid AS uuid," |
| 2141 | | - " mtime AS timestamp," |
| 2142 | | - " timestampString AS timestampString," |
| 2143 | | - " user AS user," |
| 2144 | | - " eventType AS eventType," |
| 2145 | | - " comment AS comment," |
| 2146 | | - " brief AS briefComment" |
| 2147 | | - " FROM json_timeline" |
| 2148 | | - " ORDER BY sortId", |
| 2149 | | - -1); |
| 2150 | | - db_prepare(&q,blob_buffer(&sql)); |
| 2151 | | - blob_reset(&sql); |
| 2152 | | - listV = cson_value_new_array(); |
| 2153 | | - list = cson_value_get_array(listV); |
| 2154 | | - tmp = listV; |
| 2155 | | - SET("timeline"); |
| 2156 | | - while( (SQLITE_ROW == db_step(&q) )){ |
| 2157 | | - /* convert each row into a JSON object...*/ |
| 2158 | | - int rc; |
| 2159 | | - cson_value * rowV = cson_sqlite3_row_to_object(q.pStmt); |
| 2160 | | - cson_object * row = cson_value_get_object(rowV); |
| 2161 | | - if(!row){ |
| 2162 | | - json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, |
| 2163 | | - "Could not convert at least one timeline result row to JSON." ); |
| 2164 | | - continue; |
| 2165 | | - } |
| 2166 | | - rc = cson_array_append( list, rowV ); |
| 2167 | | - if( 0 != rc ){ |
| 2168 | | - cson_value_free(rowV); |
| 2169 | | - g.json.resultCode = (cson_rc.AllocError==rc) |
| 2170 | | - ? FSL_JSON_E_ALLOC |
| 2171 | | - : FSL_JSON_E_UNKNOWN; |
| 2172 | | - goto error; |
| 2173 | | - } |
| 2174 | | - } |
| 2175 | | - db_finalize(&q); |
| 2176 | | -#undef SET |
| 2177 | | - goto ok; |
| 2178 | | - error: |
| 2179 | | - assert( 0 != g.json.resultCode ); |
| 2180 | | - cson_value_free(payV); |
| 2181 | | - payV = NULL; |
| 2182 | | - ok: |
| 2183 | | - return payV; |
| 2184 | | -} |
| 2185 | | - |
| 2186 | | - |
| 2187 | | -/* |
| 2188 | | -** Implements the /json/whoami page/command. |
| 2189 | | -*/ |
| 2190 | | -static cson_value * json_page_whoami(){ |
| 2191 | | - cson_value * payload = NULL; |
| 2192 | | - cson_object * obj = NULL; |
| 2193 | | - Stmt q; |
| 2194 | | - db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid); |
| 2195 | | - if( db_step(&q)==SQLITE_ROW ){ |
| 2196 | | - |
| 2197 | | - /* reminder: we don't use g.zLogin because it's 0 for the guest |
| 2198 | | - user and the HTML UI appears to currently allow the name to be |
| 2199 | | - changed (but doing so would break other code). */ |
| 2200 | | - char const * str; |
| 2201 | | - payload = cson_value_new_object(); |
| 2202 | | - obj = cson_value_get_object(payload); |
| 2203 | | - str = (char const *)sqlite3_column_text(q.pStmt,0); |
| 2204 | | - if( str ){ |
| 2205 | | - cson_object_set( obj, "name", |
| 2206 | | - cson_value_new_string(str,strlen(str)) ); |
| 2207 | | - } |
| 2208 | | - str = (char const *)sqlite3_column_text(q.pStmt,1); |
| 2209 | | - if( str ){ |
| 2210 | | - cson_object_set( obj, "capabilities", |
| 2211 | | - cson_value_new_string(str,strlen(str)) ); |
| 2212 | | - } |
| 2213 | | - if( g.json.authToken ){ |
| 2214 | | - cson_object_set( obj, "authToken", g.json.authToken ); |
| 2215 | | - } |
| 2216 | | - }else{ |
| 2217 | | - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; |
| 2218 | | - } |
| 2219 | | - db_finalize(&q); |
| 2220 | | - return payload; |
| 2221 | | -} |
| 2222 | 1480 | |
| 2223 | 1481 | /* |
| 2224 | 1482 | ** Impl of /json/user/list. Requires admin rights. |
| 2225 | 1483 | */ |
| 2226 | 1484 | static cson_value * json_user_list(){ |
| 2227 | 1485 | |