/* ** Very incomplete/incorrect impl of /json/artifact/TICKET_ID. */ cson_value * json_artifact_ticket( cson_object * zParent, int rid ){ cson_object * pay = NULL; Manifest *pTktChng = NULL; static cson_value * eventTypeLabel = NULL; if(! g.perm.RdTkt ){ g.json.resultCode = FSL_JSON_E_DENIED; return NULL; } if(!eventTypeLabel){ eventTypeLabel = json_new_string("ticket"); json_gc_add("$EVENT_TYPE_LABEL(ticket)", eventTypeLabel); } pTktChng = manifest_get(rid, CFTYPE_TICKET); if( pTktChng==0 ){ g.json.resultCode = FSL_JSON_E_MANIFEST_READ_FAILED; return NULL; } pay = cson_new_object(); cson_object_set(pay, "eventType", eventTypeLabel ); cson_object_set(pay, "uuid", json_new_string(pTktChng->zTicketUuid)); cson_object_set(pay, "user", json_new_string(pTktChng->zUser)); cson_object_set(pay, "timestamp", json_julian_to_timestamp(pTktChng->rDate)); manifest_destroy(pTktChng); return cson_object_value(pay); }
/* ** Implementation of /json/timeline/wiki. ** */ 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_array * list = NULL; int check = 0; Stmt q = empty_Stmt; Blob sql = empty_blob; if( !g.perm.RdWiki && !g.perm.Read ){ json_set_err( FSL_JSON_E_DENIED, "Wiki timeline requires 'o' or 'j' access."); return NULL; } payV = cson_value_new_object(); pay = cson_value_get_object(payV); check = json_timeline_setup_sql( "w", &sql, pay ); if(check){ json_set_err(check, "Query initialization failed."); goto error; } #if 0 /* only for testing! */ cson_object_set(pay, "timelineSql", cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)))); #endif db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); blob_reset(&sql); db_prepare(&q, "SELECT" " uuid AS uuid," " mtime AS timestamp," #if 0 " timestampString AS timestampString," #endif " comment AS comment, " " user AS user," " eventType AS eventType" #if 0 /* can wiki pages have tags? */ " tags AS tags," /*FIXME: split this into a JSON array*/ " tagId AS tagId," #endif " FROM json_timeline" " ORDER BY rowid"); list = cson_new_array(); json_stmt_to_array_of_obj(&q, list); cson_object_set(pay, "timeline", cson_array_value(list)); goto ok; error: assert( 0 != g.json.resultCode ); cson_value_free(payV); payV = NULL; ok: db_finalize(&q); blob_reset(&sql); return payV; }
/* ** 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/diff page. ** ** Arguments: ** ** v1=1st version to diff ** v2=2nd version to diff ** ** Can come from GET, POST.payload, CLI -v1/-v2 or as positional ** parameters following the command name (in HTTP and CLI modes). ** */ cson_value * json_page_diff(){ cson_object * pay = NULL; cson_value * v = NULL; char const * zFrom; char const * zTo; int nContext = 0; char doSBS; char doHtml; if(!g.perm.Read){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); return NULL; } zFrom = json_find_option_cstr("v1",NULL,NULL); if(!zFrom){ zFrom = json_command_arg(2); } if(!zFrom){ json_set_err(FSL_JSON_E_MISSING_ARGS, "Required 'v1' parameter is missing."); return NULL; } zTo = json_find_option_cstr("v2",NULL,NULL); if(!zTo){ zTo = json_command_arg(3); } if(!zTo){ json_set_err(FSL_JSON_E_MISSING_ARGS, "Required 'v2' parameter is missing."); return NULL; } nContext = json_find_option_int("context",NULL,"c",5); doSBS = json_find_option_bool("sbs",NULL,"y",0); doHtml = json_find_option_bool("html",NULL,"h",0); v = json_generate_diff(zFrom, zTo, nContext, doSBS, doHtml); if(!v){ if(!g.json.resultCode){ json_set_err(FSL_JSON_E_UNKNOWN, "Generating diff failed for unknown reason."); } return NULL; } pay = cson_new_object(); cson_object_set(pay, "from", json_new_string(zFrom)); cson_object_set(pay, "to", json_new_string(zTo)); cson_object_set(pay, "diff", v); v = 0; return pay ? cson_object_value(pay) : NULL; }
/* ** Implements the /json/whoami page/command. */ cson_value * json_page_whoami(){ cson_value * payload = NULL; cson_object * obj = NULL; Stmt q; if(!g.json.authToken){ /* assume we just logged out. */ db_prepare(&q, "SELECT login, cap FROM user WHERE login='******'"); } else{ 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 user and the HTML UI appears to currently allow the name to be changed (but doing so would break other code). */ char const * str; payload = cson_value_new_object(); obj = cson_value_get_object(payload); str = (char const *)sqlite3_column_text(q.pStmt,0); if( str ){ cson_object_set( obj, "name", cson_value_new_string(str,strlen(str)) ); } str = (char const *)sqlite3_column_text(q.pStmt,1); if( str ){ cson_object_set( obj, "capabilities", cson_value_new_string(str,strlen(str)) ); } if( g.json.authToken ){ cson_object_set( obj, "authToken", g.json.authToken ); } }else{ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; } db_finalize(&q); return payload; }
/* ** If any files are associated with the given rid, a JSON array ** containing information about them is returned (and is owned by the ** caller). If no files are associated with it then NULL is returned. ** ** flags may optionally be a bitmask of json_get_changed_files flags, ** or 0 for defaults. */ cson_value * json_get_changed_files(int rid, int flags){ cson_value * rowsV = NULL; cson_array * rows = NULL; Stmt q = empty_Stmt; db_prepare(&q, "SELECT (pid==0) AS isnew," " (fid==0) AS isdel," " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," " blob.uuid as uuid," " (SELECT uuid FROM blob WHERE rid=pid) as parent," " blob.size as size" " FROM mlink, blob" " WHERE mid=%d AND pid!=fid" " AND blob.rid=fid AND NOT mlink.isaux" " ORDER BY name /*sort*/", rid ); while( (SQLITE_ROW == db_step(&q)) ){ cson_value * rowV = cson_value_new_object(); cson_object * row = cson_value_get_object(rowV); int const isNew = db_column_int(&q,0); int const isDel = db_column_int(&q,1); char * zDownload = NULL; if(!rowsV){ rowsV = cson_value_new_array(); rows = cson_value_get_array(rowsV); } cson_array_append( rows, rowV ); cson_object_set(row, "name", json_new_string(db_column_text(&q,2))); cson_object_set(row, "uuid", json_new_string(db_column_text(&q,3))); if(!isNew && (flags & json_get_changed_files_ELIDE_PARENT)){ cson_object_set(row, "parent", json_new_string(db_column_text(&q,4))); } cson_object_set(row, "size", json_new_int(db_column_int(&q,5))); cson_object_set(row, "state", json_new_string(json_artifact_status_to_string(isNew,isDel))); zDownload = mprintf("/raw/%s?name=%s", /* reminder: g.zBaseURL is of course not set for CLI mode. */ db_column_text(&q,2), db_column_text(&q,3)); cson_object_set(row, "downloadPath", json_new_string(zDownload)); free(zDownload); } db_finalize(&q); return rowsV; }
/* ** Internal helper for the json_timeline_EVENTTYPE() family of ** functions. zEventType must be one of (ci, w, t). pSql must be a ** cleanly-initialized, empty Blob to store the sql in. If pPayload is ** not NULL it is assumed to be the pending response payload. If ** json_timeline_limit() returns non-0, this function adds a LIMIT ** clause to the generated SQL. ** ** If pPayload is not NULL then this might add properties to pPayload, ** reflecting options set in the request environment. ** ** Returns 0 on success. On error processing should not continue and ** the returned value should be used as g.json.resultCode. */ static int json_timeline_setup_sql( char const * zEventType, Blob * pSql, cson_object * pPayload ){ int limit; assert( zEventType && *zEventType && pSql ); json_timeline_temp_table(); blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); blob_append(pSql, json_timeline_query(), -1 ); blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){ return FSL_JSON_E_INVALID_ARGS; } json_timeline_add_time_clause(pSql); limit = json_timeline_limit(20); if(limit>0){ blob_appendf(pSql,"LIMIT %d ",limit); } if(pPayload){ cson_object_set(pPayload, "limit", json_new_int(limit)); } return 0; }
/* ** Expects pUser to contain fossil user fields in JSON form: name, ** uid, info, capabilities, password. ** ** At least one of (name, uid) must be included. All others are ** optional and their db fields will not be updated if those fields ** are not included in pUser. ** ** If uid is specified then name may refer to a _new_ name ** for a user, otherwise the name must refer to an existing user. ** If uid=-1 then the name must be specified and a new user is ** created (fails if one already exists). ** ** If uid is not set, this function might modify pUser to contain the ** db-found (or inserted) user ID. ** ** On error g.json's error state is set and one of the FSL_JSON_E_xxx ** values from FossilJsonCodes is returned. ** ** On success the db record for the given user is updated. ** ** Requires either Admin, Setup, or Password access. Non-admin/setup ** users can only change their own information. Non-setup users may ** not modify the 's' permission. Admin users without setup ** permissions may not edit any other user who has the 's' permission. ** */ int json_user_update_from_json( cson_object * pUser ){ #define CSTR(X) cson_string_cstr(cson_value_get_string( cson_object_get(pUser, X ) )) char const * zName = CSTR("name"); char const * zNameNew = zName; char * zNameFree = NULL; char const * zInfo = CSTR("info"); char const * zCap = CSTR("capabilities"); char const * zPW = CSTR("password"); cson_value const * forceLogout = cson_object_get(pUser, "forceLogout"); int gotFields = 0; #undef CSTR cson_int_t uid = cson_value_get_integer( cson_object_get(pUser, "uid") ); char const tgtHasSetup = zCap && (NULL!=strchr(zCap, 's')); char tgtHadSetup = 0; Blob sql = empty_blob; Stmt q = empty_Stmt; #if 0 if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){ return json_set_err( FSL_JSON_E_DENIED, "Password change requires 'a', 's', " "or 'p' permissions."); } #endif if(uid<=0 && (!zName||!*zName)){ return json_set_err(FSL_JSON_E_MISSING_ARGS, "One of 'uid' or 'name' is required."); }else if(uid>0){ zNameFree = db_text(NULL, "SELECT login FROM user WHERE uid=%d",uid); if(!zNameFree){ return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, "No login found for uid %d.", uid); } zName = zNameFree; }else if(-1==uid){ /* try to create a new user */ if(!g.perm.Admin && !g.perm.Setup){ json_set_err(FSL_JSON_E_DENIED, "Requires 'a' or 's' privileges."); goto error; }else if(!zName || !*zName){ json_set_err(FSL_JSON_E_MISSING_ARGS, "No name specified for new user."); goto error; }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zName) ){ json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS, "User %s already exists.", zName); goto error; }else{ Stmt ins = empty_Stmt; db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName); db_step( &ins ); db_finalize(&ins); uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName); assert(uid>0); zNameNew = zName; cson_object_set( pUser, "uid", cson_value_new_integer(uid) ); } }else{ uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName); if(uid<=0){ json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, "No login found for user [%s].", zName); goto error; } cson_object_set( pUser, "uid", cson_value_new_integer(uid) ); } /* Maintenance note: all error-returns from here on out should go via 'goto error' in order to clean up. */ if(uid != g.userUid){ if(!g.perm.Admin && !g.perm.Setup){ json_set_err(FSL_JSON_E_DENIED, "Changing another user's data requires " "'a' or 's' privileges."); goto error; } } /* check if the target uid currently has setup rights. */ tgtHadSetup = db_int(0,"SELECT 1 FROM user where uid=%d" " AND cap GLOB '*s*'", uid); if((tgtHasSetup || tgtHadSetup) && !g.perm.Setup){ /* Do not allow a non-setup user to set or remove setup privileges. setup.c uses similar logic. */ json_set_err(FSL_JSON_E_DENIED, "Modifying 's' users/privileges requires " "'s' privileges."); goto error; } /* Potential todo: do not allow a setup user to remove 's' from himself, to avoid locking himself out? */ blob_append(&sql, "UPDATE user SET",-1 ); blob_append(&sql, " mtime=cast(strftime('%s') AS INTEGER)", -1); if((uid>0) && zNameNew){ /* Check for name change... */ if(0!=strcmp(zName,zNameNew)){ if( (!g.perm.Admin && !g.perm.Setup) && (zName != zNameNew)){ json_set_err( FSL_JSON_E_DENIED, "Modifying user names requires 'a' or 's' privileges."); goto error; } forceLogout = cson_value_true() /* reminders: 1) does not allocate. 2) we do this because changing a name invalidates any login token because the old name is part of the token hash. */; blob_append_sql(&sql, ", login=%Q", zNameNew); ++gotFields; } } if( zCap && *zCap ){ if(!g.perm.Admin || !g.perm.Setup){ /* we "could" arguably silently ignore cap in this case. */ json_set_err(FSL_JSON_E_DENIED, "Changing capabilities requires 'a' or 's' privileges."); goto error; } blob_append_sql(&sql, ", cap=%Q", zCap); ++gotFields; } if( zPW && *zPW ){ if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){ json_set_err( FSL_JSON_E_DENIED, "Password change requires 'a', 's', " "or 'p' permissions."); goto error; }else{ #define TRY_LOGIN_GROUP 0 /* login group support is not yet implemented. */ #if !TRY_LOGIN_GROUP char * zPWHash = NULL; ++gotFields; zPWHash = sha1_shared_secret(zPW, zNameNew ? zNameNew : zName, NULL); blob_append_sql(&sql, ", pw=%Q", zPWHash); free(zPWHash); #else ++gotFields; blob_append_sql(&sql, ", pw=coalesce(shared_secret(%Q,%Q," "(SELECT value FROM config WHERE name='project-code')))", zPW, zNameNew ? zNameNew : zName); /* shared_secret() func is undefined? */ #endif } } if( zInfo ){ blob_append_sql(&sql, ", info=%Q", zInfo); ++gotFields; } if((g.perm.Admin || g.perm.Setup) && forceLogout && cson_value_get_bool(forceLogout)){ blob_append(&sql, ", cookie=NULL, cexpire=NULL", -1); ++gotFields; } if(!gotFields){ json_set_err( FSL_JSON_E_MISSING_ARGS, "Required user data are missing."); goto error; } assert(uid>0); #if !TRY_LOGIN_GROUP blob_append_sql(&sql, " WHERE uid=%d", uid); #else /* need name for login group support :/ */ blob_append_sql(&sql, " WHERE login=%Q", zName); #endif #if 0 puts(blob_str(&sql)); cson_output_FILE( cson_object_value(pUser), stdout, NULL ); #endif db_prepare(&q, "%s", blob_sql_text(&sql)); db_exec(&q); db_finalize(&q); #if TRY_LOGIN_GROUP if( zPW || cson_value_get_bool(forceLogout) ){ Blob groupSql = empty_blob; char * zErr = NULL; blob_append_sql(&groupSql, "INSERT INTO user(login)" " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", zName, zName ); blob_append(&groupSql, blob_str(&sql), blob_size(&sql)); login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr); blob_reset(&groupSql); if( zErr ){ json_set_err( FSL_JSON_E_UNKNOWN, "Repo-group update at least partially failed: %s", zErr); free(zErr); goto error; } } #endif /* TRY_LOGIN_GROUP */ #undef TRY_LOGIN_GROUP free( zNameFree ); blob_reset(&sql); return 0; error: assert(0 != g.json.resultCode); free(zNameFree); blob_reset(&sql); return g.json.resultCode; }
/* ** Implementation of the /json/status page. ** */ cson_value * json_page_status(){ Stmt q = empty_Stmt; cson_object * oPay; /*cson_object * files;*/ int vid, nErr = 0; cson_object * tmpO; char * zTmp; i64 iMtime; cson_array * aFiles; if(!db_open_local(0)){ json_set_err(FSL_JSON_E_DB_NEEDS_CHECKOUT, NULL); return NULL; } oPay = cson_new_object(); cson_object_set(oPay, "repository", json_new_string(db_repository_filename())); cson_object_set(oPay, "localRoot", json_new_string(g.zLocalRoot)); vid = db_lget_int("checkout", 0); if(!vid){ json_set_err( FSL_JSON_E_UNKNOWN, "Can this even happen?" ); return 0; } /* TODO: dupe show_common_info() state */ tmpO = cson_new_object(); cson_object_set(oPay, "checkout", cson_object_value(tmpO)); zTmp = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); cson_object_set(tmpO, "uuid", json_new_string(zTmp) ); free(zTmp); cson_object_set( tmpO, "tags", json_tags_for_checkin_rid(vid, 0) ); /* FIXME: optimize the datetime/timestamp queries into 1 query. */ zTmp = db_text(0, "SELECT datetime(mtime) || " "' UTC' FROM event WHERE objid=%d", vid); cson_object_set(tmpO, "datetime", json_new_string(zTmp)); free(zTmp); iMtime = db_int64(0, "SELECT CAST(strftime('%%s',mtime) AS INTEGER) " "FROM event WHERE objid=%d", vid); cson_object_set(tmpO, "timestamp", cson_value_new_integer((cson_int_t)iMtime)); #if 0 /* TODO: add parent artifact info */ tmpO = cson_new_object(); cson_object_set( oPay, "parent", cson_object_value(tmpO) ); cson_object_set( tmpO, "uuid", TODO ); cson_object_set( tmpO, "timestamp", TODO ); #endif /* Now get the list of non-pristine files... */ aFiles = cson_new_array(); cson_object_set( oPay, "files", cson_array_value( aFiles ) ); db_prepare(&q, "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)" " FROM vfile " " WHERE is_selected(id)" " AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1" ); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isChnged = db_column_int(&q,2); int isNew = db_column_int(&q,3)==0; int isRenamed = db_column_int(&q,4); cson_object * oFile; char const * zStatus = "???"; char * zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); if( isDeleted ){ zStatus = "deleted"; }else if( isNew ){ zStatus = "new" /* maintenance reminder: MUST come BEFORE the isChnged checks. */; }else if( isRenamed ){ zStatus = "renamed"; }else if( !file_wd_isfile_or_link(zFullName) ){ if( file_access(zFullName, F_OK)==0 ){ zStatus = "notAFile"; ++nErr; }else{ zStatus = "missing"; ++nErr; } }else if( 2==isChnged ){ zStatus = "updatedByMerge"; }else if( 3==isChnged ){ zStatus = "addedByMerge"; }else if( 4==isChnged ){ zStatus = "updatedByIntegrate"; }else if( 5==isChnged ){ zStatus = "addedByIntegrate"; }else if( 1==isChnged ){ if( file_contains_merge_marker(zFullName) ){ zStatus = "conflict"; }else{ zStatus = "edited"; } } oFile = cson_new_object(); cson_array_append( aFiles, cson_object_value(oFile) ); /* optimization potential: move these keys into cson_strings to take advantage of refcounting. */ cson_object_set( oFile, "name", json_new_string( zPathname ) ); cson_object_set( oFile, "status", json_new_string( zStatus ) ); free(zFullName); } cson_object_set( oPay, "errorCount", json_new_int( nErr ) ); db_finalize(&q); #if 0 /* TODO: add "merged with" status. First need (A) to decide on a structure and (B) to set up some tests for the multi-merge case.*/ db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" " WHERE id<=0"); while( db_step(&q)==SQLITE_ROW ){ const char *zLabel = "MERGED_WITH"; switch( db_column_int(&q, 1) ){ case -1: zLabel = "CHERRYPICK "; break; case -2: zLabel = "BACKOUT "; break; case -4: zLabel = "INTEGRATE "; break; } blob_append(report, zPrefix, nPrefix); blob_appendf(report, "%s %s\n", zLabel, db_column_text(&q, 0)); } db_finalize(&q); if( nErr ){ fossil_fatal("aborting due to prior errors"); } #endif return cson_object_value( oPay ); }
/* ** Implementation of the /json/login page. ** */ cson_value * json_page_login(){ char preciseErrors = /* if true, "complete" JSON error codes are used, else they are "dumbed down" to a generic login error code. */ #if 1 g.json.errorDetailParanoia ? 0 : 1 #else 0 #endif ; /* FIXME: we want to check the GET/POST args in this order: - GET: name, n, password, p - POST: name, password but a bug in cgi_parameter() is breaking that, causing PD() to return the last element of the PATH_INFO instead. Summary: If we check for P("name") first, then P("n"), then ONLY a GET param of "name" will match ("n" is not recognized). If we reverse the order of the checks then both forms work. Strangely enough, the "p"/"password" check is not affected by this. */ char const * name = cson_value_get_cstr(json_req_payload_get("name")); char const * pw = NULL; char const * anonSeed = NULL; cson_value * payload = NULL; int uid = 0; /* reminder to self: Fossil internally (for the sake of /wiki) interprets paths in the form /foo/bar/baz such that P("name") == "bar/baz". This collides with our name/password checking, and thus we do some rather elaborate name=... checking. */ pw = cson_value_get_cstr(json_req_payload_get("password")); if( !pw ){ pw = PD("p",NULL); if( !pw ){ pw = PD("password",NULL); } } if(!pw){ g.json.resultCode = preciseErrors ? FSL_JSON_E_LOGIN_FAILED_NOPW : FSL_JSON_E_LOGIN_FAILED; return NULL; } if( !name ){ name = PD("n",NULL); if( !name ){ name = PD("name",NULL); if( !name ){ g.json.resultCode = preciseErrors ? FSL_JSON_E_LOGIN_FAILED_NONAME : FSL_JSON_E_LOGIN_FAILED; return NULL; } } } if(0 == strcmp("anonymous",name)){ /* check captcha/seed values... */ enum { SeedBufLen = 100 /* in some JSON tests i once actually got an 80-digit number. */ }; static char seedBuffer[SeedBufLen]; cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed); seedBuffer[0] = 0; if( !jseed ){ jseed = json_req_payload_get(FossilJsonKeys.anonymousSeed); if( !jseed ){ jseed = json_getenv("cs") /* name used by HTML interface */; } } if(jseed){ if( cson_value_is_number(jseed) ){ sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed)); anonSeed = seedBuffer; }else if( cson_value_is_string(jseed) ){ anonSeed = cson_string_cstr(cson_value_get_string(jseed)); } } if(!anonSeed){ g.json.resultCode = preciseErrors ? FSL_JSON_E_LOGIN_FAILED_NOSEED : FSL_JSON_E_LOGIN_FAILED; return NULL; } } #if 0 { /* only for debugging the PD()-incorrect-result problem */ cson_object * o = NULL; uid = login_search_uid( name, pw ); payload = cson_value_new_object(); o = cson_value_get_object(payload); cson_object_set( o, "n", cson_value_new_string(name,strlen(name))); cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw))); return payload; } #endif uid = anonSeed ? login_is_valid_anonymous(name, pw, anonSeed) : login_search_uid(name, pw) ; if( !uid ){ g.json.resultCode = preciseErrors ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND : FSL_JSON_E_LOGIN_FAILED; return NULL; }else{ char * cookie = NULL; cson_object * po; char * cap = NULL; if(anonSeed){ login_set_anon_cookie(NULL, &cookie); }else{ login_set_user_cookie(name, uid, &cookie); } payload = cson_value_new_object(); po = cson_value_get_object(payload); cson_object_set(po, "authToken", json_new_string(cookie)); free(cookie); cson_object_set(po, "name", json_new_string(name)); cap = db_text(NULL, "SELECT cap FROM user WHERE login=%Q", name); cson_object_set(po, "capabilities", cap ? json_new_string(cap) : cson_value_null() ); free(cap); cson_object_set(po, "loginCookieName", json_new_string( login_cookie_name() ) ); /* TODO: add loginExpiryTime to the payload. To do this properly we "should" add an ([unsigned] int *) to login_set_user_cookie() and login_set_anon_cookie(), to which the expiry time is assigned. (Remember that JSON doesn't do unsigned int.) For non-anonymous users we could also simply query the user.cexpire db field after calling login_set_user_cookie(), but for anonymous we need to get the time when the cookie is set because anon does not get a db entry like normal users do. Anonymous cookies currently have a hard-coded lifetime in login_set_anon_cookie() (currently 6 hours), which we "should arguably" change to use the time configured for non-anonymous users (see login_set_user_cookie() for details). */ return payload; } }
/* ** Impl of /json/artifact. This basically just determines the type of ** an artifact and forwards the real work to another function. */ cson_value * json_page_artifact(){ cson_object * pay = NULL; char const * zName = NULL; char const * zType = NULL; char const * zUuid = NULL; cson_value * entry = NULL; Blob uuid = empty_blob; int rc; int rid = 0; ArtifactDispatchEntry const * dispatcher = &ArtifactDispatchList[0]; zName = json_find_option_cstr2("name", NULL, NULL, g.json.dispatchDepth+1); if(!zName || !*zName) { json_set_err(FSL_JSON_E_MISSING_ARGS, "Missing 'name' argument."); return NULL; } if( validate16(zName, strlen(zName)) ){ if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ zType = "ticket"; goto handle_entry; } if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){ zType = "tag"; goto handle_entry; } } blob_set(&uuid,zName); rc = name_to_uuid(&uuid,-1,"*"); /* FIXME: check for a filename if all else fails. */ if(1==rc){ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; }else if(2==rc){ g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID; goto error; } zUuid = blob_str(&uuid); rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid); if(0==rid){ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; } if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) || db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) || db_exists("SELECT 1 FROM plink WHERE pid=%d", rid)){ zType = "checkin"; goto handle_entry; }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){ zType = "wiki"; goto handle_entry; }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){ zType = "ticket"; goto handle_entry; }else if ( db_exists("SELECT 1 FROM mlink WHERE fid = %d", rid) ){ zType = "file"; goto handle_entry; }else{ g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; goto error; } error: assert( 0 != g.json.resultCode ); goto veryend; handle_entry: pay = cson_new_object(); assert( (NULL != zType) && "Internal dispatching error." ); for( ; dispatcher->name; ++dispatcher ){ if(0!=strcmp(dispatcher->name, zType)){ continue; }else{ entry = (*dispatcher->func)(pay, rid); break; } } if(!g.json.resultCode){ assert( NULL != entry ); assert( NULL != zType ); cson_object_set( pay, "type", json_new_string(zType) ); cson_object_set( pay, "uuid", json_new_string(zUuid) ); /*cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );*/ /*cson_object_set( pay, "rid", cson_value_new_integer(rid) );*/ if(cson_value_is_object(entry) && (cson_value_get_object(entry) != pay)){ cson_object_set(pay, "artifact", entry); } } veryend: blob_reset(&uuid); if(g.json.resultCode && pay){ cson_free_object(pay); pay = NULL; } return cson_object_value(pay); }
/* ** Implementation of /json/timeline/ticket. ** */ static cson_value * json_timeline_ticket(){ /* 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 check = 0; Stmt q = empty_Stmt; Blob sql = empty_blob; if( !g.perm.RdTkt && !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Ticket timeline requires 'o' or 'r' access."); return NULL; } payV = cson_value_new_object(); pay = cson_value_get_object(payV); check = json_timeline_setup_sql( "t", &sql, pay ); if(check){ json_set_err(check, "Query initialization failed."); goto error; } db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); #define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ 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," #endif " user AS user," " eventType AS eventType," " comment AS comment," " brief AS briefComment" " FROM json_timeline" " ORDER BY rowid"); listV = cson_value_new_array(); list = cson_value_get_array(listV); tmp = listV; SET("timeline"); while( (SQLITE_ROW == db_step(&q) )){ /* convert each row into a JSON object...*/ int rc; int const rid = db_column_int(&q,0); Manifest * pMan = NULL; cson_value * rowV; cson_object * row; /*printf("rid=%d\n",rid);*/ pMan = manifest_get(rid, CFTYPE_TICKET, 0); if(!pMan){ /* this might be an attachment? I'm seeing this with rid 15380, uuid [1292fef05f2472108]. /json/artifact/1292fef05f2472108 returns not-found, probably because we haven't added artifact/ticket yet(?). */ continue; } rowV = cson_sqlite3_row_to_object(q.pStmt); row = cson_value_get_object(rowV); if(!row){ manifest_destroy(pMan); json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, "Could not convert at least one timeline result row to JSON." ); continue; } /* FIXME: certainly there's a more efficient way for use to get the ticket UUIDs? */ cson_object_set(row,"ticketUuid",json_new_string(pMan->zTicketUuid)); manifest_destroy(pMan); rc = cson_array_append( list, rowV ); if( 0 != rc ){ cson_value_free(rowV); g.json.resultCode = (cson_rc.AllocError==rc) ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN; goto error; } } #undef SET goto ok; error: assert( 0 != g.json.resultCode ); cson_value_free(payV); payV = NULL; ok: blob_reset(&sql); db_finalize(&q); return payV; }
static cson_value * json_timeline_branch(){ cson_value * pay = NULL; Blob sql = empty_blob; Stmt q = empty_Stmt; int limit = 0; if(!g.perm.Read){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); return NULL; } json_timeline_temp_table(); blob_append(&sql, "SELECT" " blob.rid AS rid," " uuid AS uuid," " CAST(strftime('%s',event.mtime) AS INTEGER) as timestamp," " coalesce(ecomment, comment) as comment," " coalesce(euser, user) as user," " blob.rid IN leaf as isLeaf," " bgcolor as bgColor" " FROM event JOIN blob" " WHERE blob.rid=event.objid", -1); blob_append_sql(&sql, " AND event.type='ci'" " AND blob.rid IN (SELECT rid FROM tagxref" " WHERE tagtype>0 AND tagid=%d AND srcid!=0)" " ORDER BY event.mtime DESC", TAG_BRANCH); limit = json_timeline_limit(20); if(limit>0){ blob_append_sql(&sql," LIMIT %d ",limit); } db_prepare(&q,"%s", blob_sql_text(&sql)); blob_reset(&sql); pay = json_stmt_to_array_of_obj(&q, NULL); db_finalize(&q); assert(NULL != pay); if(pay){ /* get the array-form tags of each record. */ cson_string * tags = cson_new_string("tags",4); cson_string * isLeaf = cson_new_string("isLeaf",6); cson_array * ar = cson_value_get_array(pay); cson_object * outer = NULL; unsigned int i = 0; unsigned int len = cson_array_length_get(ar); cson_value_add_reference( cson_string_value(tags) ); cson_value_add_reference( cson_string_value(isLeaf) ); for( ; i < len; ++i ){ cson_object * row = cson_value_get_object(cson_array_get(ar,i)); int rid = cson_value_get_integer(cson_object_get(row,"rid")); assert( rid > 0 ); cson_object_set_s(row, tags, json_tags_for_checkin_rid(rid,0)); cson_object_set_s(row, isLeaf, json_value_to_bool(cson_object_get(row,"isLeaf"))); cson_object_set(row, "rid", NULL) /* remove rid - we don't really want it to be public */; } cson_value_free( cson_string_value(tags) ); cson_value_free( cson_string_value(isLeaf) ); /* now we wrap the payload in an outer shell, for consistency with other /json/timeline/xyz APIs... */ outer = cson_new_object(); if(limit>0){ cson_object_set( outer, "limit", json_new_int(limit) ); } cson_object_set( outer, "timeline", pay ); pay = cson_object_value(outer); } return pay; }
/* ** Impl for /json/report/run ** ** Options/arguments: ** ** report=int (CLI: -report # or -r #) is the report number to run. ** ** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands. ** ** format=a|o Specifies result format: a=each row is an arry, o=each ** row is an object. Default=o. */ static cson_value * json_report_run(){ int nReport; Stmt q = empty_Stmt; cson_object * pay = NULL; cson_array * tktList = NULL; char const * zFmt; char * zTitle = NULL; Blob sql = empty_blob; int limit = 0; cson_value * colNames = NULL; int i; if(!g.perm.RdTkt){ json_set_err(FSL_JSON_E_DENIED, "Requires 'r' privileges."); return NULL; } nReport = json_report_get_number(3); if(nReport <=0){ json_set_err(FSL_JSON_E_MISSING_ARGS, "Missing or invalid 'number' (-n) parameter."); goto error; } zFmt = json_find_option_cstr2("format",NULL,"f",3); if(!zFmt) zFmt = "o"; db_prepare(&q, "SELECT sqlcode, " " title" " FROM reportfmt" " WHERE rn=%d", nReport); if(SQLITE_ROW != db_step(&q)){ json_set_err(FSL_JSON_E_INVALID_ARGS, "Report number %d not found.", nReport); db_finalize(&q); goto error; } limit = json_find_option_int("limit",NULL,"n",-1); /* Copy over report's SQL...*/ blob_append(&sql, db_column_text(&q,0), -1); zTitle = mprintf("%s", db_column_text(&q,1)); db_finalize(&q); db_prepare(&q, "%s", blob_str(&sql)); /** Build the response... */ pay = cson_new_object(); cson_object_set(pay, "report", json_new_int(nReport)); cson_object_set(pay, "title", json_new_string(zTitle)); if(limit>0){ cson_object_set(pay, "limit", json_new_int((limit<0) ? 0 : limit)); } free(zTitle); zTitle = NULL; if(g.perm.TktFmt){ cson_object_set(pay, "sqlcode", cson_value_new_string(blob_str(&sql), (unsigned int)blob_size(&sql))); } blob_reset(&sql); colNames = cson_sqlite3_column_names(q.pStmt); cson_object_set( pay, "columnNames", colNames); for( i = 0 ; ((limit>0) ?(i < limit) : 1) && (SQLITE_ROW == db_step(&q)); ++i){ cson_value * row = ('a'==*zFmt) ? cson_sqlite3_row_to_array(q.pStmt) : cson_sqlite3_row_to_object2(q.pStmt, cson_value_get_array(colNames)); ; if(row && !tktList){ tktList = cson_new_array(); } cson_array_append(tktList, row); } db_finalize(&q); cson_object_set(pay, "tickets", tktList ? cson_array_value(tktList) : cson_value_null()); goto end; error: assert(0 != g.json.resultCode); cson_value_free( cson_object_value(pay) ); pay = NULL; end: return pay ? cson_object_value(pay) : NULL; }
cson_value * json_artifact_file(cson_object * zParent, int rid){ cson_object * pay = NULL; Stmt q = empty_Stmt; cson_array * checkin_arr = NULL; char contentFormat; i64 contentSize = -1; char * parentUuid; if( ! g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' privileges."); return NULL; } pay = zParent; contentFormat = json_artifact_get_content_format_flag(); if( 0 != contentFormat ){ Blob content = empty_blob; const char *zMime; char const * zFormat = (contentFormat<1) ? "raw" : "html"; content_get(rid, &content); zMime = mimetype_from_content(&content); cson_object_set(zParent, "contentType", json_new_string(zMime ? zMime : "text/plain")); if(!zMime){/* text/plain */ if(0 < blob_size(&content)){ if( 0 < contentFormat ){/*HTML-size it*/ Blob html = empty_blob; wiki_convert(&content, &html, 0); assert( blob_size(&content) < blob_size(&html) ); blob_swap( &html, &content ); assert( blob_size(&content) > blob_size(&html) ); blob_reset( &html ); }/*else as-is*/ } cson_object_set(zParent, "content", cson_value_new_string(blob_str(&content), (unsigned int)blob_size(&content))); }/*else binary: ignore*/ contentSize = blob_size(&content); cson_object_set(zParent, "contentSize", json_new_int(contentSize) ); cson_object_set(zParent, "contentFormat", json_new_string(zFormat) ); blob_reset(&content); } contentSize = db_int64(-1, "SELECT size FROM blob WHERE rid=%d", rid); assert( -1 < contentSize ); cson_object_set(zParent, "size", json_new_int(contentSize) ); parentUuid = db_text(NULL, "SELECT DISTINCT p.uuid " "FROM blob p, blob f, mlink m " "WHERE m.pid=p.rid " "AND m.fid=f.rid " "AND f.rid=%d", rid ); if(parentUuid){ cson_object_set( zParent, "parent", json_new_string(parentUuid) ); fossil_free(parentUuid); } /* Find checkins associated with this file... */ db_prepare(&q, "SELECT filename.name AS name, " " (mlink.pid==0) AS isNew," " (mlink.fid==0) AS isDel," " cast(strftime('%%s',event.mtime) as int) AS timestamp," " coalesce(event.ecomment,event.comment) as comment," " coalesce(event.euser,event.user) as user," #if 0 " a.size AS size," /* same for all checkins. */ #endif " b.uuid as checkin, " #if 0 " mlink.mperm as mperm," #endif " coalesce((SELECT value FROM tagxref" " WHERE tagid=%d AND tagtype>0 AND " " rid=mlink.mid),'trunk') as branch" " FROM mlink, filename, event, blob a, blob b" " WHERE filename.fnid=mlink.fnid" " AND event.objid=mlink.mid" " AND a.rid=mlink.fid" " AND b.rid=mlink.mid" " AND mlink.fid=%d" " ORDER BY filename.name, event.mtime", TAG_BRANCH, rid ); /* TODO: add a "state" flag for the file in each checkin, e.g. "modified", "new", "deleted". */ checkin_arr = cson_new_array(); cson_object_set(pay, "checkins", cson_array_value(checkin_arr)); while( (SQLITE_ROW==db_step(&q) ) ){ cson_object * row = cson_value_get_object(cson_sqlite3_row_to_object(q.pStmt)); /* FIXME: move this isNew/isDel stuff into an SQL CASE statement. */ char const isNew = cson_value_get_bool(cson_object_get(row,"isNew")); char const isDel = cson_value_get_bool(cson_object_get(row,"isDel")); cson_object_set(row, "isNew", NULL); cson_object_set(row, "isDel", NULL); cson_object_set(row, "state", json_new_string(json_artifact_status_to_string(isNew, isDel))); cson_array_append( checkin_arr, cson_object_value(row) ); } db_finalize(&q); return cson_object_value(pay); }
/* ** Impl of /json/dir. 98% of it was taken directly ** from browse.c::page_dir() */ static cson_value * json_page_dir_list(){ cson_object * zPayload = NULL; /* return value */ cson_array * zEntries = NULL; /* accumulated list of entries. */ cson_object * zEntry = NULL; /* a single dir/file entry. */ cson_array * keyStore = NULL; /* garbage collector for shared strings. */ cson_string * zKeyName = NULL; cson_string * zKeySize = NULL; cson_string * zKeyIsDir = NULL; cson_string * zKeyUuid = NULL; cson_string * zKeyTime = NULL; cson_string * zKeyRaw = NULL; char * zD = NULL; char const * zDX = NULL; int nD; char * zUuid = NULL; char const * zCI = NULL; Manifest * pM = NULL; Stmt q = empty_Stmt; int rid = 0; if( !g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); return NULL; } zCI = json_find_option_cstr("checkin",NULL,"ci" ); /* If a specific check-in is requested, fetch and parse it. If the ** specific check-in does not exist, clear zCI. zCI==0 will cause all ** files from all check-ins to be displayed. */ if( zCI && *zCI ){ pM = manifest_get_by_name(zCI, &rid); if( pM ){ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); }else{ json_set_err(FSL_JSON_E_UNRESOLVED_UUID, "Checkin name [%s] is unresolved.", zCI); return NULL; } } /* Jump through some hoops to find the directory name... */ zDX = json_find_option_cstr("name",NULL,NULL); if(!zDX && !g.isHTTP){ zDX = json_command_arg(g.json.dispatchDepth+1); } if(zDX && (!*zDX || (0==strcmp(zDX,"/")))){ zDX = NULL; } zD = zDX ? fossil_strdup(zDX) : NULL; nD = zD ? strlen(zD)+1 : 0; while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, pathelementFunc, 0, 0); /* Compute the temporary table "localfiles" containing the names ** of all files and subdirectories in the zD[] directory. ** ** Subdirectory names begin with "/". This causes them to sort ** first and it also gives us an easy way to distinguish files ** from directories in the loop that follows. */ if( zCI ){ Stmt ins; ManifestFile *pFile; ManifestFile *pPrev = 0; int nPrev = 0; int c; db_multi_exec( "CREATE TEMP TABLE json_dir_files(" " n UNIQUE NOT NULL," /* file name */ " fn UNIQUE NOT NULL," /* full file name */ " u DEFAULT NULL," /* file uuid */ " sz DEFAULT -1," /* file size */ " mtime DEFAULT NULL" /* file mtime in unix epoch format */ ");" ); db_prepare(&ins, "INSERT OR IGNORE INTO json_dir_files (n,fn,u,sz,mtime) " "SELECT" " pathelement(:path,0)," " CASE WHEN %Q IS NULL THEN '' ELSE %Q||'/' END ||:abspath," " a.uuid," " a.size," " CAST(strftime('%%s',e.mtime) AS INTEGER) " "FROM" " mlink m, " " event e," " blob a," " blob b " "WHERE" " e.objid=m.mid" " AND a.rid=m.fid"/*FILE artifact*/ " AND b.rid=m.mid"/*CHECKIN artifact*/ " AND a.uuid=:uuid", zD, zD ); manifest_file_rewind(pM); while( (pFile = manifest_file_next(pM,0))!=0 ){ if( nD>0 && ((pFile->zName[nD-1]!='/') || (0!=memcmp(pFile->zName, zD, nD-1))) ){ continue; } /*printf("zD=%s, nD=%d, pFile->zName=%s\n", zD, nD, pFile->zName);*/ if( pPrev && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/') ){ continue; } db_bind_text( &ins, ":path", &pFile->zName[nD] ); db_bind_text( &ins, ":abspath", &pFile->zName[nD] ); db_bind_text( &ins, ":uuid", pFile->zUuid ); db_step(&ins); db_reset(&ins); pPrev = pFile; for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} if( c=='/' ) nPrev++; } db_finalize(&ins); }else if( zD && *zD ){ db_multi_exec( "CREATE TEMP VIEW json_dir_files AS" " SELECT DISTINCT(pathelement(name,%d)) AS n," " %Q||'/'||name AS fn," " NULL AS u, NULL AS sz, NULL AS mtime" " FROM filename" " WHERE name GLOB '%q/*'" " GROUP BY n", nD, zD, zD ); }else{ db_multi_exec( "CREATE TEMP VIEW json_dir_files" " AS SELECT DISTINCT(pathelement(name,0)) AS n, NULL AS fn" " FROM filename" ); } if(zCI){ db_prepare( &q, "SELECT" " n as name," " fn as fullname," " u as uuid," " sz as size," " mtime as mtime " "FROM json_dir_files ORDER BY n"); }else{/* UUIDs are all NULL. */ db_prepare( &q, "SELECT n, fn FROM json_dir_files ORDER BY n"); } zKeyName = cson_new_string("name",4); zKeyUuid = cson_new_string("uuid",4); zKeyIsDir = cson_new_string("isDir",5); keyStore = cson_new_array(); cson_array_append( keyStore, cson_string_value(zKeyName) ); cson_array_append( keyStore, cson_string_value(zKeyUuid) ); cson_array_append( keyStore, cson_string_value(zKeyIsDir) ); if( zCI ){ zKeySize = cson_new_string("size",4); cson_array_append( keyStore, cson_string_value(zKeySize) ); zKeyTime = cson_new_string("timestamp",9); cson_array_append( keyStore, cson_string_value(zKeyTime) ); zKeyRaw = cson_new_string("downloadPath",12); cson_array_append( keyStore, cson_string_value(zKeyRaw) ); } zPayload = cson_new_object(); cson_object_set_s( zPayload, zKeyName, json_new_string((zD&&*zD) ? zD : "/") ); if( zUuid ){ cson_object_set( zPayload, "checkin", json_new_string(zUuid) ); } while( (SQLITE_ROW==db_step(&q)) ){ cson_value * name = NULL; char const * n = db_column_text(&q,0); char const isDir = ('/'==*n); zEntry = cson_new_object(); if(!zEntries){ zEntries = cson_new_array(); cson_object_set( zPayload, "entries", cson_array_value(zEntries) ); } cson_array_append(zEntries, cson_object_value(zEntry) ); if(isDir){ name = json_new_string( n+1 ); cson_object_set_s(zEntry, zKeyIsDir, cson_value_true() ); } else{ name = json_new_string( n ); } cson_object_set_s(zEntry, zKeyName, name ); if( zCI && !isDir){ /* Don't add the uuid/size for dir entries - that data refers to one of the files in that directory :/. Entries with no --checkin may refer to N versions, and therefore we cannot associate a single size and uuid with them (and fetching all would be overkill for most use cases). */ char const * fullName = db_column_text(&q,1); char const * u = db_column_text(&q,2); sqlite_int64 const sz = db_column_int64(&q,3); sqlite_int64 const ts = db_column_int64(&q,4); cson_object_set_s(zEntry, zKeyUuid, json_new_string( u ) ); cson_object_set_s(zEntry, zKeySize, cson_value_new_integer( (cson_int_t)sz )); cson_object_set_s(zEntry, zKeyTime, cson_value_new_integer( (cson_int_t)ts )); cson_object_set_s(zEntry, zKeyRaw, json_new_string_f("/raw/%T?name=%t", fullName, u)); } } db_finalize(&q); if(pM){ manifest_destroy(pM); } cson_free_array( keyStore ); free( zUuid ); free( zD ); return cson_object_value(zPayload); }
/* ** Internal helper to append query information if the ** "tag" or "branch" request properties (CLI: --tag/--branch) ** are set. Limits the query to a particular branch/tag. ** ** tag works like HTML mode's "t" option and branch works like HTML ** mode's "r" option. They are very similar, but subtly different - ** tag mode shows only entries with a given tag but branch mode can ** also reveal some with "related" tags (meaning they were merged into ** the requested branch, or back). ** ** pSql is the target blob to append the query [subset] ** to. ** ** Returns a positive value if it modifies pSql, 0 if it ** does not. It returns a negative value if the tag ** provided to the request was not found (pSql is not modified ** in that case). ** ** If payload is not NULL then on success its "tag" or "branch" ** property is set to the tag/branch name found in the request. ** ** Only one of "tag" or "branch" modes will work at a time, and if ** both are specified, which one takes precedence is unspecified. */ static char json_timeline_add_tag_branch_clause(Blob *pSql, cson_object * pPayload){ char const * zTag = NULL; char const * zBranch = NULL; char const * zMiOnly = NULL; char const * zUnhide = NULL; int tagid = 0; if(! g.perm.Read ){ return 0; } zTag = json_find_option_cstr("tag",NULL,NULL); if(!zTag || !*zTag){ zBranch = json_find_option_cstr("branch",NULL,NULL); if(!zBranch || !*zBranch){ return 0; } zTag = zBranch; zMiOnly = json_find_option_cstr("mionly",NULL,NULL); } zUnhide = json_find_option_cstr("unhide",NULL,NULL); tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag); if(tagid<=0){ return -1; } if(pPayload){ cson_object_set( pPayload, zBranch ? "branch" : "tag", json_new_string(zTag) ); } blob_appendf(pSql, " AND (" " EXISTS(SELECT 1 FROM tagxref" " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", tagid); if(!zUnhide){ blob_appendf(pSql, " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=blob.rid" " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", TAG_HIDDEN); } if(zBranch){ /* from "r" flag code in page_timeline().*/ blob_appendf(pSql, " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", tagid); if( !zUnhide ){ blob_appendf(pSql, " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", TAG_HIDDEN); } if( zMiOnly==0 ){ blob_appendf(pSql, " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", tagid); if( !zUnhide ){ blob_appendf(pSql, " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", TAG_HIDDEN); } } } blob_append(pSql," ) ",3); return 1; }