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.
442 lines
11 KiB
C
442 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);
|
|
} else if( http_request_route( req, "polls" ) ) {
|
|
return route_polls(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;
|
|
}
|
|
|