You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
6.8 KiB
C

#include "account.h"
// Submodules
#include "json/json.h"
#include "json/layout.h"
#include "http_client/client.h"
#include "ffdb/fs_list.h"
#include "ffdb/hash_index.h"
// Model
#include "model/server.h"
#include "model/ap/account.h"
#include "model/crypto/keys.h"
// Stdlib
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <sys/stat.h>
static const char* safe( const char* value, const char* other )
{
if( !value ) { return other; }
return value;
}
static const char* b(bool value)
{
return value ? "true" : "false";
}
static struct json_enum account_types_enum[] = {
{ "owner", at_owner },
{ "bot", at_bot },
{ "activity_pub", at_remote_activity_pub },
{ "rss", at_rss_feed },
{ NULL },
};
static struct json_object_field account_layout[] = {
{ "handle", offsetof( struct account, handle ), true, &json_field_string },
{ "server", offsetof( struct account, server ), true, &json_field_string },
{ "display_name", offsetof( struct account, display_name ), true, &json_field_string },
{ "avatar", offsetof( struct account, avatar.url ), true, &json_field_string },
{ "avatar_static", offsetof( struct account, avatar.static_url ), true, &json_field_string },
{ "account_type", offsetof( struct account, account_type ), true, &json_field_enum, account_types_enum },
{ "account_url", offsetof( struct account, account_url ), true, &json_field_string },
{ "inbox", offsetof( struct account, inbox ), false, &json_field_string },
{ NULL },
};
struct account* account_from_id( unsigned int id )
{
char filename[512];
snprintf( filename, 512, "data/accounts/%d.json", id );
struct account* a = malloc(sizeof(struct account));
memset( a, 0, sizeof(struct account) );
a->id = id;
if( !json_read_object_layout_from_file( filename, account_layout, a ) ) {
account_free(a);
return NULL;
}
return a;
}
static bool index_uri_to_account_id( const char* uri, int account_id )
{
return hash_index_set( "data/accounts/uri_index/", uri, account_id );
}
static int lookup_account_id_from_uri( const char* uri )
{
int result = 0;
if( !hash_index_get( "data/accounts/uri_index/", uri, &result ) ) {
return -1;
}
return result;
}
struct account* account_from_uri( const char* uri )
{
struct account* result = NULL;
// Handle owner as special case
char buffer[512];
snprintf( buffer, 512, "https://%s/owner/actor", g_server_name );
if( 0 == strcmp(buffer,uri) ) {
return account_from_id(0);
}
// TODO: handle bots
int account_id = lookup_account_id_from_uri( uri );
if( account_id == -1 ) { return NULL; }
return account_from_id( account_id );
}
void account_sync_from_acitvity_pub( unsigned int account_id )
{
char filename[512];
snprintf( filename, 512, "data/accounts/%d/ap.json", account_id );
struct ap_account* ap = ap_account_from_file( filename );
//printf( "ap = " ); ap_account_debug_dump(ap);
struct account* a = malloc(sizeof(struct account));
memset(a,0,sizeof(*a));
a->id = account_id;
a->handle = strdup(ap->preferredUsername);
a->display_name = strdup(ap->name);
a->avatar.url = strdup(ap->avatar);
a->avatar.static_url = strdup(ap->avatar);
a->bot = ( ap->type != apacct_Person );
a->account_type = at_remote_activity_pub;
a->account_url = strdup(ap->url);
a->inbox = strdup(ap->inbox);
if( 0 == strncmp( ap->id, "https://", 8 ) ) {
char* server_name = strdup(&ap->id[8]);
char* discard;
strtok_r(server_name,"/",&discard);
a->server = server_name;
}
// Extract out the public key
char* id = strdup(ap->public_key.id);
char* key_id = NULL;
strtok_r( id, "#", &key_id );
snprintf( filename, sizeof(filename), "data/accounts/%d/%s.pem", a->id, key_id );
FILE* key_pem = fopen(filename,"w");
fprintf( key_pem, "%s", ap->public_key.pem );
fclose(key_pem);
account_save(a);
ap_account_free(ap);
account_free(a);
}
struct crypto_keys* account_get_public_key( struct account* a, const char* key_name )
{
char filename[512];
snprintf( filename, sizeof(filename), "data/accounts/%d/%s.pem", a->id, key_name );
FILE* f = fopen( filename, "r" );
if( !f ) {
printf( "Failed to open file %s\n", filename );
return NULL;
}
fclose(f);
struct crypto_keys* keys = crypto_keys_new();
if( crypto_keys_load_public( keys, filename ) ) {
return keys;
}
printf( "Failed to load public key from %s\n", filename );
crypto_keys_free(keys);
return NULL;
}
struct crypto_keys* account_get_private_key( struct account* a )
{
return NULL;
}
static void create_account_skeleton( int account_id )
{
// Make sure the account directory exists
char dirname[512];
snprintf( dirname, 512, "data/accounts/%d", account_id );
mkdir( dirname, 0755 );
snprintf( dirname, 512, "data/accounts/%d/timeline", account_id );
mkdir( dirname, 0755 );
char filename[512];
snprintf( filename, 512, "data/accounts/%d/timeline/HEAD", account_id );
fs_list_set( filename, 0 );
}
struct account* account_fetch_from_uri( const char* uri )
{
int account_id = lookup_account_id_from_uri( uri );
if( -1 == account_id ) {
account_id = fs_list_get( "data/accounts/HEAD" ) + 1;
fs_list_set( "data/accounts/HEAD", account_id );
index_uri_to_account_id( uri, account_id );
}
printf( "account_id = %d\n", account_id );
create_account_skeleton(account_id);
// Fetch the ActivityPub actor data if we don't already have it
char filename[512];
snprintf( filename, 512, "data/accounts/%d/ap.json", account_id );
FILE* f = fopen(filename,"r");
if( !f ) {
char tmp_filename[512];
snprintf( tmp_filename, 512, "%s.tmp", filename );
printf( "tmp_filename = %s\n", tmp_filename );
f = fopen(tmp_filename,"w");
if( !f ) {
printf( "Unable to open %s\n", tmp_filename );
return NULL;
}
const void* request[] = {
HTTP_REQ_URL, uri,
HTTP_REQ_HEADER, "Accept: application/json",
HTTP_REQ_OUTFILE, f,
NULL,
};
if( !http_client_do( request ) ) {
printf( "Unable to fetch %s\n", uri );
return NULL;
}
fclose(f);
rename(tmp_filename,filename);
}
account_sync_from_acitvity_pub( account_id );
return account_from_id(account_id);
}
void account_free( struct account* a )
{
if( !a ) { return; }
free(a->handle);
free(a->server);
free(a->inbox);
free(a->display_name);
free(a->account_url);
free(a->avatar.url);
free(a->avatar.static_url);
free(a);
}
void account_save( struct account* a )
{
char filename[512];
snprintf( filename, 512, "data/accounts/%d.json", a->id );
json_write_object_layout_to_file( filename, "\t", account_layout, a );
}
void account_add_follower( struct account* a, struct account* follower )
{
printf( "TODO: implement account_add_follower()\n" );
}
void account_remove_follower( struct account* a, struct account* follower )
{
printf( "TODO: implement account_remove_follower()\n" );
}
// TODO: move to controller/view
void account_write_as_json( struct account* a, FILE* f )
{
#define RENDER
#include "src/model/account.json.inc"
#undef RENDER
}