Index: src/json.c ================================================================== --- src/json.c +++ src/json.c @@ -206,29 +206,35 @@ C(GENERIC,"Generic error"); C(INVALID_REQUEST,"Invalid request"); C(UNKNOWN_COMMAND,"Unknown Command"); C(UNKNOWN,"Unknown error"); - C(RESOURCE_NOT_FOUND,"Resource not found"); C(TIMEOUT,"Timeout reached"); C(ASSERT,"Assertion failed"); C(ALLOC,"Resource allocation failed"); C(NYI,"Not yet implemented"); + C(PANIC,"x"); + C(MANIFEST_READ_FAILED,"Reading artifact manifest failed."); + C(FILE_OPEN_FAILED,"Opening file failed."); + C(AUTH,"Authentication error"); + C(MISSING_AUTH,"Authentication info missing from request"); + C(DENIED,"Access denied"); + C(WRONG_MODE,"Request not allowed (wrong operation mode)"); C(LOGIN_FAILED,"Login failed"); C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed"); C(LOGIN_FAILED_NONAME,"Login failed - name not supplied"); C(LOGIN_FAILED_NOPW,"Login failed - password not supplied"); C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found"); - C(MISSING_AUTH,"Authentication info missing from request"); - C(DENIED,"Access denied"); - C(WRONG_MODE,"Request not allowed (wrong operation mode)"); C(USAGE,"Usage error"); - C(INVALID_ARGS,"Invalid arguments"); - C(MISSING_ARGS,"Missing arguments"); - C(AMBIGUOUS_UUID,"Argument is ambiguous"); + C(INVALID_ARGS,"Invalid argument(s)"); + C(MISSING_ARGS,"Missing argument(s)"); + C(AMBIGUOUS_UUID,"Resource identifier is ambiguous"); + C(UNRESOLVED_UUID,"Provided uuid/tag/branch could not be resolved"); + C(RESOURCE_ALREADY_EXISTS,"Resource already exists"); + C(RESOURCE_NOT_FOUND,"Resource not found"); C(DB,"Database error"); C(STMT_PREP,"Statement preparation failed"); C(STMT_BIND,"Statement parameter binding failed"); C(STMT_EXEC,"Statement execution/stepping failed"); @@ -882,18 +888,18 @@ if( once ){ return; }else{ once = 1; } + g.json.isJsonMode = 1; + g.json.resultCode = 0; + g.json.cmd.offset = -1; g.json.jsonp = PD("jsonp",NULL) /* FIXME: do some sanity checking on g.json.jsonp and ignore it if it is not halfway reasonable. */ ; - g.json.isJsonMode = 1; - g.json.resultCode = 0; - g.json.cmd.offset = -1; if( !g.isHTTP && g.fullHttpReply ){ /* workaround for server mode, so we see it as CGI mode. */ g.isHTTP = 1; } @@ -961,11 +967,11 @@ } inFile = (0==strcmp("-",jfile)) ? stdin : fopen(jfile,"rb"); if(!inFile){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; + g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED; fossil_fatal("Could not open JSON file [%s].",jfile) /* Does not return. */ ; } cgi_parse_POST_JSON(inFile, 0); @@ -1555,11 +1561,12 @@ cson_value * jv = NULL; cson_object * jo = NULL; cson_value * jv2 = NULL; cson_object * jo2 = NULL; if( !g.perm.Read ){ - g.json.resultCode = FSL_JSON_E_DENIED; + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); return NULL; } if( g.isHTTP ){ full = json_getenv_bool("full",0); }else{ @@ -1672,11 +1679,11 @@ 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; + json_set_err(FSL_JSON_E_MISSING_ARGS, "No subcommand specified."); return NULL; } def = json_handler_for_name( cmd, pages ); if(!def){ g.json.resultCode = FSL_JSON_E_UNKNOWN_COMMAND; @@ -1700,18 +1707,21 @@ /* ** Impl of /json/rebuild. Requires admin previleges. */ static cson_value * json_page_rebuild(){ if( !g.perm.Admin ){ - g.json.resultCode = FSL_JSON_E_DENIED; + json_set_err(FSL_JSON_E_DENIED,"Requires 'a' privileges."); return NULL; }else{ - /* Reminder: the db_xxx() ops "should" fail via - the fossil core error handlers, which will cause - a JSON error and exit(). i.e. we don't handle - the errors here. TODO: confirm that all these - db routine fail gracefully in JSON mode. + /* Reminder: the db_xxx() ops "should" fail via the fossil core + error handlers, which will cause a JSON error and exit(). i.e. we + don't handle the errors here. TODO: confirm that all these db + routine fail gracefully in JSON mode. + + On large repos (e.g. fossil's) this operation is likely to take + longer than the client timeout, which will cause it to fail (but + it's sqlite3, so it'll fail gracefully). */ db_close(1); db_open_repository(g.zRepositoryName); db_begin_transaction(); rebuild_db(0, 0, 0); @@ -1737,11 +1747,12 @@ " mtime AS mtime" " FROM user ORDER BY login"); payV = json_stmt_to_array_of_obj(&q, NULL); db_finalize(&q); if(NULL == payV){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; + json_set_err(FSL_JSON_E_UNKNOWN, + "Could not convert user list to JSON."); } return payV; } /* @@ -1762,11 +1773,11 @@ if we pass the name as part of the path, so we check the path _before_ checking for name=XYZ. */; } if(!pUser || !*pUser){ - g.json.resultCode = FSL_JSON_E_MISSING_ARGS; + json_set_err(FSL_JSON_E_MISSING_ARGS,"Missing 'name' property."); return NULL; } db_prepare(&q,"SELECT uid AS uid," " login AS name," " cap AS capabilities," @@ -1776,14 +1787,14 @@ " WHERE login=%Q", pUser); if( (SQLITE_ROW == db_step(&q)) ){ payV = cson_sqlite3_row_to_object(q.pStmt); if(!payV){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; + json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert user row to JSON."); } }else{ - g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,"User not found."); } db_finalize(&q); return payV; } Index: src/json_artifact.c ================================================================== --- src/json_artifact.c +++ src/json_artifact.c @@ -169,11 +169,11 @@ json_gc_add("$EVENT_TYPE_LABEL(ticket)", eventTypeLabel, 1); } pTktChng = manifest_get(rid, CFTYPE_TICKET); if( pTktChng==0 ){ - g.json.resultCode = FSL_JSON_E_UNKNOWN; + g.json.resultCode = FSL_JSON_E_MANIFEST_READ_FAILED; return NULL; } payV = cson_value_new_object(); pay = cson_value_get_object(payV); cson_object_set(pay, "eventType", eventTypeLabel ); Index: src/json_detail.h ================================================================== --- src/json_detail.h +++ src/json_detail.h @@ -36,51 +36,54 @@ ** value. ** */ enum FossilJsonCodes { FSL_JSON_W_START = 0, -FSL_JSON_W_UNKNOWN = FSL_JSON_W_START + 1, -FSL_JSON_W_ROW_TO_JSON_FAILED = FSL_JSON_W_START + 2, -FSL_JSON_W_COL_TO_JSON_FAILED = FSL_JSON_W_START + 3, -FSL_JSON_W_STRING_TO_ARRAY_FAILED = FSL_JSON_W_START + 4, +FSL_JSON_W_UNKNOWN /*+1*/, +FSL_JSON_W_ROW_TO_JSON_FAILED /*+2*/, +FSL_JSON_W_COL_TO_JSON_FAILED /*+3*/, +FSL_JSON_W_STRING_TO_ARRAY_FAILED /*+4*/, FSL_JSON_W_END = 1000, FSL_JSON_E_GENERIC = 1000, FSL_JSON_E_GENERIC_SUB1 = FSL_JSON_E_GENERIC + 100, -FSL_JSON_E_INVALID_REQUEST = FSL_JSON_E_GENERIC_SUB1 + 1, -FSL_JSON_E_UNKNOWN_COMMAND = FSL_JSON_E_GENERIC_SUB1 + 2, -FSL_JSON_E_UNKNOWN = FSL_JSON_E_GENERIC_SUB1 + 3, -FSL_JSON_E_RESOURCE_NOT_FOUND = FSL_JSON_E_GENERIC_SUB1 + 4, -FSL_JSON_E_TIMEOUT = FSL_JSON_E_GENERIC_SUB1 + 5, -FSL_JSON_E_ASSERT = FSL_JSON_E_GENERIC_SUB1 + 6, -FSL_JSON_E_ALLOC = FSL_JSON_E_GENERIC_SUB1 + 7, -FSL_JSON_E_NYI = FSL_JSON_E_GENERIC_SUB1 + 8, -FSL_JSON_E_PANIC = FSL_JSON_E_GENERIC_SUB1 + 9, +FSL_JSON_E_INVALID_REQUEST /*+1*/, +FSL_JSON_E_UNKNOWN_COMMAND /*+2*/, +FSL_JSON_E_UNKNOWN /*+3*/, +/*REUSE: +4*/ +FSL_JSON_E_TIMEOUT /*+5*/, +FSL_JSON_E_ASSERT /*+6*/, +FSL_JSON_E_ALLOC /*+7*/, +FSL_JSON_E_NYI /*+8*/, +FSL_JSON_E_PANIC /*+9*/, +FSL_JSON_E_MANIFEST_READ_FAILED /*+10*/, +FSL_JSON_E_FILE_OPEN_FAILED /*+11*/, FSL_JSON_E_AUTH = 2000, -FSL_JSON_E_MISSING_AUTH = FSL_JSON_E_AUTH + 1, -FSL_JSON_E_DENIED = FSL_JSON_E_AUTH + 2, -FSL_JSON_E_WRONG_MODE = FSL_JSON_E_AUTH + 3, -FSL_JSON_E_RESOURCE_ALREADY_EXISTS = FSL_JSON_E_AUTH + 4, - -FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH + 100, -FSL_JSON_E_LOGIN_FAILED_NOSEED = FSL_JSON_E_LOGIN_FAILED + 1, -FSL_JSON_E_LOGIN_FAILED_NONAME = FSL_JSON_E_LOGIN_FAILED + 2, -FSL_JSON_E_LOGIN_FAILED_NOPW = FSL_JSON_E_LOGIN_FAILED + 3, -FSL_JSON_E_LOGIN_FAILED_NOTFOUND = FSL_JSON_E_LOGIN_FAILED + 4, +FSL_JSON_E_MISSING_AUTH /*+1*/, +FSL_JSON_E_DENIED /*+2*/, +FSL_JSON_E_WRONG_MODE /*+3*/, + +FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH +100, +FSL_JSON_E_LOGIN_FAILED_NOSEED /*+1*/, +FSL_JSON_E_LOGIN_FAILED_NONAME /*+2*/, +FSL_JSON_E_LOGIN_FAILED_NOPW /*+3*/, +FSL_JSON_E_LOGIN_FAILED_NOTFOUND /*+4*/, FSL_JSON_E_USAGE = 3000, -FSL_JSON_E_INVALID_ARGS = FSL_JSON_E_USAGE + 1, -FSL_JSON_E_MISSING_ARGS = FSL_JSON_E_USAGE + 2, -FSL_JSON_E_AMBIGUOUS_UUID = FSL_JSON_E_USAGE + 3, - +FSL_JSON_E_INVALID_ARGS /*+1*/, +FSL_JSON_E_MISSING_ARGS /*+2*/, +FSL_JSON_E_AMBIGUOUS_UUID /*+3*/, +FSL_JSON_E_UNRESOLVED_UUID /*+4*/, +FSL_JSON_E_RESOURCE_ALREADY_EXISTS /*+5*/, +FSL_JSON_E_RESOURCE_NOT_FOUND /*+6*/, FSL_JSON_E_DB = 4000, -FSL_JSON_E_STMT_PREP = FSL_JSON_E_DB + 1, -FSL_JSON_E_STMT_BIND = FSL_JSON_E_DB + 2, -FSL_JSON_E_STMT_EXEC = FSL_JSON_E_DB + 3, -FSL_JSON_E_DB_LOCKED = FSL_JSON_E_DB + 4, +FSL_JSON_E_STMT_PREP /*+1*/, +FSL_JSON_E_STMT_BIND /*+2*/, +FSL_JSON_E_STMT_EXEC /*+3*/, +FSL_JSON_E_DB_LOCKED /*+4*/, FSL_JSON_E_DB_NEEDS_REBUILD = FSL_JSON_E_DB + 101 }; Index: src/json_timeline.c ================================================================== --- src/json_timeline.c +++ src/json_timeline.c @@ -401,18 +401,20 @@ } payV = cson_value_new_object(); pay = cson_value_get_object(payV); check = json_timeline_setup_sql( "ci", &sql, pay ); if(check){ - g.json.resultCode = check; + json_set_err(check, "Query initialization failed."); goto error; } #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ + json_set_err((cson_rc.AllocError==check) \ + ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN,\ + "Object property insertion failed"); \ goto error;\ - } + } (void)0 + #if 0 /* only for testing! */ tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); SET("timelineSql"); #endif @@ -489,19 +491,20 @@ } payV = cson_value_new_object(); pay = cson_value_get_object(payV); check = json_timeline_setup_sql( "w", &sql, pay ); if(check){ - g.json.resultCode = check; + json_set_err(check, "Query initialization failed."); goto error; } #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ + json_set_err((cson_rc.AllocError==check) \ + ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \ + "Object property insertion failed."); \ goto error;\ - } + } (void)0 #if 0 /* only for testing! */ tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); SET("timelineSql"); #endif @@ -562,28 +565,34 @@ } payV = cson_value_new_object(); pay = cson_value_get_object(payV); check = json_timeline_setup_sql( "t", &sql, pay ); if(check){ - g.json.resultCode = check; + json_set_err(check, "Query initialization failed."); goto error; } db_multi_exec(blob_buffer(&sql)); #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ - g.json.resultCode = (cson_rc.AllocError==check) \ - ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; \ + json_set_err((cson_rc.AllocError==check) \ + ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \ + "Object property insertion failed."); \ goto error;\ - } + } (void)0 #if 0 /* only for testing! */ tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); SET("timelineSql"); #endif blob_reset(&sql); + /* + REMINDER/FIXME(?): we have both uuid (the change uuid?) and + ticketUuid (the actual ticket). This is different from the wiki + timeline, where we only have the wiki page uuid. + */ db_prepare(&q, "SELECT rid AS rid," " uuid AS uuid," " mtime AS timestamp," #if 0 " timestampString AS timestampString," Index: src/json_wiki.c ================================================================== --- src/json_wiki.c +++ src/json_wiki.c @@ -47,10 +47,79 @@ */ cson_value * json_page_wiki(){ return json_page_dispatch_helper(&JsonPageDefs_Wiki[0]); } + +/* +** Loads the given wiki page and creates a JSON object representation +** of it. If the page is not found then NULL is returned. If doParse +** is true then the page content is HTML-ized using fossil's +** conventional wiki format, else it is not parsed. +** +** The returned value, if not NULL, is-a JSON Object owned by the +** caller. +*/ +cson_value * json_get_wiki_page(char const * zPageName, char doParse){ + int rid; + Manifest *pWiki = 0; + char const * zBody = NULL; + char const * zFormat = NULL; + char * zUuid = NULL; + Stmt q; + db_prepare(&q, + "SELECT x.rid, b.uuid FROM tag t, tagxref x, blob b" + " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q' " + " AND b.rid=x.rid" + " ORDER BY x.mtime DESC LIMIT 1", + zPageName + ); + if( (SQLITE_ROW != db_step(&q)) ){ + return NULL; + } + rid = db_column_int(&q,0); + zUuid = db_column_malloc(&q,1); + db_finalize(&q); + if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ + zBody = pWiki->zWiki; + } + + { + unsigned int len; + cson_value * payV = cson_value_new_object(); + cson_object * pay = cson_value_get_object(payV); + cson_object_set(pay,"name",json_new_string(zPageName)); + cson_object_set(pay,"uuid",json_new_string(zUuid)); + free(zUuid); + zUuid = NULL; + cson_object_set(pay,"rid",json_new_int((cson_int_t)rid)); + cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser)); + cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate)); + cson_object_set(pay,"contentFormat",json_new_string(zFormat)); + if( doParse ){ + Blob content = empty_blob; + Blob raw = empty_blob; + blob_append(&raw,zBody,-1); + wiki_convert(&raw,&content,0); + len = strlen(zBody); + len = (unsigned int)blob_size(&content); + cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len)); + cson_object_set(pay,"content", + cson_value_new_string(blob_buffer(&content),len)); + blob_reset(&content); + blob_reset(&raw); + }else{ + len = zBody ? strlen(zBody) : 0; + cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len)); + cson_object_set(pay,"content",cson_value_new_string(zBody,len)); + } + /*TODO: add 'T' (tag) fields*/ + /*TODO: add the 'A' card (file attachment) entries?*/ + manifest_destroy(pWiki); + return payV; + } +} /* ** Implementation of /json/wiki/get. ** */ @@ -89,72 +158,11 @@ zFormat = "raw"; } if( 'r' != *zFormat ){ zFormat = "html"; } - db_prepare(&q, - "SELECT x.rid, b.uuid FROM tag t, tagxref x, blob b" - " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q' " - " AND b.rid=x.rid" - " ORDER BY x.mtime DESC LIMIT 1", - zPageName - ); - if( (SQLITE_ROW != db_step(&q)) ){ - manifest_destroy(pWiki); - json_set_err(FSL_JSON_E_UNKNOWN, - "Error reading wiki page manifest."); - return NULL; - } - rid = db_column_int(&q,0); - zUuid = db_column_malloc(&q,1); - db_finalize(&q); - - if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ - zBody = pWiki->zWiki; - } - if( zBody==0 ){ - manifest_destroy(pWiki); - free(zUuid); - json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, - "Wiki body is empty (is that possible?)"); - return NULL; - } - - { - unsigned int len; - cson_value * payV = cson_value_new_object(); - cson_object * pay = cson_value_get_object(payV); - cson_object_set(pay,"name",json_new_string(zPageName)); - cson_object_set(pay,"uuid",json_new_string(zUuid)); - free(zUuid); - zUuid = NULL; - cson_object_set(pay,"rid",json_new_int((cson_int_t)rid)); - cson_object_set(pay,"lastSavedBy",json_new_string(pWiki->zUser)); - cson_object_set(pay,FossilJsonKeys.timestamp, json_julian_to_timestamp(pWiki->rDate)); - cson_object_set(pay,"contentFormat",json_new_string(zFormat)); - if( ('h'==*zFormat) ){ - Blob content = empty_blob; - Blob raw = empty_blob; - blob_append(&raw,zBody,-1); - wiki_convert(&raw,&content,0); - len = strlen(zBody); - len = (unsigned int)blob_size(&content); - cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len)); - cson_object_set(pay,"content", - cson_value_new_string(blob_buffer(&content),len)); - blob_reset(&content); - blob_reset(&raw); - }else{ - len = strlen(zBody); - cson_object_set(pay,"contentLength",json_new_int((cson_int_t)len)); - cson_object_set(pay,"content",cson_value_new_string(zBody,len)); - } - /*TODO: add 'T' (tag) fields*/ - /*TODO: add the 'A' card (file attachment) entries?*/ - manifest_destroy(pWiki); - return payV; - } + return json_get_wiki_page(zPageName, 'h'==*zFormat); } /* ** Internal impl of /wiki/save and /wiki/create. If createMode is 0 ** and the page already exists then a @@ -306,21 +314,27 @@ listV = cson_value_new_array(); list = cson_value_get_array(listV); while( SQLITE_ROW == db_step(&q) ){ cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); if(!v){ + json_set_err(FSL_JSON_E_UNKNOWN, + "Could not convert wiki name column to JSON."); goto error; }else if( 0 != cson_array_append( list, v ) ){ cson_value_free(v); + json_set_err(FSL_JSON_E_ALLOC,"Could not append wiki page name to array.") + /* OOM (or maybe numeric overflow) are the only realistic + error codes for that particular failure.*/; goto error; } } goto end; error: + assert(0 != g.json.resultCode); cson_value_free(listV); listV = NULL; json_set_err(FSL_JSON_E_UNKNOWN, "Error creating wiki page list."); end: db_finalize(&q); return listV; }