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.

440 lines
11 KiB
C

#include "mastodon_api.h"
#include "http/server/request.h"
// Submodules
#include "form.h"
#include "json/json.h"
#include "http/query.h"
#include "ffdb/fs_list.h"
#include "util/format.h"
#include "ap/object.h"
// Model
#include "model/status.h"
#include "model/account.h"
#include "model/notification.h"
#include "model/server.h"
#include "model/media.h"
#include "model/marker.h"
#include "model/peer.h"
// View
#include "view/api/Attachement.h"
// Controller
#include "controller/pleroma_api.h"
#include "controller/api/timeline.h"
#include "controller/api/client_apps.h"
#include "controller/api/status.h"
#include "controller/api/notice.h"
#include "controller/api/accounts.h"
#include "controller/api/emoji.h"
#include <stddef.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
bool handle_scheduled_statuses( struct http_request* req )
{
http_request_send_headers( req, 200, "application/json", true );
FILE* body = http_request_get_response_body( req );
fprintf( body, "[]" );
return true;
}
ssize_t getline_stripped( char** restrict lineptr, size_t* restrict n, FILE* restrict stream);
bool http_request_write_multipart_to_FILE( struct http_request* req, FILE* f, char** content_type )
{
char* line = NULL;
char* boundary = NULL;
FILE* data = http_request_get_request_data( req );
size_t n;
// lineptr should match the contents of the Content-Type boundary= parametera
/*ssize_t res = */getline_stripped( &line, &n, data );
boundary = aformat( "\r\n%s", line );
int boundary_size = strlen(boundary);
struct ring_buffer {
char* data;
int count;
int limit;
int head;
};
struct ring_buffer buffer = {
.data = malloc(boundary_size + 20),
.count = 0,
.limit = boundary_size + 20,
.head = 0
};
bool ring_buffer_push_back( struct ring_buffer* rb, char ch )
{
if( rb->count >= rb->limit ) { return false; }
rb->data[ ( rb->head + rb->count ) % rb->limit ] = ch;
rb->count += 1;
return true;
}
bool ring_buffer_pop_front( struct ring_buffer* rb, char* ch )
{
if( rb->count == 0 ) { return false; }
*ch = rb->data[ rb->head ];
rb->head = ( rb->head + 1 ) % rb->limit;
rb->count -= 1;
return true;
}
bool ring_buffer_compare_prefix( struct ring_buffer* rb, const char* str )
{
for( int i = 0; i < rb->count; ++i, ++str ) {
if( !*str ) {
// String matches start of buffer
return true;
}
char ch = rb->data[ ( i + rb->head ) % rb->limit ];
if( *str != ch ) {
return false;
}
}
return true;
}
memset( buffer.data, 0, boundary_size );
// Eat all header data
while( getline_stripped( &line, &n, data ) > 0 ) {
if( 0 == strncasecmp( line, "Content-Type: ", 14 ) ) {
if( content_type ) {
*content_type = strdup( &line[14] );
}
printf( "content_type=%s\n", *content_type );
} else {
printf( "Header: %s\n", line );
}
}
free(line);
// Read the line
int filesize = 0;
int ch;
do {
ch = fgetc(data);
if( ch != EOF ) {
ring_buffer_push_back(&buffer,ch );
}
if( ring_buffer_compare_prefix(&buffer,boundary) ) {
break;
}
if( buffer.count > boundary_size || ch == EOF ) {
char c;
if( ring_buffer_pop_front( &buffer, &c ) ) {
filesize += 1;
fputc( c, f );
}
}
} while( buffer.count > 0 );
//printf( "%d bytes uploaded\n", filesize );
free(boundary);
free(buffer.data);
return true;
}
// route: GET /api/v1/announcements
bool handle_announcements( struct http_request* req )
{
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[]" );
return true;
}
// route: GET /api/v1/directory
bool handle_directory( struct http_request* req )
{
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[]" );
return true;
}
// route: GET /api/v1/blocks
bool handle_blocks( struct http_request* req )
{
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[]" );
return true;
}
// route: POST /api/v1/markers
bool handle_update_markers( struct http_request* req )
{
FILE* f = http_request_get_request_data(req);
struct json_pull_parser* jpp = json_pull_parser_new(f);
int save = 0;
if( !json_pull_parser_begin_object(jpp,&save) ) { return false; }
char* key = json_pull_parser_read_object_key(jpp);
if( !key ) { return false; }
char* marker_name = key;
int save2 = 0;
if( !json_pull_parser_begin_object(jpp,&save2) ) { return false; }
char* last_read_id;
while( (key=json_pull_parser_read_object_key(jpp)) ) {
if( 0 == strcmp(key,"last_read_id") ) {
last_read_id = json_pull_parser_read_string(jpp);
} else {
json_pull_parser_read_value(jpp,NULL);
}
}
if( !json_pull_parser_end_object(jpp,&save2) ) { return false; }
struct marker* m = marker_from_name(marker_name);
if( last_read_id ) {
free(m->last_read_id);
m->last_read_id = last_read_id;
m->version += 1;
marker_save(m);
}
http_request_send_headers( req, 200, "application/json", true );
FILE* o = http_request_get_response_body(req);
fprintf(o,"{");
fprintf(o,"\"%s\":",key);
marker_write_to_FILE(m,o);
fprintf(o,"}");
marker_free(m);
if( !json_pull_parser_end_object(jpp,&save) ) {
return false;
}
json_pull_parser_release(jpp);
return true;
}
// route: GET /api/v1/markers
bool handle_markers( struct http_request* req )
{
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "{" );
bool is_first = true;
if( http_request_route( req, "?" ) ) {
if( 0 == strcmp("timeline[]",http_request_route_query_key(req) ) ) {
if( !is_first ) {
fprintf( f, "," );
}
const char* marker_name = http_request_route_query_value(req);
struct marker* m = marker_from_name(marker_name);
if( m ) {
fprintf( f, "\"%s\":", marker_name );
marker_write_to_FILE( m, f );
marker_free(m);
is_first = false;
}
}
}
fprintf( f, "}" );
return true;
}
// route: POST /api/v1/media
bool handle_media( struct http_request* req )
{
bool result = false;
char filename[512];
FILE* f = NULL;
struct media* m = NULL;
// TODO: move this to src/model
int id = fs_list_inc( "data/media/HEAD" );
m = malloc(sizeof(*m));
memset(m,0,sizeof(*m));
m->id = id;
m->remote_url = aformat( "https://%s/media/%d/blob", g_server->domain, id );
m->preview_url = aformat( "https://%s/media/%d/preview", g_server->domain, id );
f = fopen(format(filename,sizeof(filename), "data/media/%d.blob",id), "w" );
if( !http_request_write_multipart_to_FILE( req, f, &m->content_type ) ) { goto failed; }
fclose(f); f = NULL;
if( 0 == strncmp("image/",m->content_type,6) ) {
printf( "This is an image, create preview\n" );
char buffer[512];
system( "set -x; pwd" );
int res = system( format( buffer,sizeof(buffer),"set -x; /usr/bin/convert '%s' -thumbnail '250x80>' '%s.preview'", filename, filename ) );
printf( "$ %s -> %d\n", buffer, res );
}
media_save(m);
http_request_send_headers( req, 200, "application/json", true );
FILE* body = http_request_get_response_body( req );
api_Attachement_write( m, body, 0 );
result = true;
cleanup:
if( f ) { fclose(f); }
media_free(m);
return result;
failed:
result = false;
goto cleanup;
}
bool http_request_is_tor_request( struct http_request* req )
{
if( g_server->tor_hidden_service ) {
const char* host = http_request_get_header( req, "Host" );
if( host ) {
if( 0 == strcmp( host, g_server->tor_hidden_service ) ) {
return true;
}
}
}
return false;
}
bool handle_peers( struct http_request* req )
{
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[" );
bool first = true;
for( int id = 1;; ++id ) {
struct peer* p = peer_from_id(id);
if( p ) {
if( !first ) {
fprintf( f, "," );
}
first = false;
fprintf( f, "\"%s\"", p->domain );
} else {
break;
}
}
fprintf( f, "]" );
return true;
}
// route: /api/v1/
bool route_mastodon_api( struct http_request* req )
{
bool is_tor_request = http_request_is_tor_request(req);
if( is_tor_request ) {
printf( "\tTOR request\n" );
}
if( http_request_route_term( req, "apps" ) ) {
if( http_request_route_method( req, "POST" ) ) {
return handle_mastodon_api_apps(req);
}
} else if( http_request_route( req, "timelines/public" ) ) {
return handle_timeline( req, tli_public );
} else if( http_request_route( req, "timelines/tag/" ) ) {
char* tag = http_request_route_get_dir_or_file(req);
bool res = handle_tag_timeline( req, tag );
free(tag);
return res;
} else if( http_request_route( req, "instance" ) ) {
if( http_request_route_term( req, "/peers" ) ) {
return handle_peers(req);
} else {
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
#include "src/view/api/instance_data.json.inc"
return true;
}
} else if( http_request_route_term( req, "custom_emojis" ) ) {
return route_custom_emojis(req);
} else if( http_request_route( req, "statuses" ) ) {
return route_statuses(req);
}
if( !check_authentication_header(req) ) {
printf( "User-Agent: %s\n", http_request_get_header(req,"user-agent") );
http_request_send_headers( req, 401, "text/plain", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "Not authorized to use this endpoint.\n" );
return true;
}
if( http_request_route( req, "pleroma" ) ) {
return route_pleroma_api( req );
}
if( http_request_route( req, "notifications" ) ) {
return handle_notifications(req);
} else if( http_request_route( req, "announcements" ) ) {
return handle_announcements(req);
} else if( http_request_route( req, "blocks" ) ) {
return handle_blocks(req);
} else if( http_request_route( req, "directory" ) ) {
return handle_directory(req);
} else if( http_request_route( req, "markers" ) ) {
if( http_request_route_method( req, "POST" ) ) {
return handle_update_markers(req);
} else {
return handle_markers(req);
}
} else if( http_request_route( req, "filters" ) ) {
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
fprintf( f, "[]" );
return true;
} else if( http_request_route_term( req, "scheduled_statuses" ) ) {
return handle_scheduled_statuses(req);
} else if( http_request_route( req, "timelines/" ) ) {
if( http_request_route( req, "home" ) ) {
return handle_timeline( req, tli_home );
} else if( http_request_route( req, "public" ) ) {
return handle_timeline( req, tli_public );
}
} else if( http_request_route_term( req, "media" ) ) {
if( http_request_route_method( req, "POST" ) ) {
return handle_media(req);
}
} else if( http_request_route_term( req, "bookmarks" ) ) {
return handle_show_bookmarks( req );
} else if( http_request_route( req, "apps/" ) ) {
if( http_request_route( req, "verify_credentials" ) ) {
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
#include "src/view/api/verify_credentials.json.inc"
return true;
}
} else if( http_request_route( req, "accounts/" ) ) {
return route_api_account( req );
}
return false;
}