#ifdef FOSSIL_ENABLE_JSON /* ** Copyright (c) 2011 D. Richard Hipp ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the Simplified BSD License (also ** known as the "2-Clause License" or "FreeBSD License".) ** ** This program is distributed in the hope that it will be useful, ** but without any warranty; without even the implied warranty of ** merchantability or fitness for a particular purpose. ** ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** */ #include "VERSION.h" #include "config.h" #include "json_artifact.h" #if INTERFACE #include "json_detail.h" #endif /* ** Internal callback for /json/artifact handlers. rid refers to ** the rid of a given type of artifact, and each callback is ** specialized to return a JSON form of one type of artifact. ** ** Implementations may assert() that rid refers to requested artifact ** type, since mismatches in the artifact types come from ** json_page_artifact() as opposed to client data. */ typedef cson_value * (*artifact_f)( int rid ); /* ** Internal per-artifact-type dispatching helper. */ typedef struct ArtifactDispatchEntry { /** Artifact type name, e.g. "checkin", "ticket", "wiki". */ char const * name; /** JSON construction callback. Creates the contents for the payload.artifact property of /json/artifact responses. */ artifact_f func; } ArtifactDispatchEntry; /* ** Generates a JSON Array reference holding the parent UUIDs (as strings). ** If it finds no matches then it returns NULL (OOM is a fatal error). ** ** Returned value is NULL or an Array owned by the caller. */ cson_value * json_parent_uuids_for_ci( int rid ){ Stmt q = empty_Stmt; cson_array * pParents = NULL; db_prepare( &q, "SELECT uuid FROM plink, blob" " WHERE plink.cid=%d AND blob.rid=plink.pid" " ORDER BY plink.isprim DESC", rid ); while( SQLITE_ROW==db_step(&q) ){ if(!pParents) { pParents = cson_new_array(); } cson_array_append( pParents, cson_sqlite3_column_to_value( q.pStmt, 0 ) ); } db_finalize(&q); return cson_array_value(pParents); } /* ** Generates an artifact Object for the given rid, ** which must refer to a Checkin. ** ** Returned value is NULL or an Object owned by the caller. */ cson_value * json_artifact_for_ci( int rid, char showFiles ){ cson_value * v = NULL; Stmt q = empty_Stmt; static cson_value * eventTypeLabel = NULL; if(!eventTypeLabel){ eventTypeLabel = json_new_string("checkin"); json_gc_add("$EVENT_TYPE_LABEL(commit)", eventTypeLabel); } db_prepare(&q, "SELECT b.uuid, " " cast(strftime('%%s',e.mtime) as int), " " strftime('%%s',e.omtime)," " e.user, " " e.comment" " FROM blob b, event e" " WHERE b.rid=%d" " AND e.objid=%d", rid, rid ); if( db_step(&q)==SQLITE_ROW ){ cson_object * o; cson_value * tmpV = NULL; const char *zUuid = db_column_text(&q, 0); const char *zUser; const char *zComment; char * zEUser, * zEComment; int mtime, omtime; v = cson_value_new_object(); o = cson_value_get_object(v); #define SET(K,V) cson_object_set(o,(K), (V)) SET("type", eventTypeLabel ); SET("uuid",json_new_string(zUuid)); SET("isLeaf", cson_value_new_bool(is_a_leaf(rid))); mtime = db_column_int(&q,1); SET("mtime",json_new_int(mtime)); omtime = db_column_int(&q,2); if(omtime && (omtime!=mtime)){ SET("originTime",json_new_int(omtime)); } zUser = db_column_text(&q,3); zEUser = db_text(0, "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", TAG_USER, rid); if(zEUser){ SET("user", json_new_string(zEUser)); if(0!=strcmp(zEUser,zUser)){ SET("originUser",json_new_string(zUser)); } free(zEUser); }else{ SET("user",json_new_string(zUser)); } zComment = db_column_text(&q,4); zEComment = db_text(0, "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", TAG_COMMENT, rid); if(zEComment){ SET("comment",json_new_string(zEComment)); if(0 != strcmp(zEComment,zComment)){ SET("originComment", json_new_string(zComment)); } free(zEComment); }else{ SET("comment",json_new_string(zComment)); } tmpV = json_parent_uuids_for_ci(rid); if(tmpV){ SET("parents", tmpV); } tmpV = json_tags_for_checkin_rid(rid,0); if(tmpV){ SET("tags",tmpV); } if( showFiles ){ tmpV = json_get_changed_files(rid); if(tmpV){ SET("files",tmpV); } } #undef SET } db_finalize(&q); return v; } /* ** Very incomplete/incorrect impl of /json/artifact/TICKET_ID. */ cson_value * json_artifact_ticket( 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); } /* ** Sub-impl of /json/artifact for checkins. */ static cson_value * json_artifact_ci( int rid ){ if(!g.perm.Read){ json_set_err( FSL_JSON_E_DENIED, "Viewing checkins requires 'o' privileges." ); return NULL; }else{ return json_artifact_for_ci(rid, 1); } } /* ** Internal mapping of /json/artifact/FOO commands/callbacks. */ static ArtifactDispatchEntry ArtifactDispatchList[] = { {"checkin", json_artifact_ci}, {"file", json_artifact_file}, {"tag", NULL}, {"ticket", json_artifact_ticket}, {"wiki", json_artifact_wiki}, /* Final entry MUST have a NULL name. */ {NULL,NULL} }; /* ** Internal helper which returns true (non-0) if the includeContent ** (HTTP) or -content|-c flags (CLI) are set. */ static char json_artifact_include_content_flag(){ return json_find_option_bool("includeContent","content","c",0); } cson_value * json_artifact_wiki(int rid){ if( ! g.perm.RdWiki ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'j' privileges."); return NULL; }else{ char contentFormat = json_wiki_get_content_format_flag(-9); if(-9 == contentFormat){ contentFormat = json_artifact_include_content_flag() ? -1 : 0; } return json_get_wiki_page_by_rid(rid, contentFormat); } } cson_value * json_artifact_file(int rid){ cson_object * pay = NULL; const char *zMime; Blob content = empty_blob; Stmt q = empty_Stmt; cson_array * checkin_arr = NULL; if( ! g.perm.Read ){ json_set_err(FSL_JSON_E_DENIED, "Requires 'o' privileges."); return NULL; } pay = cson_new_object(); content_get(rid, &content); cson_object_set(pay, "contentLength", json_new_int( blob_size(&content) ) /* achtung: overflow potential on 32-bit builds! */); zMime = mimetype_from_content(&content); cson_object_set(pay, "contentType", json_new_string(zMime ? zMime : "text/plain")); if( json_artifact_include_content_flag() && !zMime ){ cson_object_set(pay, "content", cson_value_new_string(blob_str(&content), (unsigned int)blob_size(&content))); } blob_reset(&content); db_prepare(&q, "SELECT filename.name AS name, " " cast(strftime('%%s',event.mtime) as int) AS mtime," " coalesce(event.ecomment,event.comment) as comment," " coalesce(event.euser,event.user) as user," " b.uuid as uuid, " #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)); json_stmt_to_array_of_obj( &q, checkin_arr ); db_finalize(&q); return cson_object_value(pay); } /* ** 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,"*"); 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: assert( (NULL != zType) && "Internal dispatching error." ); for( ; dispatcher->name; ++dispatcher ){ if(0!=strcmp(dispatcher->name, zType)){ continue; }else{ entry = (*dispatcher->func)(rid); break; } } if(!g.json.resultCode){ assert( NULL != entry ); assert( NULL != zType ); pay = cson_new_object(); 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(entry){ cson_object_set(pay, "artifact", entry); } } veryend: blob_reset(&uuid); return cson_object_value(pay); } #endif /* FOSSIL_ENABLE_JSON */