Meta: Add files to repository
This commit is contained in:
parent
6d27d43268
commit
52cb92f587
120 changed files with 71820 additions and 0 deletions
274
Slon/Api/V1/Accounts.HC
Normal file
274
Slon/Api/V1/Accounts.HC
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
U0 @slon_api_v1_accounts_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer;
|
||||||
|
|
||||||
|
U8* path = @slon_strnew(session, @slon_http_request_path(session));
|
||||||
|
I64 path_segments_count = 0;
|
||||||
|
U8** path_segments = String.Split(path, '/', &path_segments_count);
|
||||||
|
|
||||||
|
JsonObject* acct = NULL;
|
||||||
|
|
||||||
|
if (!StrICmp("verify_credentials", path_segments[3])) {
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
SLON_AUTH_ACCOUNT_ID
|
||||||
|
acct = @slon_api_account_by_id(account_id);
|
||||||
|
if (acct) {
|
||||||
|
@slon_http_send_json(session, acct);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Work with account :id
|
||||||
|
U8* some_account_id = path_segments[3];
|
||||||
|
acct = @slon_api_account_by_id(some_account_id);
|
||||||
|
if (!acct) {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
goto slon_api_v1_accounts_get_return;
|
||||||
|
}
|
||||||
|
if (path_segments_count > 5) {
|
||||||
|
U8* method = path_segments[4];
|
||||||
|
if (!StrICmp("following", method)) {
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
goto slon_api_v1_accounts_get_return;
|
||||||
|
}
|
||||||
|
if (!StrICmp("statuses", method)) {
|
||||||
|
// Return the Account's Statuses
|
||||||
|
JsonArray* status_array = db->o("statuses")->a(some_account_id);
|
||||||
|
|
||||||
|
I64 count = 0;
|
||||||
|
|
||||||
|
// FILTERS
|
||||||
|
I64 limit = 20; // default
|
||||||
|
U64 max_id = 0;
|
||||||
|
U64 min_id = 0;
|
||||||
|
Bool only_media = request_json->@("only_media");
|
||||||
|
Bool exclude_replies = request_json->@("exclude_replies");
|
||||||
|
Bool exclude_reblogs = request_json->@("exclude_reblogs");
|
||||||
|
no_warn exclude_reblogs;
|
||||||
|
Bool pinned = request_json->@("pinned");
|
||||||
|
// FIXME: Implement "only_media", "exclude_reblogs", "tagged"
|
||||||
|
|
||||||
|
Bool exclude_status = FALSE;
|
||||||
|
U64 status_id = 0;
|
||||||
|
|
||||||
|
if (StrLen(request_json->@("limit")) > 0) {
|
||||||
|
// 40 = maximum per https://docs.joinmastodon.org/methods/accounts/#statuses
|
||||||
|
limit = MinI64(40, Str2I64(request_json->@("limit")));
|
||||||
|
}
|
||||||
|
if (StrLen(request_json->@("max_id")) > 0) {
|
||||||
|
max_id = Str2I64(request_json->@("max_id"));
|
||||||
|
}
|
||||||
|
if (StrLen(request_json->@("min_id")) > 0) {
|
||||||
|
min_id = Str2I64(request_json->@("min_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray* statuses = Json.CreateArray();
|
||||||
|
JsonObject* status = NULL;
|
||||||
|
|
||||||
|
if (status_array && status_array->length) {
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < status_array->length; i++) {
|
||||||
|
status = status_array->o(i);
|
||||||
|
status_id = Str2I64(status->@("id"));
|
||||||
|
exclude_status = FALSE;
|
||||||
|
if (status->@("deleted")) {
|
||||||
|
exclude_status = TRUE;
|
||||||
|
}
|
||||||
|
if (max_id > 0 && status_id >= max_id) {
|
||||||
|
exclude_status = TRUE;
|
||||||
|
}
|
||||||
|
if (min_id > 0 && status_id <= min_id) {
|
||||||
|
exclude_status = TRUE;
|
||||||
|
}
|
||||||
|
if (only_media && !Json.Get(status, "media_attachments")(JsonArray*)->length) {
|
||||||
|
exclude_status = TRUE;
|
||||||
|
}
|
||||||
|
if (exclude_replies && StrLen(status->@("in_reply_to_account_id")) > 0 && StrICmp(account_id, status->@("in_reply_to_account_id"))) {
|
||||||
|
exclude_status = TRUE;
|
||||||
|
}
|
||||||
|
if (pinned && !status->@("pinned")) {
|
||||||
|
exclude_status = TRUE;
|
||||||
|
}
|
||||||
|
if (!exclude_status) {
|
||||||
|
statuses->append(Json.CreateItem(status, JSON_OBJECT));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (limit > 0 && count >= limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_send_json(session, statuses);
|
||||||
|
|
||||||
|
Json.Delete(statuses);
|
||||||
|
goto slon_api_v1_accounts_get_return;
|
||||||
|
}
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
} else {
|
||||||
|
// Return the Account profile
|
||||||
|
JsonObject* profile_object = Json.Clone(acct);
|
||||||
|
profile_object->unset("source");
|
||||||
|
@slon_http_send_json(session, profile_object);
|
||||||
|
Json.Delete(profile_object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
slon_api_v1_accounts_get_return:
|
||||||
|
@slon_free(session, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @slon_api_v1_accounts_key_is_boolean(U8* name)
|
||||||
|
{
|
||||||
|
return (!StrICmp(name, "locked") || !StrICmp(name, "bot") || !StrICmp(name, "discoverable") || !StrICmp(name, "hide_collections") || !StrICmp(name, "indexable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_api_v1_accounts_patch(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
U8* path = @slon_strnew(session, @slon_http_request_path(session));
|
||||||
|
I64 path_segments_count = 0;
|
||||||
|
U8** path_segments = String.Split(path, '/', &path_segments_count);
|
||||||
|
|
||||||
|
JsonObject* acct = NULL;
|
||||||
|
|
||||||
|
if (!StrICmp("update_credentials", path_segments[3])) {
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
SLON_AUTH_ACCOUNT_ID
|
||||||
|
|
||||||
|
if (!request_json || !request_json->keys) {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
goto slon_api_v1_accounts_patch_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Support avatars/banners
|
||||||
|
acct = @slon_api_account_by_id(account_id);
|
||||||
|
if (!acct) {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
goto slon_api_v1_accounts_patch_return;
|
||||||
|
}
|
||||||
|
JsonObject* source = acct->@("source");
|
||||||
|
|
||||||
|
I64 fields_attributes_indexes[16];
|
||||||
|
I64 fields_attributes_count = 0;
|
||||||
|
U8* field_name;
|
||||||
|
U8* field_value;
|
||||||
|
JsonKey* update_field_index;
|
||||||
|
JsonObject* field_object;
|
||||||
|
Bool update_fields_from_form_data = FALSE;
|
||||||
|
Bool integer_is_in_index = FALSE;
|
||||||
|
|
||||||
|
I64 i;
|
||||||
|
I64 index;
|
||||||
|
MemSet(fields_attributes_indexes, NULL, sizeof(I64) * 16);
|
||||||
|
JsonArray* fields_array = Json.CreateArray();
|
||||||
|
|
||||||
|
JsonKey* key = request_json->keys;
|
||||||
|
while (key) {
|
||||||
|
if (!String.BeginsWith("fields_attributes", key->name) && !String.BeginsWith("source", key->name)) {
|
||||||
|
if (@slon_api_v1_accounts_key_is_boolean(key->name)) {
|
||||||
|
switch (key->type) {
|
||||||
|
case JSON_STRING:
|
||||||
|
acct->set(key->name, @slon_api_boolean_from_string(key->value), JSON_BOOLEAN);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
acct->set(key->name, key->value > 0, JSON_BOOLEAN);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acct->set(key->name, key->value, key->type);
|
||||||
|
}
|
||||||
|
} else if (String.BeginsWith("source", key->name)) {
|
||||||
|
if (!StrICmp("source[language]", key->name)) {
|
||||||
|
source->set("language", key->value);
|
||||||
|
}
|
||||||
|
if (!StrICmp("source[privacy]", key->name)) {
|
||||||
|
source->set("privacy", key->value);
|
||||||
|
}
|
||||||
|
} else if (String.BeginsWith("fields_attributes[", key->name)) {
|
||||||
|
// Get fields indexes from form data
|
||||||
|
update_fields_from_form_data = TRUE;
|
||||||
|
index = Str2I64(key->name + StrLen("fields_attributes["));
|
||||||
|
if (!fields_attributes_count) {
|
||||||
|
fields_attributes_indexes[fields_attributes_count] = index;
|
||||||
|
++fields_attributes_count;
|
||||||
|
} else {
|
||||||
|
integer_is_in_index = FALSE;
|
||||||
|
i = 0;
|
||||||
|
while (i < fields_attributes_count) {
|
||||||
|
if (index == fields_attributes_indexes[i])
|
||||||
|
integer_is_in_index = TRUE;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (!integer_is_in_index) {
|
||||||
|
fields_attributes_indexes[fields_attributes_count] = index;
|
||||||
|
++fields_attributes_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!StrICmp("fields_attributes", key->name)) {
|
||||||
|
// Get fields data from JSON object
|
||||||
|
AdamLog("let's get fields data from JSON object!!\n");
|
||||||
|
update_field_index = key->value(JsonObject*)->keys;
|
||||||
|
while (update_field_index) {
|
||||||
|
field_object = update_field_index->value;
|
||||||
|
field_object->set("verified_at", NULL, JSON_NULL);
|
||||||
|
AdamLog("before stringify\n");
|
||||||
|
AdamLog("%s\n", Json.Stringify(field_object));
|
||||||
|
AdamLog("after stringify\n");
|
||||||
|
fields_array->append(Json.CreateItem(field_object, JSON_OBJECT));
|
||||||
|
update_field_index = update_field_index->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key = key->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update_fields_from_form_data) {
|
||||||
|
for (i = 0; i < fields_attributes_count; i++) {
|
||||||
|
index = fields_attributes_indexes[i];
|
||||||
|
field_name = NULL;
|
||||||
|
field_value = NULL;
|
||||||
|
key = request_json->keys;
|
||||||
|
while (key) {
|
||||||
|
StrPrint(scratch_buffer, "fields_attributes[%d][name]", index);
|
||||||
|
if (String.BeginsWith(scratch_buffer, key->name)) {
|
||||||
|
field_name = key->value;
|
||||||
|
}
|
||||||
|
StrPrint(scratch_buffer, "fields_attributes[%d][value]", index);
|
||||||
|
if (String.BeginsWith(scratch_buffer, key->name)) {
|
||||||
|
field_value = key->value;
|
||||||
|
}
|
||||||
|
if (field_name && field_value) {
|
||||||
|
// create new field_object, and append to acct->fields
|
||||||
|
field_object = Json.CreateObject();
|
||||||
|
field_object->set("name", field_name, JSON_STRING);
|
||||||
|
field_object->set("value", field_value, JSON_STRING);
|
||||||
|
field_object->set("verified_at", NULL, JSON_NULL);
|
||||||
|
fields_array->append(Json.CreateItem(field_object, JSON_OBJECT));
|
||||||
|
field_name = NULL;
|
||||||
|
field_value = NULL;
|
||||||
|
}
|
||||||
|
key = key->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acct->set("fields", fields_array, JSON_ARRAY);
|
||||||
|
source->set("fields", acct->@("fields"), JSON_ARRAY);
|
||||||
|
|
||||||
|
@slon_db_save_accounts_to_disk;
|
||||||
|
@slon_db_actors_update_user(acct);
|
||||||
|
@slon_http_send_json(session, acct);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
slon_api_v1_accounts_patch_return:
|
||||||
|
@slon_free(session, path);
|
||||||
|
}
|
73
Slon/Api/V1/Apps.HC
Normal file
73
Slon/Api/V1/Apps.HC
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
U8* @slon_api_v1_apps_generate_app_id(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
U8* app_id = @slon_calloc(session, 16);
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < 6; i++) {
|
||||||
|
String.Append(app_id, "%d", RandU64 % 10);
|
||||||
|
}
|
||||||
|
return app_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_api_v1_apps_generate_client_id(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
U8* client_id = NULL;
|
||||||
|
Bool client_id_exists = TRUE;
|
||||||
|
while (client_id_exists) {
|
||||||
|
if (client_id)
|
||||||
|
@slon_free(session, client_id);
|
||||||
|
client_id = @slon_api_generate_random_hex_string(session, 16);
|
||||||
|
client_id_exists = db->o("apps")->@(client_id) > 0;
|
||||||
|
}
|
||||||
|
return client_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_api_v1_apps_generate_client_secret(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
return @slon_api_generate_random_hex_string(session, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_api_v1_apps_post(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer;
|
||||||
|
|
||||||
|
U8* id = @slon_api_v1_apps_generate_app_id(session);
|
||||||
|
U8* client_id = @slon_api_v1_apps_generate_client_id(session);
|
||||||
|
U8* client_secret = @slon_api_v1_apps_generate_client_secret(session);
|
||||||
|
|
||||||
|
I64 request_scopes_count = 0;
|
||||||
|
U8** request_scopes = NULL;
|
||||||
|
if (StrFind("+", request_json->@("scopes")) > 0) {
|
||||||
|
request_scopes = String.Split(request_json->@("scopes"), '+', &request_scopes_count);
|
||||||
|
} else {
|
||||||
|
request_scopes = String.Split(request_json->@("scopes"), ' ', &request_scopes_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray* scopes = Json.CreateArray();
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < request_scopes_count; i++) {
|
||||||
|
scopes->append(Json.CreateItem(request_scopes[i], JSON_STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonArray* redirect_uris = Json.CreateArray();
|
||||||
|
redirect_uris->append(Json.CreateItem(request_json->@("redirect_uris"), JSON_STRING));
|
||||||
|
|
||||||
|
JsonObject* credential_app = Json.CreateObject();
|
||||||
|
credential_app->set("id", id, JSON_STRING);
|
||||||
|
credential_app->set("name", request_json->@("client_name"), JSON_STRING);
|
||||||
|
credential_app->set("website", request_json->@("website"), JSON_STRING);
|
||||||
|
credential_app->set("scopes", scopes, JSON_ARRAY);
|
||||||
|
credential_app->set("redirect_uris", redirect_uris, JSON_ARRAY);
|
||||||
|
credential_app->set("redirect_uri", request_json->@("redirect_uris"), JSON_STRING);
|
||||||
|
credential_app->set("client_id", client_id, JSON_STRING);
|
||||||
|
credential_app->set("client_secret", client_secret, JSON_STRING);
|
||||||
|
credential_app->set("client_secret_expires_at", "0", JSON_STRING);
|
||||||
|
db->o("apps")->set(client_id, credential_app, JSON_OBJECT);
|
||||||
|
@slon_db_save_apps_to_disk;
|
||||||
|
|
||||||
|
@slon_http_send_json(session, credential_app);
|
||||||
|
|
||||||
|
@slon_free(session, id);
|
||||||
|
@slon_free(session, client_id);
|
||||||
|
@slon_free(session, client_secret);
|
||||||
|
}
|
12
Slon/Api/V1/Blocks.HC
Normal file
12
Slon/Api/V1/Blocks.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_blocks_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/Bookmarks.HC
Normal file
12
Slon/Api/V1/Bookmarks.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_bookmarks_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/Conversations.HC
Normal file
12
Slon/Api/V1/Conversations.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_conversations_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/CustomEmojis.HC
Normal file
12
Slon/Api/V1/CustomEmojis.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_custom_emojis_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/Favourites.HC
Normal file
12
Slon/Api/V1/Favourites.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_favourites_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/Filters.HC
Normal file
12
Slon/Api/V1/Filters.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_filters_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/FollowRequests.HC
Normal file
12
Slon/Api/V1/FollowRequests.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_follow_requests_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/FollowedTags.HC
Normal file
12
Slon/Api/V1/FollowedTags.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_followed_tags_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
16
Slon/Api/V1/Notifications.HC
Normal file
16
Slon/Api/V1/Notifications.HC
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
U0 @slon_api_v1_notifications_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
if (String.EndsWith("policy", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
|
||||||
|
} else {
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
144
Slon/Api/V1/Statuses.HC
Normal file
144
Slon/Api/V1/Statuses.HC
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
U0 (*@slon_api_status_create_fedi)(JsonObject* status) = NULL;
|
||||||
|
U0 (*@slon_api_status_delete_fedi)(JsonObject* status) = NULL;
|
||||||
|
|
||||||
|
U0 @slon_api_v1_statuses_delete(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
SLON_AUTH_ACCOUNT_ID
|
||||||
|
|
||||||
|
JsonArray* statuses = db->o("statuses")->a(account_id);
|
||||||
|
if (!statuses || !statuses->length) {
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* path = @slon_strnew(session, @slon_http_request_path(session));
|
||||||
|
I64 path_segments_count = 0;
|
||||||
|
U8** path_segments = String.Split(path, '/', &path_segments_count);
|
||||||
|
|
||||||
|
if (path_segments_count < 4) {
|
||||||
|
goto slon_api_v1_statuses_delete_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* id = path_segments[3];
|
||||||
|
JsonObject* status;
|
||||||
|
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < statuses->length; i++) {
|
||||||
|
status = statuses->@(i);
|
||||||
|
if (!StrICmp(status->@("id"), id)) {
|
||||||
|
status->set("deleted", TRUE, JSON_BOOLEAN);
|
||||||
|
@slon_db_save_statuses_to_disk;
|
||||||
|
@slon_db_instance_decrement_status_count;
|
||||||
|
@slon_db_save_instance_to_disk;
|
||||||
|
if (@slon_api_status_delete_fedi) {
|
||||||
|
@slon_api_status_delete_fedi(Json.Clone(status));
|
||||||
|
}
|
||||||
|
goto slon_api_v1_statuses_delete_return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slon_api_v1_statuses_delete_return:
|
||||||
|
Free(path_segments);
|
||||||
|
@slon_free(session, path);
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_api_v1_statuses_post(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
SLON_AUTH_ACCOUNT_ID
|
||||||
|
|
||||||
|
Bool idempotency_key_already_seen = FALSE;
|
||||||
|
U8* idempotency_key = @slon_http_request_header(session, "idempotency-key");
|
||||||
|
if (StrLen(idempotency_key) > 0 && db->o("idempotency_keys")->@(idempotency_key)) {
|
||||||
|
idempotency_key_already_seen = TRUE;
|
||||||
|
}
|
||||||
|
if (!idempotency_key_already_seen) {
|
||||||
|
Json.Set(db->o("idempotency_keys"), idempotency_key, Now, JSON_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* id = @slon_api_generate_unique_id(session);
|
||||||
|
U8* created_at = @slon_api_timestamp_from_cdate(session, Now);
|
||||||
|
|
||||||
|
JsonObject* app_object = db->o("apps")->@(Json.Get(session->auth, "client_id"));
|
||||||
|
|
||||||
|
JsonObject* status_app = Json.CreateObject();
|
||||||
|
status_app->set("name", app_object->@("name"), JSON_STRING);
|
||||||
|
status_app->set("website", app_object->@("website"), JSON_STRING);
|
||||||
|
|
||||||
|
JsonObject* account_object = Json.Clone(@slon_api_account_by_id(account_id));
|
||||||
|
account_object->unset("source");
|
||||||
|
|
||||||
|
// U8* language = request_json->@("language");
|
||||||
|
U8* username = account_object->@("username");
|
||||||
|
|
||||||
|
Bool sensitive = request_json->@("sensitive") > 0;
|
||||||
|
U8* in_reply_to_id = request_json->@("in_reply_to_id");
|
||||||
|
U8* visibility = request_json->@("visibility");
|
||||||
|
|
||||||
|
if (!StrLen(visibility)) {
|
||||||
|
visibility = "public";
|
||||||
|
}
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s/statuses/%s", db->o("instance")->@("uri"), username, id);
|
||||||
|
U8* uri = @slon_strnew(session, scratch_buffer);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/@%s/%s", db->o("instance")->@("uri"), username, id);
|
||||||
|
U8* url = @slon_strnew(session, scratch_buffer);
|
||||||
|
|
||||||
|
// Mona lets us post with: id, created_at, content, visibility, uri, url, account, application
|
||||||
|
// Mastodon iOS app lets us post with +: reblogs_count, favourites_count, emojis, tags, mentions
|
||||||
|
// IceCubesApp lets us post with +: media_attachments, replies_count, spoiler_text, sensitive
|
||||||
|
|
||||||
|
JsonObject* status = Json.CreateObject();
|
||||||
|
status->set("id", id, JSON_STRING);
|
||||||
|
status->set("created_at", created_at, JSON_STRING);
|
||||||
|
status->set("content", request_json->@("status"), JSON_STRING);
|
||||||
|
status->set("visibility", visibility, JSON_STRING);
|
||||||
|
status->set("uri", uri, JSON_STRING);
|
||||||
|
status->set("url", url, JSON_STRING);
|
||||||
|
status->set("account", account_object, JSON_OBJECT);
|
||||||
|
status->set("application", status_app, JSON_OBJECT);
|
||||||
|
status->set("reblogs_count", 0, JSON_NUMBER);
|
||||||
|
status->set("favourites_count", 0, JSON_NUMBER);
|
||||||
|
status->set("emojis", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
status->set("tags", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
status->set("mentions", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
status->set("media_attachments", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
status->set("replies_count", 0, JSON_NUMBER);
|
||||||
|
status->set("spoiler_text", "", JSON_STRING);
|
||||||
|
status->set("sensitive", sensitive, JSON_BOOLEAN);
|
||||||
|
|
||||||
|
if (StrLen(in_reply_to_id) > 0) {
|
||||||
|
status->set("in_reply_to_id", in_reply_to_id, JSON_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!idempotency_key_already_seen) {
|
||||||
|
db->o("statuses")->a(account_id)->append(Json.CreateItem(status, JSON_OBJECT));
|
||||||
|
@slon_db_save_statuses_to_disk;
|
||||||
|
@slon_db_instance_increment_status_count;
|
||||||
|
@slon_db_save_instance_to_disk;
|
||||||
|
if (@slon_api_status_create_fedi) {
|
||||||
|
@slon_api_status_create_fedi(Json.Clone(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_send_json(session, status);
|
||||||
|
|
||||||
|
Json.Delete(status_app);
|
||||||
|
Json.Delete(account_object);
|
||||||
|
Json.Delete(app_object);
|
||||||
|
|
||||||
|
@slon_free(session, uri);
|
||||||
|
@slon_free(session, url);
|
||||||
|
@slon_free(session, id);
|
||||||
|
@slon_free(session, created_at);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V1/Timelines.HC
Normal file
12
Slon/Api/V1/Timelines.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v1_timelines_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
12
Slon/Api/V2/Filters.HC
Normal file
12
Slon/Api/V2/Filters.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v2_filters_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
9
Slon/Api/V2/Instance.HC
Normal file
9
Slon/Api/V2/Instance.HC
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
U0 @slon_api_v2_instance_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn request_json;
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "{\"domain\":\"%s\"}", db->o("instance")->@("uri"));
|
||||||
|
@slon_http_set_content_type(session, "application/json; charset=utf-8");
|
||||||
|
@slon_http_send_string(session, scratch_buffer);
|
||||||
|
}
|
12
Slon/Api/V2/Suggestions.HC
Normal file
12
Slon/Api/V2/Suggestions.HC
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
U0 @slon_api_v2_suggestions_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (@slon_api_authorized(session)) {
|
||||||
|
// SLON_AUTH_ACCOUNT_ID
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
}
|
4
Slon/Endpoints/Delete/Statuses.HC
Normal file
4
Slon/Endpoints/Delete/Statuses.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (String.BeginsWith("/api/v1/statuses", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_statuses_delete(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Accounts.HC
Normal file
4
Slon/Endpoints/Get/Accounts.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (String.BeginsWith("/api/v1/accounts", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_accounts_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/ActivityPub.HC
Normal file
4
Slon/Endpoints/Get/ActivityPub.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (String.BeginsWith("/users/", @slon_http_request_path(session)) && String.EndsWith("json", @slon_http_request_header(session, "accept"))) {
|
||||||
|
@slon_activitypub_users_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Blocks.HC
Normal file
4
Slon/Endpoints/Get/Blocks.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/blocks", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_blocks_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Bookmarks.HC
Normal file
4
Slon/Endpoints/Get/Bookmarks.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/bookmarks", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_bookmarks_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Conversations.HC
Normal file
4
Slon/Endpoints/Get/Conversations.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/conversations", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_conversations_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/CustomEmojis.HC
Normal file
4
Slon/Endpoints/Get/CustomEmojis.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/custom_emojis", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_custom_emojis_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Favourites.HC
Normal file
4
Slon/Endpoints/Get/Favourites.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/favourites", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_favourites_get(session);
|
||||||
|
return;
|
||||||
|
}
|
9
Slon/Endpoints/Get/Filters.HC
Normal file
9
Slon/Endpoints/Get/Filters.HC
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
if (!StrICmp("/api/v1/filters", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_filters_get(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/api/v2/filters", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v2_filters_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/FollowRequests.HC
Normal file
4
Slon/Endpoints/Get/FollowRequests.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/follow_requests", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_follow_requests_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/FollowedTags.HC
Normal file
4
Slon/Endpoints/Get/FollowedTags.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/followed_tags", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_followed_tags_get(session);
|
||||||
|
return;
|
||||||
|
}
|
9
Slon/Endpoints/Get/Instance.HC
Normal file
9
Slon/Endpoints/Get/Instance.HC
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
if (!StrICmp("/api/v1/instance", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_send_json(session, db->o("instance"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/api/v2/instance", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v2_instance_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Notifications.HC
Normal file
4
Slon/Endpoints/Get/Notifications.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (String.BeginsWith("/api/v1/notifications", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_notifications_get(session);
|
||||||
|
return;
|
||||||
|
}
|
9
Slon/Endpoints/Get/OAuth.HC
Normal file
9
Slon/Endpoints/Get/OAuth.HC
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
if (!StrICmp("/oauth/authorize", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_send_html_file(session, "M:/Slon/Static/oauth/authorize.html");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/oauth/verify_access", @slon_http_request_path(session))) {
|
||||||
|
@slon_oauth_verify_access_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Suggestions.HC
Normal file
4
Slon/Endpoints/Get/Suggestions.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v2/suggestions", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v2_suggestions_get(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Get/Timelines.HC
Normal file
4
Slon/Endpoints/Get/Timelines.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (String.BeginsWith("/api/v1/timelines", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_timelines_get(session);
|
||||||
|
return;
|
||||||
|
}
|
28
Slon/Endpoints/Get/Web.HC
Normal file
28
Slon/Endpoints/Get/Web.HC
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
if (String.EndsWith(".css", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_set_content_type(session, "text/css");
|
||||||
|
@slon_http_send_file(session, "M:/Slon/Static/css/main.css");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/js/header.js", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_set_content_type(session, "text/javascript");
|
||||||
|
@slon_http_send_file(session, "M:/Slon/Static/js/header.js");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.EndsWith(".js", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_set_content_type(session, "text/javascript");
|
||||||
|
@slon_http_send_file(session, "M:/Slon/Static/js/statuses.js");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/alec.png", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_set_content_type(session, "image/png");
|
||||||
|
@slon_http_send_file(session, "A:/avatar-circle-4bpp.png");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.BeginsWith("/@", @slon_http_request_path(session))) {
|
||||||
|
@slon_web_user_get(session);
|
||||||
|
return;
|
||||||
|
}
|
14
Slon/Endpoints/Get/WellKnown.HC
Normal file
14
Slon/Endpoints/Get/WellKnown.HC
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
if (!StrICmp("/.well-known/host-meta", @slon_http_request_path(session))) {
|
||||||
|
@slon_host_meta(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/.well-known/oauth-authorization-server", @slon_http_request_path(session))) {
|
||||||
|
@slon_oauth_well_known(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/.well-known/webfinger", @slon_http_request_path(session))) {
|
||||||
|
@slon_webfinger(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Patch/Accounts.HC
Normal file
4
Slon/Endpoints/Patch/Accounts.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (String.BeginsWith("/api/v1/accounts", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_accounts_patch(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Post/ActivityPub.HC
Normal file
4
Slon/Endpoints/Post/ActivityPub.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (String.BeginsWith("/users/", @slon_http_request_path(session))) {
|
||||||
|
@slon_activitypub_users_post(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Post/Apps.HC
Normal file
4
Slon/Endpoints/Post/Apps.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/apps", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_apps_post(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Post/OAuth.HC
Normal file
4
Slon/Endpoints/Post/OAuth.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/oauth/token", @slon_http_request_path(session))) {
|
||||||
|
@slon_oauth_token_post(session);
|
||||||
|
return;
|
||||||
|
}
|
4
Slon/Endpoints/Post/Statuses.HC
Normal file
4
Slon/Endpoints/Post/Statuses.HC
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
if (!StrICmp("/api/v1/statuses", @slon_http_request_path(session))) {
|
||||||
|
@slon_api_v1_statuses_post(session);
|
||||||
|
return;
|
||||||
|
}
|
394
Slon/Http/AdminServer.HC
Normal file
394
Slon/Http/AdminServer.HC
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
U0 @slon_admin_html_form_from_json_object(SlonHttpSession* session, U8* buf, JsonObject* o)
|
||||||
|
{
|
||||||
|
if (!session || !buf || !o)
|
||||||
|
return;
|
||||||
|
|
||||||
|
JsonKey* key = o->keys;
|
||||||
|
String.Append(buf, "<table>");
|
||||||
|
while (key) {
|
||||||
|
switch (key->type) {
|
||||||
|
case JSON_BOOLEAN:
|
||||||
|
case JSON_STRING:
|
||||||
|
case JSON_NUMBER:
|
||||||
|
String.Append(buf, "<tr><td><label>%s</label></td><td>", key->name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (key->type) {
|
||||||
|
case JSON_BOOLEAN:
|
||||||
|
String.Append(buf, "<input name=%s type=checkbox %s>", key->name, @t(key->value, "checked", ""));
|
||||||
|
break;
|
||||||
|
case JSON_STRING:
|
||||||
|
String.Append(buf, "<input name=%s type=text value=\"%s\" required>", key->name, key->value);
|
||||||
|
break;
|
||||||
|
case JSON_NUMBER:
|
||||||
|
String.Append(buf, "<input name=%s type=text value=\"%d\" required>", key->name, ToI64(key->value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String.Append(buf, "</td></tr>");
|
||||||
|
key = key->next;
|
||||||
|
}
|
||||||
|
String.Append(buf, "</table>");
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_create_ap_actor(SlonHttpSession* session, JsonObject* acct)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
JsonObject* actors = db->o("actors");
|
||||||
|
U8* domain = db->o("instance")->@("uri");
|
||||||
|
U8* username = acct->@("username");
|
||||||
|
|
||||||
|
JsonObject* actor = Json.Clone(SLON_DEFAULT_ACTOR_OBJECT);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s", domain, username);
|
||||||
|
actor->set("id", scratch_buffer, JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s/following", domain, username);
|
||||||
|
actor->set("following", scratch_buffer, JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s/followers", domain, username);
|
||||||
|
actor->set("followers", scratch_buffer, JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s/inbox", domain, username);
|
||||||
|
actor->set("inbox", scratch_buffer, JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s/outbox", domain, username);
|
||||||
|
actor->set("outbox", scratch_buffer, JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s/collections/featured", domain, username);
|
||||||
|
actor->set("featured", scratch_buffer, JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s/collections/tags", domain, username);
|
||||||
|
actor->set("featuredTags", scratch_buffer, JSON_STRING);
|
||||||
|
actor->set("preferredUsername", username, JSON_STRING);
|
||||||
|
actor->set("name", acct->@("display_name"), JSON_STRING);
|
||||||
|
actor->set("summary", acct->@("note"), JSON_STRING);
|
||||||
|
|
||||||
|
JsonObject* icon = Json.Parse("{\"type\":\"Image\"}");
|
||||||
|
icon->set("url", acct->@("avatar"), JSON_STRING);
|
||||||
|
actor->set("icon", icon, JSON_OBJECT);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "https://%s/@%s", domain, username);
|
||||||
|
actor->set("url", scratch_buffer, JSON_STRING);
|
||||||
|
actor->set("published", acct->@("created_at"), JSON_STRING);
|
||||||
|
actor->set("attachment", acct->@("fields"), JSON_ARRAY);
|
||||||
|
actor->set("accountId", acct->@("id"), JSON_STRING);
|
||||||
|
|
||||||
|
db->o("private_keys")->set(username, request_json->@("privatekey"), JSON_STRING);
|
||||||
|
|
||||||
|
JsonObject* publickey = Json.CreateObject();
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s#main-key", domain, username);
|
||||||
|
publickey->set("id", scratch_buffer, JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s", domain, username);
|
||||||
|
publickey->set("owner", scratch_buffer, JSON_STRING);
|
||||||
|
I64 x;
|
||||||
|
publickey->set("publicKeyPem", @base64_decode(request_json->@("publickey"), &x), JSON_STRING);
|
||||||
|
actor->set("publicKey", publickey, JSON_OBJECT);
|
||||||
|
|
||||||
|
actors->set(acct->@("username"), actor, JSON_OBJECT);
|
||||||
|
@slon_db_save_to_disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_create_account(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
U8* id = @slon_api_generate_unique_id(session);
|
||||||
|
U8* created_at = @slon_api_timestamp_from_cdate(session, Now);
|
||||||
|
|
||||||
|
JsonObject* acct = Json.CreateObject();
|
||||||
|
JsonObject* source = Json.CreateObject();
|
||||||
|
|
||||||
|
acct->set("id", id, JSON_STRING);
|
||||||
|
acct->set("created_at", created_at, JSON_STRING);
|
||||||
|
acct->set("username", request_json->@("username"), JSON_STRING);
|
||||||
|
acct->set("acct", request_json->@("username"), JSON_STRING);
|
||||||
|
acct->set("display_name", request_json->@("display_name"), JSON_STRING);
|
||||||
|
acct->set("email", request_json->@("email"), JSON_STRING);
|
||||||
|
acct->set("note", request_json->@("bio"), JSON_STRING);
|
||||||
|
acct->set("avatar", request_json->@("avatar"), JSON_STRING);
|
||||||
|
acct->set("header", request_json->@("header"), JSON_STRING);
|
||||||
|
acct->set("avatar_static", acct->@("avatar"), JSON_STRING);
|
||||||
|
acct->set("header_static", acct->@("header"), JSON_STRING);
|
||||||
|
acct->set("last_status_at", "0", JSON_STRING);
|
||||||
|
|
||||||
|
acct->set("followers_count", 0, JSON_NUMBER);
|
||||||
|
acct->set("following_count", 0, JSON_NUMBER);
|
||||||
|
acct->set("statuses_count", 0, JSON_NUMBER);
|
||||||
|
|
||||||
|
acct->set("locked", FALSE, JSON_BOOLEAN);
|
||||||
|
acct->set("bot", FALSE, JSON_BOOLEAN);
|
||||||
|
acct->set("discoverable", FALSE, JSON_BOOLEAN);
|
||||||
|
acct->set("indexable", FALSE, JSON_BOOLEAN);
|
||||||
|
acct->set("hide_collections", FALSE, JSON_BOOLEAN);
|
||||||
|
|
||||||
|
acct->set("emojis", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
acct->set("fields", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
|
||||||
|
source->set("privacy", "public", JSON_STRING);
|
||||||
|
source->set("sensitive", FALSE, JSON_BOOLEAN);
|
||||||
|
source->set("language", "", JSON_STRING);
|
||||||
|
source->set("note", acct->@("note"), JSON_STRING);
|
||||||
|
source->set("fields", acct->@("fields"), JSON_ARRAY);
|
||||||
|
source->set("follow_requests_count", 0, JSON_NUMBER);
|
||||||
|
|
||||||
|
acct->set("source", source, JSON_OBJECT);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "https://%s/@%s", db->o("instance")->@("uri"), acct->@("username"));
|
||||||
|
acct->set("url", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
db->a("accounts")->append(Json.CreateItem(acct, JSON_OBJECT));
|
||||||
|
db->o("statuses")->set(acct->@("id"), Json.CreateArray(), JSON_ARRAY);
|
||||||
|
@slon_admin_create_ap_actor(session, acct);
|
||||||
|
|
||||||
|
@slon_db_instance_update_user_count;
|
||||||
|
@slon_db_save_to_disk;
|
||||||
|
|
||||||
|
@slon_free(session, created_at);
|
||||||
|
@slon_free(session, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_settings_accounts_new_get(SlonHttpSession* session, U8* buf)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer, request_json;
|
||||||
|
String.Append(buf, "settings/accounts/new");
|
||||||
|
String.Append(buf, "<br><br><form action=/settings/accounts/new/save>");
|
||||||
|
@slon_admin_html_form_from_json_object(session, buf, SLON_DEFAULT_ACCT_OBJECT);
|
||||||
|
String.Append(buf, "<br><br><input id=save type=submit value=Save></form>");
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_settings_accounts_get(SlonHttpSession* session, U8* buf)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer, request_json;
|
||||||
|
String.Append(buf, "settings/accounts");
|
||||||
|
String.Append(buf, "<br><br><table><tr><th>id</th><th>username</th></tr>");
|
||||||
|
JsonArray* arr = db->a("accounts");
|
||||||
|
JsonObject* acct;
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < arr->length; i++) {
|
||||||
|
acct = arr->@(i);
|
||||||
|
if (acct) {
|
||||||
|
String.Append(buf, "<tr><td>%s</td><td>%s</td></tr>", acct->@("id"), acct->@("username"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String.Append(buf, "</table><br><br><button onclick=\"window.location='/settings/accounts/new'\">New</button>");
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_settings_apps_get(SlonHttpSession* session, U8* buf)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer, request_json;
|
||||||
|
String.Append(buf, "settings/apps");
|
||||||
|
String.Append(buf, "<br><br>");
|
||||||
|
U8* tmp = Json.Stringify(db->o("apps"));
|
||||||
|
String.Append(buf, tmp);
|
||||||
|
Free(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_settings_oauth_get(SlonHttpSession* session, U8* buf)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer, request_json;
|
||||||
|
String.Append(buf, "settings/oauth");
|
||||||
|
String.Append(buf, "<br><br>");
|
||||||
|
U8* tmp = Json.Stringify(db->o("oauth"));
|
||||||
|
String.Append(buf, tmp);
|
||||||
|
Free(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_settings_instance_save_get(SlonHttpSession* session, U8* buf)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer;
|
||||||
|
|
||||||
|
JsonObject* instance = db->o("instance");
|
||||||
|
instance->set("uri", request_json->@("uri"));
|
||||||
|
instance->set("title", request_json->@("title"));
|
||||||
|
instance->set("short_description", request_json->@("short_description"));
|
||||||
|
instance->set("description", request_json->@("description"));
|
||||||
|
instance->set("email", request_json->@("email"));
|
||||||
|
instance->set("version", request_json->@("version"));
|
||||||
|
if (!request_json->@("registrations")) {
|
||||||
|
instance->set("registrations", FALSE);
|
||||||
|
} else {
|
||||||
|
instance->set("registrations", !StrICmp("on", request_json->@("registrations")));
|
||||||
|
}
|
||||||
|
String.Append(buf, "<script>window.location='/settings/instance';</script>");
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_settings_instance_get(SlonHttpSession* session, U8* buf)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer, request_json;
|
||||||
|
|
||||||
|
String.Append(buf, "settings/instance");
|
||||||
|
String.Append(buf, "<br><br><form action=/settings/instance/save>");
|
||||||
|
@slon_admin_html_form_from_json_object(session, buf, db->o("instance"));
|
||||||
|
String.Append(buf, "<br><br><input type=submit value=Save></form>");
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_new_account(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
if (db->o("actors")->@(request_json->@("username"))) {
|
||||||
|
StrPrint(scratch_buffer, "{\"error\":\"account already exists\"}");
|
||||||
|
@slon_http_set_content_type(session, "application/json");
|
||||||
|
@slon_http_send(session, scratch_buffer, StrLen(scratch_buffer));
|
||||||
|
} else {
|
||||||
|
@slon_admin_create_account(session);
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_manage_accounts(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
@slon_http_send_json(session, db->a("accounts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_info_stats(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn request_json;
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "{");
|
||||||
|
String.Append(scratch_buffer, "\"uptime\":\"%d\"", cnts.jiffies);
|
||||||
|
String.Append(scratch_buffer, "}");
|
||||||
|
|
||||||
|
@slon_http_set_content_type(session, "application/json");
|
||||||
|
@slon_http_send(session, scratch_buffer, StrLen(scratch_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_server_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (!db->@("setup")) {
|
||||||
|
if (StrICmp("/", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_set_status_code(session, 302);
|
||||||
|
@slon_http_set_header(session, "Location", "/");
|
||||||
|
} else {
|
||||||
|
@slon_http_send_html_file(session, "M:/Slon/Static/html/admin/setup_instance.html");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/info/stats", @slon_http_request_path(session))) {
|
||||||
|
@slon_admin_info_stats(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/manage/accounts", @slon_http_request_path(session))) {
|
||||||
|
@slon_admin_manage_accounts(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/", @slon_http_request_path(session))) {
|
||||||
|
@slon_http_send_html_file(session, "M:/Slon/Static/html/admin/main.html");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_setup_instance(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer;
|
||||||
|
|
||||||
|
JsonObject* instance = db->o("instance");
|
||||||
|
instance->set("uri", request_json->@("uri"));
|
||||||
|
instance->set("title", request_json->@("title"));
|
||||||
|
instance->set("short_description", request_json->@("description"));
|
||||||
|
instance->set("description", request_json->@("description"));
|
||||||
|
instance->set("email", request_json->@("email"));
|
||||||
|
instance->set("registrations", request_json->@("registrations"));
|
||||||
|
@slon_db_save_to_disk;
|
||||||
|
db->set("setup", TRUE);
|
||||||
|
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_server_post(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (StrFind("json", @slon_http_request_header(session, "content-type")) > 0) {
|
||||||
|
@slon_http_parse_request_as_json(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/setup/instance", @slon_http_request_path(session))) {
|
||||||
|
@slon_admin_setup_instance(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("/new/account", @slon_http_request_path(session))) {
|
||||||
|
@slon_admin_new_account(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_http_handle_get_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (@slon_http_request_has_query_string(session)) {
|
||||||
|
@slon_http_parse_query_string(session);
|
||||||
|
}
|
||||||
|
@slon_admin_server_get(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_http_handle_post_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
@slon_admin_server_post(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_http_handle_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
switch (@slon_http_request_verb(session)) {
|
||||||
|
case SLON_HTTP_VERB_GET:
|
||||||
|
@slon_admin_http_handle_get_request(session);
|
||||||
|
break;
|
||||||
|
case SLON_HTTP_VERB_POST:
|
||||||
|
@slon_admin_http_handle_post_request(session);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
@slon_http_set_status_code(session, 405);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_admin_http_task(TcpSocket* s)
|
||||||
|
{
|
||||||
|
// Bail if we can't acquire socket for some reason
|
||||||
|
if (!@tcp_socket_accept(s))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Init session
|
||||||
|
SlonHttpSession* session = @slon_http_init_session(s);
|
||||||
|
|
||||||
|
// Parse headers if they are available
|
||||||
|
while (!@slon_http_request_headers_have_been_parsed(session)) {
|
||||||
|
@slon_http_receive(session);
|
||||||
|
|
||||||
|
// Handle malformed requests (anything less than "GET / HTTP/1.0\r\n\r\n" is probably a bad request)
|
||||||
|
if (session->request->buffer->size < 18) {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
goto slon_admin_http_task_send_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_try_parse_request_headers(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a content-length header, consume until we receive all the data, then set request->data pointer and size
|
||||||
|
if (StrLen(@slon_http_request_header(session, "content-length"))) {
|
||||||
|
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
|
||||||
|
while (session->request->buffer->data + session->request->buffer->size - session->request->data < content_length)
|
||||||
|
@slon_http_receive(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_admin_http_handle_request(session);
|
||||||
|
|
||||||
|
slon_admin_http_task_send_response:
|
||||||
|
@slon_http_send_response(session);
|
||||||
|
@slon_http_free_session(session);
|
||||||
|
s->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Adam("U0 @spawn_slon_admin_http_task(TcpSocket *s){Spawn(%d, s, \"SlonAdminHttpTask\");};\n", &@slon_admin_http_task);
|
||||||
|
@tcp_socket_bind(9000, "@spawn_slon_admin_http_task");
|
232
Slon/Http/LocalServer.HC
Normal file
232
Slon/Http/LocalServer.HC
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
U0 @slon_local_server_set_mime_type(SlonHttpSession* session, U8* filepath)
|
||||||
|
{
|
||||||
|
// FIXME: Do this programmatically like the Jakt version, this is awful
|
||||||
|
if (String.EndsWith(".html", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "text/html");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String.EndsWith(".txt", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "text/plain");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String.EndsWith(".css", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "text/css");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String.EndsWith(".js", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "text/javascript");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String.EndsWith(".json", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "application/json");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String.EndsWith(".gif", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "image/gif");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String.EndsWith(".png", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "image/png");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (String.EndsWith(".jpeg", filepath) || String.EndsWith(".jpg", filepath)) {
|
||||||
|
@slon_http_set_content_type(session, "image/jpeg");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@slon_http_set_content_type(session, "application/octet-stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_local_server_send_file(SlonHttpSession* session, U8* filepath)
|
||||||
|
{
|
||||||
|
@slon_local_server_set_mime_type(session, filepath);
|
||||||
|
@slon_http_send_file(session, filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_local_server_directory_listing(SlonHttpSession* session, U8* path)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn request_json;
|
||||||
|
|
||||||
|
U8* html = @slon_calloc(session, 1048576);
|
||||||
|
|
||||||
|
String.Append(html, "<html><head><title>Index of ");
|
||||||
|
String.Append(html, path);
|
||||||
|
String.Append(html, "</title><style type=text/css>.img-back{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAMAAAD3n0w0AAAAElBMVEX////M//+ZmZlmZmYzMzMAAACei5rnAAAAAnRSTlP/AOW3MEoAAABVSURBVHjabdFBCsBACENR45j7X7kQtC0T//KRjRhYevGgyjBL+VLZUtlS2VItS1AI1QQONgNZHCSUZJAc+ZB3sViFGzPcDmxZqdsvgRB/aJRu73D0HuO2BJfZn2SOAAAAAElFTkSuQmCC)} .img-folder{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAMAAAD3n0w0AAAAElBMVEX/////zJnM//+ZZjMzMzMAAADCEvqoAAAAA3RSTlP//wDXyg1BAAAASElEQVR42s3KAQbAQAxE0W4m//5XboesdihQ6A/ES4566TsyPZE1caNtwmFE22bBuDTtG8ZMaoyZ8Z+fijEWytpYdEZfWGRdJzEsA9OaTRTxAAAAAElFTkSuQmCC)} .img-file{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAMAAAD3n0w0AAAAD1BMVEX////M//+ZmZkzMzMAAABVsTOVAAAAAnRSTlP/AOW3MEoAAAA6SURBVHja3cjHAcAwEIRATqj/lp3jWhUYfoPPag+5EkeII8QRYmB3O3ArENrSB0k8+ivaXrliVY+qZm7SAaxLXnOsAAAAAElFTkSuQmCC)}</style></head><body><h1>Index of ");
|
||||||
|
String.Append(html, path);
|
||||||
|
String.Append(html, "</h1><table>");
|
||||||
|
String.Append(html, "<tr><th></th><th align=left style=padding-right:16px>Name</th><th align=left>Last modified</th><th align=right>Size</th></tr>");
|
||||||
|
String.Append(html, "<tr><th colspan=4><hr></th></tr>");
|
||||||
|
StrPrint(scratch_buffer, "A:%s*", path);
|
||||||
|
CDirEntry* files = FilesFind(scratch_buffer);
|
||||||
|
CDirEntry* de = files->next;
|
||||||
|
CDateStruct ds;
|
||||||
|
while (de) {
|
||||||
|
String.Append(html, "<tr><td><a href=\"");
|
||||||
|
String.Append(html, de->name);
|
||||||
|
String.Append(html, "\">");
|
||||||
|
if (!StrICmp("..", de->name)) {
|
||||||
|
String.Append(html, "<img class=img-back alt=Back>");
|
||||||
|
} else {
|
||||||
|
if (de->attr & RS_ATTR_DIR) {
|
||||||
|
String.Append(html, "<img class=img-folder alt=Folder>");
|
||||||
|
} else {
|
||||||
|
String.Append(html, "<img class=img-file alt=File>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String.Append(html, "</a></td>");
|
||||||
|
String.Append(html, "<td><a href=\"");
|
||||||
|
String.Append(html, de->name);
|
||||||
|
if (de->attr & RS_ATTR_DIR) {
|
||||||
|
String.Append(html, "/");
|
||||||
|
}
|
||||||
|
String.Append(html, "\">");
|
||||||
|
if (!StrICmp("..", de->name)) {
|
||||||
|
String.Append(html, "Parent Directory");
|
||||||
|
} else {
|
||||||
|
String.Append(html, de->name);
|
||||||
|
}
|
||||||
|
String.Append(html, "</a></td><td align=right>");
|
||||||
|
Date2Struct(&ds, de->datetime);
|
||||||
|
String.Append(html, "%02d-%03tZ-%04d %02d:%02d", ds.day_of_mon, ds.mon - 1, "ST_MONTHS", ds.year, ds.hour, ds.min);
|
||||||
|
String.Append(html, " ");
|
||||||
|
String.Append(html, "</td><td align=right>");
|
||||||
|
if (de->attr & RS_ATTR_DIR) {
|
||||||
|
String.Append(html, " - ");
|
||||||
|
} else {
|
||||||
|
String.Append(html, "%d", de->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
String.Append(html, "</td></tr>");
|
||||||
|
de = de->next;
|
||||||
|
}
|
||||||
|
DirTreeDel(files);
|
||||||
|
|
||||||
|
String.Append(html, "<tr><th colspan=4><hr></th></tr>");
|
||||||
|
String.Append(html, "</table>");
|
||||||
|
String.Append(html, "<address>Slon static file webserver for (TempleOS) Server</address>");
|
||||||
|
String.Append(html, "</body></html>");
|
||||||
|
|
||||||
|
@slon_http_set_content_type(session, "text/html");
|
||||||
|
@slon_http_send(session, html, StrLen(html));
|
||||||
|
@slon_free(session, html);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_local_server_not_found(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
@slon_http_set_content_type(session, "text/html");
|
||||||
|
@slon_http_send(session, "<h2>404 Not Found</h2>", 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_local_server_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn request_json;
|
||||||
|
|
||||||
|
U8* path = @slon_http_request_path(session);
|
||||||
|
|
||||||
|
if (!path || !StrLen(path) || StrFind(":", path) > 0) {
|
||||||
|
@slon_local_server_not_found(session);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path[0] == '/' && StrLen(path) == 1) {
|
||||||
|
// Handle root path
|
||||||
|
if (FileFind("A:/index.html")) {
|
||||||
|
@slon_local_server_send_file(session, "A:/index.html");
|
||||||
|
} else {
|
||||||
|
@slon_local_server_directory_listing(session, "/");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.EndsWith("/", path)) {
|
||||||
|
StrPrint(scratch_buffer, "A:%sindex.html", path);
|
||||||
|
if (FileFind(scratch_buffer)) {
|
||||||
|
@slon_local_server_send_file(session, scratch_buffer);
|
||||||
|
} else {
|
||||||
|
StrPrint(scratch_buffer, "A:%s", path);
|
||||||
|
scratch_buffer[StrLen(scratch_buffer) - 1] = NULL;
|
||||||
|
if (IsDir(scratch_buffer)) {
|
||||||
|
@slon_local_server_directory_listing(session, path);
|
||||||
|
} else {
|
||||||
|
@slon_local_server_not_found(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "A:%s", path);
|
||||||
|
if (!FileFind(scratch_buffer)) {
|
||||||
|
@slon_local_server_not_found(session);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (IsDir(scratch_buffer)) {
|
||||||
|
@slon_http_set_status_code(session, 301);
|
||||||
|
StrPrint(scratch_buffer, "%s/", path);
|
||||||
|
@slon_http_set_header(session, "Location", scratch_buffer);
|
||||||
|
} else {
|
||||||
|
@slon_local_server_send_file(session, scratch_buffer);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldn't get here :^)
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_local_http_handle_get_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
@slon_local_server_get(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_local_http_handle_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
switch (@slon_http_request_verb(session)) {
|
||||||
|
case SLON_HTTP_VERB_GET:
|
||||||
|
@slon_local_http_handle_get_request(session);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
@slon_http_set_status_code(session, 405);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_local_http_task(TcpSocket* s)
|
||||||
|
{
|
||||||
|
// Bail if we can't acquire socket for some reason
|
||||||
|
if (!@tcp_socket_accept(s))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Init session
|
||||||
|
SlonHttpSession* session = @slon_http_init_session(s);
|
||||||
|
|
||||||
|
// Parse headers if they are available
|
||||||
|
while (!@slon_http_request_headers_have_been_parsed(session)) {
|
||||||
|
@slon_http_receive(session);
|
||||||
|
|
||||||
|
// Handle malformed requests (anything less than "GET / HTTP/1.0\r\n\r\n" is probably a bad request)
|
||||||
|
if (session->request->buffer->size < 18) {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
goto slon_local_http_task_send_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_try_parse_request_headers(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a content-length header, consume until we receive all the data, then set request->data pointer and size
|
||||||
|
if (StrLen(@slon_http_request_header(session, "content-length"))) {
|
||||||
|
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
|
||||||
|
while (session->request->buffer->data + session->request->buffer->size - session->request->data < content_length)
|
||||||
|
@slon_http_receive(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_local_http_handle_request(session);
|
||||||
|
|
||||||
|
slon_local_http_task_send_response:
|
||||||
|
@slon_http_send_response(session);
|
||||||
|
@slon_http_free_session(session);
|
||||||
|
s->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Adam("U0 @spawn_slon_local_http_task(TcpSocket *s){Spawn(%d, s, \"SlonLocalHttpTask\");};\n", &@slon_local_http_task);
|
||||||
|
@tcp_socket_bind(8000, "@spawn_slon_local_http_task");
|
542
Slon/Http/Server.HC
Normal file
542
Slon/Http/Server.HC
Normal file
|
@ -0,0 +1,542 @@
|
||||||
|
SlonHttpBuffer* @slon_http_init_buffer(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SlonHttpBuffer* buffer = @slon_calloc(session, sizeof(SlonHttpBuffer));
|
||||||
|
buffer->data = @slon_calloc(session, SLON_HTTP_BUFFER_SIZE);
|
||||||
|
buffer->capacity = SLON_HTTP_BUFFER_SIZE;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_free_response(SlonHttpSession* session, SlonHttpResponse* response)
|
||||||
|
{
|
||||||
|
// FIXME: Free headers JsonObject
|
||||||
|
if (response) {
|
||||||
|
if (response->buffer && response->buffer->data) {
|
||||||
|
@slon_free(session, response->buffer->data);
|
||||||
|
@slon_free(session, response->buffer);
|
||||||
|
}
|
||||||
|
@slon_free(session, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_free_request(SlonHttpSession* session, SlonHttpRequest* request)
|
||||||
|
{
|
||||||
|
// FIXME: Free headers JsonObject
|
||||||
|
if (request) {
|
||||||
|
if (request->buffer && request->buffer->data) {
|
||||||
|
@slon_free(session, request->buffer->data);
|
||||||
|
@slon_free(session, request->buffer);
|
||||||
|
}
|
||||||
|
if (request->verb)
|
||||||
|
@slon_free(session, session->request->verb);
|
||||||
|
if (request->raw_path)
|
||||||
|
@slon_free(session, session->request->raw_path);
|
||||||
|
if (request->path)
|
||||||
|
@slon_free(session, session->request->path);
|
||||||
|
@slon_free(session, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_free_session(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (!session)
|
||||||
|
return;
|
||||||
|
@slon_http_free_response(session, session->response);
|
||||||
|
@slon_http_free_request(session, session->request);
|
||||||
|
I64 bytes_used = session->bytes_used - MSize2(session);
|
||||||
|
Free(session);
|
||||||
|
if (bytes_used) {
|
||||||
|
AdamLog("*** Session leaked %d bytes of memory ***\n", bytes_used);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SlonHttpRequest* @slon_http_init_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SlonHttpRequest* request = @slon_calloc(session, sizeof(SlonHttpRequest));
|
||||||
|
request->buffer = @slon_http_init_buffer(session);
|
||||||
|
request->headers = Json.CreateObject();
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlonHttpResponse* @slon_http_init_response(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SlonHttpResponse* response = @slon_calloc(session, sizeof(SlonHttpResponse));
|
||||||
|
response->buffer = @slon_http_init_buffer(session);
|
||||||
|
response->headers = Json.CreateObject();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlonHttpSession* @slon_http_init_session(TcpSocket* s)
|
||||||
|
{
|
||||||
|
SlonHttpSession* session = CAlloc(sizeof(SlonHttpSession), adam_task);
|
||||||
|
session->bytes_used = MSize2(session);
|
||||||
|
session->s = s;
|
||||||
|
session->request = @slon_http_init_request(session);
|
||||||
|
session->response = @slon_http_init_response(session);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_receive(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
// FIXME: grow the buffer
|
||||||
|
SlonHttpBuffer* buffer = session->request->buffer;
|
||||||
|
I64 chunk_size = @tcp_socket_receive(session->s, buffer->data + buffer->size, 65536);
|
||||||
|
buffer->size += chunk_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @slon_http_request_headers_have_been_parsed(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
return session->request->headers_have_been_parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_buffer_append(SlonHttpBuffer* buffer, U8* src, I64 size)
|
||||||
|
{
|
||||||
|
if (!buffer || !src || !size)
|
||||||
|
return;
|
||||||
|
MemCpy(buffer->data + buffer->size, src, size);
|
||||||
|
buffer->size += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_buffer_append_string(SlonHttpBuffer* buffer, U8* str)
|
||||||
|
{
|
||||||
|
@slon_http_buffer_append(buffer, str, StrLen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send_response(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SlonHttpBuffer* buffer = session->response->buffer;
|
||||||
|
U8 scratch_buffer[256][4];
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer[0], "%d", session->response->status_code);
|
||||||
|
StrPrint(scratch_buffer[1], "HTTP/1.0 %d %s\r\n", session->response->status_code, Json.Get(SLON_HTTP_STATUS_CODES, scratch_buffer[0]));
|
||||||
|
@slon_http_buffer_append_string(buffer, scratch_buffer[1]);
|
||||||
|
|
||||||
|
JsonKey* key = session->response->headers->keys;
|
||||||
|
while (key) {
|
||||||
|
StrPrint(scratch_buffer[0], "%s: %s\r\n", key->name, key->value);
|
||||||
|
@slon_http_buffer_append_string(buffer, scratch_buffer[0]);
|
||||||
|
key = key->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer[0], "content-length: %d\r\n", session->response->size);
|
||||||
|
@slon_http_buffer_append_string(buffer, scratch_buffer[0]);
|
||||||
|
|
||||||
|
StrCpy(scratch_buffer[0], "pragma: no-cache\r\n\r\n");
|
||||||
|
@slon_http_buffer_append_string(buffer, scratch_buffer[0]);
|
||||||
|
|
||||||
|
if (session->response->data && session->response->size) {
|
||||||
|
@slon_http_buffer_append(buffer, session->response->data, session->response->size);
|
||||||
|
@slon_free(session, session->response->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@tcp_socket_send(session->s, buffer->data, buffer->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_rstrip_char_from_string(U8* str, I64 ch)
|
||||||
|
{
|
||||||
|
while (str[StrLen(str) - 1] == ch)
|
||||||
|
str[StrLen(str) - 1] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_try_parse_request_headers(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SlonHttpBuffer* buffer = session->request->buffer;
|
||||||
|
I64 i = 0;
|
||||||
|
// Do we have headers yet? let's find out
|
||||||
|
while (i < buffer->size) {
|
||||||
|
if (!MemCmp(buffer->data + i, "\r\n\r\n", 4)) {
|
||||||
|
i += 4;
|
||||||
|
goto slon_http_parse_request_headers;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
slon_http_parse_request_headers:
|
||||||
|
// Set pointer for request content
|
||||||
|
session->request->data = buffer->data + i;
|
||||||
|
|
||||||
|
// We have headers, let's parse them
|
||||||
|
U8* raw_headers = @slon_calloc(session, i);
|
||||||
|
MemCpy(raw_headers, buffer->data, i - 4);
|
||||||
|
|
||||||
|
I64 raw_header_lines_count = 0;
|
||||||
|
U8** raw_header_lines = String.Split(raw_headers, '\n', &raw_header_lines_count);
|
||||||
|
|
||||||
|
if (!raw_header_lines_count) {
|
||||||
|
// FIXME: Handle this
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 request_first_line_segments_count = 0;
|
||||||
|
U8** request_first_line_segments = String.Split(raw_header_lines[0], ' ', &request_first_line_segments_count);
|
||||||
|
|
||||||
|
if (request_first_line_segments_count < 2) {
|
||||||
|
// FIXME: Handle this
|
||||||
|
}
|
||||||
|
|
||||||
|
session->request->verb = @slon_strnew(session, request_first_line_segments[0]);
|
||||||
|
session->request->raw_path = @slon_strnew(session, request_first_line_segments[1]);
|
||||||
|
if (StrFind("?", session->request->raw_path)) {
|
||||||
|
session->request->path = @slon_strnew(session, session->request->raw_path);
|
||||||
|
*(StrFind("?", session->request->path)) = NULL;
|
||||||
|
} else {
|
||||||
|
session->request->path = @slon_strnew(session, session->request->raw_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* key;
|
||||||
|
U8* value;
|
||||||
|
|
||||||
|
for (i = 1; i < raw_header_lines_count; i++) {
|
||||||
|
key = NULL;
|
||||||
|
value = NULL;
|
||||||
|
if (StrFind(": ", raw_header_lines[i])) {
|
||||||
|
value = StrFind(": ", raw_header_lines[i]) + 2;
|
||||||
|
@slon_http_rstrip_char_from_string(value, '\r');
|
||||||
|
*(StrFind(": ", raw_header_lines[i])) = NULL;
|
||||||
|
key = raw_header_lines[i];
|
||||||
|
Json.Set(session->request->headers, key, value, JSON_STRING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_free(session, raw_headers);
|
||||||
|
session->request->headers_have_been_parsed = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_authorize(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (StrLen(@slon_http_request_header(session, "authorization"))) {
|
||||||
|
U8* access_token = StrFind(" ", @slon_http_request_header(session, "authorization")) + 1;
|
||||||
|
session->auth = db->o("oauth")->o("tokens")->@(access_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_debug_print_request(SlonHttpSession* session, Bool show_headers = FALSE)
|
||||||
|
{
|
||||||
|
AdamLog("[httpd] %d => request: %s %s\n", session->s, session->request->verb, session->request->raw_path);
|
||||||
|
if (show_headers) {
|
||||||
|
U8* headers_stringified = Json.Stringify(session->request->headers);
|
||||||
|
AdamLog("[httpd] %d => headers: %s\n", session->s, headers_stringified);
|
||||||
|
Free(headers_stringified);
|
||||||
|
//@slon_free(session, headers_stringified);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_debug_print_response(SlonHttpSession* session, Bool show_headers = FALSE)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn request_json;
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "%d", session->response->status_code);
|
||||||
|
AdamLog("[httpd] %d <= response: %d %s\n", session->s, session->response->status_code, Json.Get(SLON_HTTP_STATUS_CODES, scratch_buffer));
|
||||||
|
if (show_headers) {
|
||||||
|
U8* headers_stringified = Json.Stringify(session->response->headers);
|
||||||
|
AdamLog("[httpd] %d <= headers: %s\n", session->s, headers_stringified);
|
||||||
|
Free(headers_stringified);
|
||||||
|
//@slon_free(session, headers_stringified);
|
||||||
|
}
|
||||||
|
if (session->response->data) {
|
||||||
|
AdamLog("data: %s\n", session->response->data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_http_json_string_from_form_urlencoded_string(SlonHttpSession* session, U8* form_urlencoded_string)
|
||||||
|
{
|
||||||
|
// FIXME: Implement arrays, objects per https://jsonapi.org/format/#fetching
|
||||||
|
U8* json_string = @slon_calloc(session, StrLen(form_urlencoded_string) * 2);
|
||||||
|
String.Append(json_string, "{");
|
||||||
|
U8* form_urlencoded_string_copy = @slon_strnew(session, form_urlencoded_string);
|
||||||
|
I64 raw_values_count = 0;
|
||||||
|
U8** raw_values = String.Split(form_urlencoded_string_copy, '&', &raw_values_count);
|
||||||
|
I64 i = 0;
|
||||||
|
U8* key;
|
||||||
|
U8* value;
|
||||||
|
for (i = 0; i < raw_values_count; i++) {
|
||||||
|
value = StrFind("=", raw_values[i]) + 1;
|
||||||
|
*(StrFind("=", raw_values[i])) = NULL;
|
||||||
|
key = raw_values[i];
|
||||||
|
U8* decoded_value = @slon_http_decode_urlencoded_string(session, value);
|
||||||
|
String.Append(json_string, "\"%s\":\"%s\"", key, decoded_value);
|
||||||
|
@slon_free(session, decoded_value);
|
||||||
|
if (i < raw_values_count - 1) {
|
||||||
|
String.Append(json_string, ",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String.Append(json_string, "}");
|
||||||
|
@slon_free(session, form_urlencoded_string_copy);
|
||||||
|
return json_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_http_json_string_from_multipart_form_data(SlonHttpSession* session, U8* multipart_form_data)
|
||||||
|
{
|
||||||
|
U8* json_string = @slon_calloc(session, StrLen(multipart_form_data) * 2);
|
||||||
|
String.Append(json_string, "{");
|
||||||
|
U8* multipart_form_data_copy = @slon_strnew(session, multipart_form_data);
|
||||||
|
|
||||||
|
U8* boundary = StrFind("boundary=", @slon_http_request_header(session, "content-type")) + 9;
|
||||||
|
// Strip begin double-quotes and ending CRLF, double-quotes
|
||||||
|
while (boundary[0] == '"')
|
||||||
|
boundary++;
|
||||||
|
|
||||||
|
while (boundary[StrLen(boundary) - 1] == '\"' || boundary[StrLen(boundary) - 1] == ' ' || boundary[StrLen(boundary) - 1] == '\r' || boundary[StrLen(boundary) - 1] == '\n')
|
||||||
|
boundary[StrLen(boundary) - 1] = NULL;
|
||||||
|
|
||||||
|
I64 state = SLON_MULTIPART_PARSER_CONSUME_BOUNDARY;
|
||||||
|
I64 lines_count = 0;
|
||||||
|
U8** lines = String.Split(multipart_form_data_copy, '\n', &lines_count);
|
||||||
|
|
||||||
|
U8* line;
|
||||||
|
U8* name;
|
||||||
|
U8* replace_line;
|
||||||
|
|
||||||
|
I64 i = 0;
|
||||||
|
while (i < lines_count) {
|
||||||
|
line = lines[i];
|
||||||
|
// Strip any ending CRLF
|
||||||
|
while (line[StrLen(line) - 1] == '\r' || line[StrLen(line) - 1] == '\n') {
|
||||||
|
line[StrLen(line) - 1] = NULL;
|
||||||
|
}
|
||||||
|
switch (state) {
|
||||||
|
case SLON_MULTIPART_PARSER_CONSUME_BOUNDARY:
|
||||||
|
if (StrFind(boundary, line)) {
|
||||||
|
state = SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION:
|
||||||
|
if (StrFind("ontent-", line) && StrFind("isposition:", line) && StrFind("name=", line)) {
|
||||||
|
name = StrFind("name=", line) + 5;
|
||||||
|
// Strip begin/end double-quotes
|
||||||
|
while (name[0] == '"')
|
||||||
|
name++;
|
||||||
|
while (name[StrLen(name) - 1] == '\"')
|
||||||
|
name[StrLen(name) - 1] = NULL;
|
||||||
|
String.Append(json_string, "\"%s\":\"", name);
|
||||||
|
state = SLON_MULTIPART_PARSER_CONSUME_CONTENT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SLON_MULTIPART_PARSER_CONSUME_CONTENT:
|
||||||
|
if (StrFind(boundary, line)) {
|
||||||
|
String.Append(json_string, "\"");
|
||||||
|
if (!String.EndsWith("--", line)) {
|
||||||
|
String.Append(json_string, ",");
|
||||||
|
state = SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION;
|
||||||
|
} else {
|
||||||
|
state = SLON_MULTIPART_PARSER_DONE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
replace_line = String.Replace(line, "\"", "\\\"");
|
||||||
|
String.Append(json_string, replace_line);
|
||||||
|
Free(replace_line);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
String.Append(json_string, "}");
|
||||||
|
@slon_free(session, multipart_form_data_copy);
|
||||||
|
return json_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_parse_query_string(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
U8* raw_path_copy = @slon_strnew(session, session->request->raw_path);
|
||||||
|
I64 raw_path_split_count = 0;
|
||||||
|
U8** raw_path_split = String.Split(raw_path_copy, '?', &raw_path_split_count);
|
||||||
|
if (raw_path_split_count > 1) {
|
||||||
|
U8* json_string = @slon_http_json_string_from_form_urlencoded_string(session, raw_path_split[1]);
|
||||||
|
session->request->json = Json.Parse(json_string);
|
||||||
|
@slon_free(session, json_string);
|
||||||
|
}
|
||||||
|
@slon_free(session, raw_path_copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_parse_request_as_form_urlencoded(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
U8* json_string = @slon_http_json_string_from_form_urlencoded_string(session, session->request->data);
|
||||||
|
session->request->json = Json.Parse(json_string);
|
||||||
|
@slon_free(session, json_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_parse_request_as_multipart_form_data(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
U8* json_string = @slon_http_json_string_from_multipart_form_data(session, session->request->data);
|
||||||
|
session->request->json = Json.Parse(json_string);
|
||||||
|
@slon_free(session, json_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_parse_request_as_json(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
session->request->json = Json.Parse(session->request->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_handle_delete_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* clang-format off */
|
||||||
|
|
||||||
|
#include "Endpoints/Delete/Statuses";
|
||||||
|
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
|
// FIXME: Implement this
|
||||||
|
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_handle_get_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (@slon_http_request_has_query_string(session)) {
|
||||||
|
@slon_http_parse_query_string(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
SLON_DEBUG_PRINT_REQUEST_JSON
|
||||||
|
|
||||||
|
/* clang-format off */
|
||||||
|
|
||||||
|
#include "Endpoints/Get/Accounts";
|
||||||
|
#include "Endpoints/Get/ActivityPub";
|
||||||
|
#include "Endpoints/Get/Blocks";
|
||||||
|
#include "Endpoints/Get/Bookmarks";
|
||||||
|
#include "Endpoints/Get/Conversations";
|
||||||
|
#include "Endpoints/Get/CustomEmojis";
|
||||||
|
#include "Endpoints/Get/Favourites";
|
||||||
|
#include "Endpoints/Get/Filters";
|
||||||
|
#include "Endpoints/Get/FollowRequests";
|
||||||
|
#include "Endpoints/Get/FollowedTags";
|
||||||
|
#include "Endpoints/Get/Instance";
|
||||||
|
#include "Endpoints/Get/Notifications";
|
||||||
|
#include "Endpoints/Get/OAuth";
|
||||||
|
#include "Endpoints/Get/Suggestions";
|
||||||
|
#include "Endpoints/Get/Timelines";
|
||||||
|
#include "Endpoints/Get/Web";
|
||||||
|
#include "Endpoints/Get/WellKnown";
|
||||||
|
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_handle_patch_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (StrFind("json", @slon_http_request_header(session, "content-type")) > 0) {
|
||||||
|
@slon_http_parse_request_as_json(session);
|
||||||
|
}
|
||||||
|
if (String.BeginsWith("application/x-www-form-urlencoded", @slon_http_request_header(session, "content-type"))) {
|
||||||
|
@slon_http_parse_request_as_form_urlencoded(session);
|
||||||
|
}
|
||||||
|
if (String.BeginsWith("multipart/form-data", @slon_http_request_header(session, "content-type"))) {
|
||||||
|
@slon_http_parse_request_as_multipart_form_data(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
SLON_DEBUG_PRINT_REQUEST_JSON
|
||||||
|
|
||||||
|
/* clang-format off */
|
||||||
|
|
||||||
|
#include "Endpoints/Patch/Accounts";
|
||||||
|
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_handle_post_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (StrFind("json", @slon_http_request_header(session, "content-type")) > 0) {
|
||||||
|
@slon_http_parse_request_as_json(session);
|
||||||
|
}
|
||||||
|
if (String.BeginsWith("application/x-www-form-urlencoded", @slon_http_request_header(session, "content-type"))) {
|
||||||
|
@slon_http_parse_request_as_form_urlencoded(session);
|
||||||
|
}
|
||||||
|
if (String.BeginsWith("multipart/form-data", @slon_http_request_header(session, "content-type"))) {
|
||||||
|
@slon_http_parse_request_as_multipart_form_data(session);
|
||||||
|
}
|
||||||
|
// Workaround for IceCubesApp: https://github.com/Dimillian/IceCubesApp/issues/2235
|
||||||
|
if (!StrLen(@slon_http_request_header(session, "content-type")) && @slon_http_request_has_query_string(session)) {
|
||||||
|
@slon_http_parse_query_string(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
SLON_DEBUG_PRINT_REQUEST_JSON
|
||||||
|
|
||||||
|
/* clang-format off */
|
||||||
|
|
||||||
|
#include "Endpoints/Post/ActivityPub";
|
||||||
|
#include "Endpoints/Post/Apps";
|
||||||
|
#include "Endpoints/Post/OAuth";
|
||||||
|
#include "Endpoints/Post/Statuses";
|
||||||
|
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_handle_request(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
|
||||||
|
// .purge_expired_idempotency_keys()
|
||||||
|
@slon_http_authorize(session);
|
||||||
|
switch (@slon_http_request_verb(session)) {
|
||||||
|
case SLON_HTTP_VERB_DELETE:
|
||||||
|
@slon_http_handle_delete_request(session);
|
||||||
|
break;
|
||||||
|
case SLON_HTTP_VERB_GET:
|
||||||
|
@slon_http_handle_get_request(session);
|
||||||
|
break;
|
||||||
|
case SLON_HTTP_VERB_OPTIONS:
|
||||||
|
@slon_http_set_status_code(session, 200);
|
||||||
|
break;
|
||||||
|
case SLON_HTTP_VERB_PATCH:
|
||||||
|
@slon_http_handle_patch_request(session);
|
||||||
|
break;
|
||||||
|
case SLON_HTTP_VERB_POST:
|
||||||
|
@slon_http_handle_post_request(session);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
@slon_http_set_status_code(session, 405);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_task(TcpSocket* s)
|
||||||
|
{
|
||||||
|
// Bail if we can't acquire socket for some reason
|
||||||
|
if (!@tcp_socket_accept(s))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Init session
|
||||||
|
SlonHttpSession* session = @slon_http_init_session(s);
|
||||||
|
|
||||||
|
// Parse headers if they are available
|
||||||
|
while (!@slon_http_request_headers_have_been_parsed(session)) {
|
||||||
|
@slon_http_receive(session);
|
||||||
|
|
||||||
|
// Handle malformed requests (anything less than "GET / HTTP/1.0\r\n\r\n" is probably a bad request)
|
||||||
|
if (session->request->buffer->size < 18) {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
goto slon_http_task_send_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_try_parse_request_headers(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
//@slon_http_debug_print_request(session, FALSE);
|
||||||
|
|
||||||
|
// If we have a content-length header, consume until we receive all the data, then set request->data pointer and size
|
||||||
|
if (StrLen(@slon_http_request_header(session, "content-length"))) {
|
||||||
|
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
|
||||||
|
while (session->request->buffer->data + session->request->buffer->size - session->request->data < content_length)
|
||||||
|
@slon_http_receive(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_handle_request(session);
|
||||||
|
|
||||||
|
slon_http_task_send_response:
|
||||||
|
//@slon_http_debug_print_response(session, FALSE);
|
||||||
|
|
||||||
|
@slon_http_send_response(session);
|
||||||
|
|
||||||
|
@slon_http_free_session(session);
|
||||||
|
|
||||||
|
AdamLog("\n");
|
||||||
|
s->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Adam("U0 @spawn_slon_http_task(TcpSocket *s){Spawn(%d, s, \"SlonHttpTask\");};\n", &@slon_http_task);
|
||||||
|
@tcp_socket_bind(80, "@spawn_slon_http_task");
|
42
Slon/MakeSlon.HC
Normal file
42
Slon/MakeSlon.HC
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/* clang-format off */
|
||||||
|
|
||||||
|
DocMax(Fs);
|
||||||
|
WinMax(Fs);
|
||||||
|
|
||||||
|
#include "Modules/Log";
|
||||||
|
#include "Modules/Db";
|
||||||
|
#include "Modules/Http";
|
||||||
|
#include "Modules/Api";
|
||||||
|
|
||||||
|
#include "Api/V1/Accounts";
|
||||||
|
#include "Api/V1/Apps";
|
||||||
|
#include "Api/V1/Blocks";
|
||||||
|
#include "Api/V1/Bookmarks";
|
||||||
|
#include "Api/V1/Conversations";
|
||||||
|
#include "Api/V1/CustomEmojis";
|
||||||
|
#include "Api/V1/Favourites";
|
||||||
|
#include "Api/V1/Filters";
|
||||||
|
#include "Api/V1/FollowRequests";
|
||||||
|
#include "Api/V1/FollowedTags";
|
||||||
|
#include "Api/V1/Notifications";
|
||||||
|
#include "Api/V1/Statuses";
|
||||||
|
#include "Api/V1/Timelines";
|
||||||
|
|
||||||
|
#include "Api/V2/Filters";
|
||||||
|
#include "Api/V2/Instance";
|
||||||
|
#include "Api/V2/Suggestions";
|
||||||
|
|
||||||
|
#include "Modules/ActivityPub";
|
||||||
|
#include "Modules/Meta";
|
||||||
|
#include "Modules/OAuth";
|
||||||
|
#include "Modules/Web";
|
||||||
|
#include "Modules/Webfinger";
|
||||||
|
|
||||||
|
#include "Http/Server";
|
||||||
|
#include "Http/LocalServer";
|
||||||
|
#include "Http/AdminServer";
|
||||||
|
|
||||||
|
@slon_log(0, "slon is up and running");
|
||||||
|
@slon_log(0, "instance on port 80, fs on port 8000, admin on port 9000");
|
||||||
|
|
||||||
|
WinToTop(adam_task);
|
795
Slon/Modules/ActivityPub.HC
Normal file
795
Slon/Modules/ActivityPub.HC
Normal file
|
@ -0,0 +1,795 @@
|
||||||
|
U8* @slon_activitypub_strip_double_quotes(U8* str)
|
||||||
|
{
|
||||||
|
while (str[0] == '"')
|
||||||
|
str++;
|
||||||
|
while (str[StrLen(str) - 1] == '"')
|
||||||
|
str[StrLen(str) - 1] = NULL;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @slon_activitypub_http_signature_is_valid(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer;
|
||||||
|
|
||||||
|
// 1. Check that we have a signature and digest
|
||||||
|
if (!StrLen(@slon_http_request_header(session, "signature")) || !StrLen(@slon_http_request_header(session, "digest"))) {
|
||||||
|
AdamLog("[verify_signature] no signature or digest header present\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check that digest 1) is SHA-256 and 2) matches content
|
||||||
|
U8* request_digest = @slon_http_request_header(session, "digest");
|
||||||
|
if (!(String.BeginsWith("SHA-256", request_digest) || String.BeginsWith("sha-256", request_digest))) {
|
||||||
|
AdamLog("[verify_signature] digest is not SHA-256\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
request_digest = StrFind("=", request_digest) + 1;
|
||||||
|
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
|
||||||
|
if (!content_length) {
|
||||||
|
AdamLog("[verify_signature] content-length is 0\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 content_hash[512];
|
||||||
|
calc_sha_256(content_hash, session->request->data, content_length);
|
||||||
|
U8* computed_digest = @base64_encode(content_hash, 32);
|
||||||
|
|
||||||
|
if (StrICmp(computed_digest, request_digest)) {
|
||||||
|
AdamLog("[verify_signature] digest header and computed digest do not match\n");
|
||||||
|
Free(computed_digest);
|
||||||
|
return FALSE;
|
||||||
|
} else {
|
||||||
|
Free(computed_digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse values from Signature header
|
||||||
|
U8* signature_header = @slon_http_request_header(session, "signature");
|
||||||
|
I64 signature_fragment_count = 0;
|
||||||
|
U8** signature_fragments = String.Split(signature_header, ',', &signature_fragment_count);
|
||||||
|
|
||||||
|
U8* keyId = NULL;
|
||||||
|
U8* algorithm = NULL;
|
||||||
|
U8* headers = NULL;
|
||||||
|
U8* signature = NULL;
|
||||||
|
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < signature_fragment_count; i++) {
|
||||||
|
if (String.BeginsWith("keyId=", signature_fragments[i])) {
|
||||||
|
keyId = signature_fragments[i] + 6;
|
||||||
|
keyId = @slon_activitypub_strip_double_quotes(keyId);
|
||||||
|
}
|
||||||
|
if (String.BeginsWith("algorithm=", signature_fragments[i])) {
|
||||||
|
algorithm = signature_fragments[i] + 10;
|
||||||
|
algorithm = @slon_activitypub_strip_double_quotes(algorithm);
|
||||||
|
}
|
||||||
|
if (String.BeginsWith("headers=", signature_fragments[i])) {
|
||||||
|
headers = signature_fragments[i] + 8;
|
||||||
|
headers = @slon_activitypub_strip_double_quotes(headers);
|
||||||
|
}
|
||||||
|
if (String.BeginsWith("signature=", signature_fragments[i])) {
|
||||||
|
signature = signature_fragments[i] + 10;
|
||||||
|
signature = @slon_activitypub_strip_double_quotes(signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Confirm actor matches keyId
|
||||||
|
if (!request_json->@("actor")) {
|
||||||
|
AdamLog("[verify_signature] actor is not present in request\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (!String.BeginsWith(request_json->@("actor"), keyId)) {
|
||||||
|
AdamLog("[verify_signature] actor does not match keyId\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if public key is cached for keyId, if not, fetch it
|
||||||
|
if (!db->o("public_keys")->@(keyId)) {
|
||||||
|
|
||||||
|
@slon_log(LOG_HTTPD, "Actor's public key is not cached, attempting to fetch");
|
||||||
|
|
||||||
|
HttpUrl* url = @http_parse_url(request_json->@("actor"));
|
||||||
|
if (!url) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, malformed url or unspecified error");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
JsonObject* http_headers = Json.CreateObject();
|
||||||
|
http_headers->set("accept", "application/json", JSON_STRING);
|
||||||
|
@http_response* resp = Http.Get(url, fetch_buffer, NULL, http_headers);
|
||||||
|
|
||||||
|
if (!resp) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, invalid response from remote server");
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (resp->state != HTTP_STATE_DONE) {
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resp->body.length) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, empty response from remote server");
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(fetch_buffer);
|
||||||
|
|
||||||
|
JsonObject* user_object = Json.Parse(resp->body.data);
|
||||||
|
if (!user_object) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, user object not present in response from remote server");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject* pubkey_object = user_object->@("publicKey");
|
||||||
|
if (!pubkey_object) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, publicKey object not present in user object");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pubkey_object->@("id")) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, id not present in publicKey object");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (!pubkey_object->@("owner")) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, owner not present in publicKey object");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (!pubkey_object->@("publicKeyPem")) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, publicKeyPem not present in publicKey object");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrICmp(pubkey_object->@("id"), keyId)) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, keyId does not match id present in publicKey object");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (StrICmp(pubkey_object->@("owner"), request_json->@("actor"))) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, actor does not match owner present in publicKey object");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* pem_string = pubkey_object->@("publicKeyPem");
|
||||||
|
|
||||||
|
// Convert Base64 PEM to single line
|
||||||
|
U8* pem_single_line = @slon_calloc(session, StrLen(pem_string));
|
||||||
|
I64 pem_lines_count = 0;
|
||||||
|
U8** pem_lines = String.Split(pem_string, '\n', &pem_lines_count);
|
||||||
|
i = 0;
|
||||||
|
while (i < pem_lines_count) {
|
||||||
|
if (pem_lines[i] && StrLen(pem_lines[i]) > 0) {
|
||||||
|
if (!StrFind("KEY", pem_lines[i])) {
|
||||||
|
StrCpy(pem_single_line + StrLen(pem_single_line), pem_lines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode PEM to DER
|
||||||
|
I64 der_buf_length = 0;
|
||||||
|
U8* der_buf = @base64_decode(pem_single_line, &der_buf_length);
|
||||||
|
|
||||||
|
// Cache the public key
|
||||||
|
JsonObject* cached_key = Json.CreateObject();
|
||||||
|
cached_key->set("key", der_buf, JSON_NUMBER);
|
||||||
|
cached_key->set("length", der_buf_length, JSON_NUMBER);
|
||||||
|
db->o("public_keys")->set(keyId, cached_key, JSON_OBJECT);
|
||||||
|
|
||||||
|
@slon_free(session, pem_single_line);
|
||||||
|
|
||||||
|
Json.Delete(user_object);
|
||||||
|
Json.Delete(http_headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate our signature string allocation
|
||||||
|
I64 sig_string_alloc_length = 0;
|
||||||
|
|
||||||
|
I64 headers_split_count = 0;
|
||||||
|
U8** headers_split = String.Split(headers, ' ', &headers_split_count);
|
||||||
|
i = 0;
|
||||||
|
while (i < headers_split_count) {
|
||||||
|
sig_string_alloc_length += StrLen(@slon_http_request_header(session, headers_split[i]));
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
sig_string_alloc_length += StrLen(@slon_http_request_verb(session));
|
||||||
|
sig_string_alloc_length += StrLen(@slon_http_request_path(session));
|
||||||
|
sig_string_alloc_length *= 2;
|
||||||
|
|
||||||
|
// Construct our signature string
|
||||||
|
U8* sig_string = @slon_calloc(session, sig_string_alloc_length);
|
||||||
|
i = 0;
|
||||||
|
while (i < headers_split_count) {
|
||||||
|
if (StrLen(headers_split[i]) && headers_split[i][0] >= 'A' && headers_split[i][0] <= 'Z') {
|
||||||
|
headers_split[i][0] += 'a' - headers_split[i][0];
|
||||||
|
}
|
||||||
|
if (!StrCmp("(request-target)", headers_split[i])) {
|
||||||
|
String.Append(sig_string, "(request-target): %s %s", "post", @slon_http_request_path(session));
|
||||||
|
} else {
|
||||||
|
String.Append(sig_string, "%s: %s", headers_split[i], @slon_http_request_header(session, headers_split[i]));
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
if (i < headers_split_count) {
|
||||||
|
String.Append(sig_string, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 decode request's signature
|
||||||
|
I64 verify_sig_buf_length = 0;
|
||||||
|
U8* verify_sig_buf = @base64_decode(signature, &verify_sig_buf_length);
|
||||||
|
|
||||||
|
// Hash our constructed signature string
|
||||||
|
U8 sig_string_hash[32];
|
||||||
|
calc_sha_256(sig_string_hash, sig_string, StrLen(sig_string));
|
||||||
|
|
||||||
|
// Import RSA key
|
||||||
|
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
|
||||||
|
I64 res = @rsa_import(db->o("public_keys")->o(keyId)->@("key"), db->o("public_keys")->o(keyId)->@("length"), rsa_key);
|
||||||
|
if (res != 0) { // CRYPT_OK = 0
|
||||||
|
@slon_log(LOG_HTTPD, "Received error from @rsa_import: %d", res);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify signature
|
||||||
|
I32 stat = 0;
|
||||||
|
res = @rsa_verify_signature(verify_sig_buf, verify_sig_buf_length, sig_string_hash, 32, &stat, rsa_key);
|
||||||
|
if (res != 0) { // CRYPT_OK = 0
|
||||||
|
@slon_log(LOG_HTTPD, "Received error from @rsa_verify_signature: %d", res);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(rsa_key);
|
||||||
|
Free(verify_sig_buf);
|
||||||
|
@slon_free(session, sig_string);
|
||||||
|
|
||||||
|
return stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_users_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
U8* path = @slon_strnew(session, @slon_http_request_path(session));
|
||||||
|
I64 path_segments_count = 0;
|
||||||
|
U8** path_segments = String.Split(path, '/', &path_segments_count);
|
||||||
|
if (path_segments_count == 3) {
|
||||||
|
JsonObject* actor = db->o("actors")->@(path_segments[1]);
|
||||||
|
if (actor) {
|
||||||
|
@slon_http_send_ap_json(session, actor);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
}
|
||||||
|
slon_activitypub_users_get_return:
|
||||||
|
@slon_free(session, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_async_accept_request(JsonObject* o)
|
||||||
|
{
|
||||||
|
JsonObject* request = o->o("request");
|
||||||
|
if (!StrICmp("accept", request->@("type")) || !StrICmp("reject", request->@("type"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sleep(2000);
|
||||||
|
|
||||||
|
U8 scratch_buffer[2048];
|
||||||
|
|
||||||
|
U8* this_actor = db->o("actors")->o(o->@("user"))->@("id");
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "%s/accept/%d", this_actor, Now);
|
||||||
|
JsonObject* accept_object = Json.CreateObject();
|
||||||
|
accept_object->set("@context", request->@("@context"), JSON_STRING);
|
||||||
|
accept_object->set("id", scratch_buffer, JSON_STRING);
|
||||||
|
accept_object->set("type", "Accept", JSON_STRING);
|
||||||
|
accept_object->set("actor", this_actor, JSON_STRING);
|
||||||
|
accept_object->set("object", request, JSON_OBJECT);
|
||||||
|
|
||||||
|
U8* accept_object_s = Json.Stringify(accept_object);
|
||||||
|
|
||||||
|
U8 content_hash[512];
|
||||||
|
calc_sha_256(content_hash, accept_object_s, StrLen(accept_object_s));
|
||||||
|
U8* computed_digest = @base64_encode(content_hash, 32);
|
||||||
|
|
||||||
|
JsonObject* http_headers = Json.CreateObject();
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "%s/inbox", request->@("actor"));
|
||||||
|
HttpUrl* url = @http_parse_url(scratch_buffer);
|
||||||
|
|
||||||
|
CDateStruct ds;
|
||||||
|
Date2Struct(&ds, Now + 1043910000);
|
||||||
|
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
|
||||||
|
ds.year, ds.hour, ds.min, ds.sec);
|
||||||
|
http_headers->set("Date", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
|
||||||
|
http_headers->set("Digest", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
|
||||||
|
String.Append(scratch_buffer, "host: %s\n", url->host);
|
||||||
|
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
|
||||||
|
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
|
||||||
|
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
|
||||||
|
|
||||||
|
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
|
||||||
|
|
||||||
|
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
|
||||||
|
|
||||||
|
U8* user = StrFind("/users/", this_actor) + 7;
|
||||||
|
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
|
||||||
|
if (!private_key_binary) {
|
||||||
|
I64 private_key_binary_size = 0;
|
||||||
|
private_key_binary = Json.CreateObject();
|
||||||
|
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
|
||||||
|
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
|
||||||
|
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 res;
|
||||||
|
|
||||||
|
// Import RSA key
|
||||||
|
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
|
||||||
|
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
|
||||||
|
AdamLog("@rsa_import: res: %d\n", res);
|
||||||
|
|
||||||
|
U8 sig[256];
|
||||||
|
U64 siglen = 256;
|
||||||
|
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
|
||||||
|
AdamLog("@rsa_create_signature: res: %d\n", res);
|
||||||
|
U8* computed_sig = @base64_encode(sig, 256);
|
||||||
|
|
||||||
|
StrCpy(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
|
||||||
|
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
|
||||||
|
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
|
||||||
|
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
|
||||||
|
http_headers->set("Signature", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
@http_response* resp = Http.Post(url, fetch_buffer, accept_object_s, http_headers);
|
||||||
|
|
||||||
|
if (!resp) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (resp->state != HTTP_STATE_DONE) {
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdamLog("code: %d\n", resp->status.code);
|
||||||
|
|
||||||
|
Free(fetch_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_follow_request()
|
||||||
|
{
|
||||||
|
|
||||||
|
U8 scratch_buffer[2048];
|
||||||
|
|
||||||
|
U8* this_actor = "https://error.checksum.fail/users/alec";
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "%s/follow/%d", this_actor, Now);
|
||||||
|
JsonObject* follow_object = Json.CreateObject();
|
||||||
|
follow_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING);
|
||||||
|
follow_object->set("id", scratch_buffer, JSON_STRING);
|
||||||
|
follow_object->set("type", "Follow", JSON_STRING);
|
||||||
|
follow_object->set("actor", this_actor, JSON_STRING);
|
||||||
|
follow_object->set("object", "https://techhub.social/users/ryeucrvtexw3", JSON_STRING);
|
||||||
|
|
||||||
|
U8* follow_object_s = Json.Stringify(follow_object);
|
||||||
|
|
||||||
|
U8 content_hash[512];
|
||||||
|
calc_sha_256(content_hash, follow_object_s, StrLen(follow_object_s));
|
||||||
|
U8* computed_digest = @base64_encode(content_hash, 32);
|
||||||
|
|
||||||
|
JsonObject* http_headers = Json.CreateObject();
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "%s/inbox", "https://techhub.social/users/ryeucrvtexw3");
|
||||||
|
HttpUrl* url = @http_parse_url(scratch_buffer);
|
||||||
|
|
||||||
|
CDateStruct ds;
|
||||||
|
Date2Struct(&ds, Now + 1043910000);
|
||||||
|
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
|
||||||
|
ds.year, ds.hour, ds.min, ds.sec);
|
||||||
|
http_headers->set("Date", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
|
||||||
|
http_headers->set("Digest", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
|
||||||
|
String.Append(scratch_buffer, "host: %s\n", url->host);
|
||||||
|
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
|
||||||
|
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
|
||||||
|
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
|
||||||
|
|
||||||
|
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
|
||||||
|
|
||||||
|
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
|
||||||
|
|
||||||
|
U8* user = StrFind("/users/", this_actor) + 7;
|
||||||
|
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
|
||||||
|
if (!private_key_binary) {
|
||||||
|
I64 private_key_binary_size = 0;
|
||||||
|
private_key_binary = Json.CreateObject();
|
||||||
|
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
|
||||||
|
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
|
||||||
|
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 res;
|
||||||
|
|
||||||
|
// Import RSA key
|
||||||
|
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
|
||||||
|
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
|
||||||
|
AdamLog("@rsa_import: res: %d\n", res);
|
||||||
|
|
||||||
|
U8 sig[256];
|
||||||
|
U64 siglen = 256;
|
||||||
|
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
|
||||||
|
AdamLog("@rsa_create_signature: res: %d\n", res);
|
||||||
|
U8* computed_sig = @base64_encode(sig, 256);
|
||||||
|
|
||||||
|
StrCpy(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
|
||||||
|
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
|
||||||
|
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
|
||||||
|
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
|
||||||
|
http_headers->set("Signature", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
@http_response* resp = Http.Post(url, fetch_buffer, follow_object_s, http_headers);
|
||||||
|
|
||||||
|
if (!resp) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (resp->state != HTTP_STATE_DONE) {
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdamLog("code: %d\n", resp->status.code);
|
||||||
|
|
||||||
|
Free(fetch_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_async_create_status(JsonObject* status)
|
||||||
|
{
|
||||||
|
Sleep(2000);
|
||||||
|
U8 scratch_buffer[2048];
|
||||||
|
|
||||||
|
U8* dest = "https://techhub.social/users/ryeucrvtexw3/inbox";
|
||||||
|
U8* this_actor = StrNew(status->@("uri"), adam_task);
|
||||||
|
StrFind("/statuses/", this_actor)[0] = NULL;
|
||||||
|
|
||||||
|
JsonObject* create_object = Json.CreateObject();
|
||||||
|
|
||||||
|
create_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "%s/activity", status->@("uri"));
|
||||||
|
create_object->set("id", scratch_buffer, JSON_STRING);
|
||||||
|
create_object->set("type", "Create", JSON_STRING);
|
||||||
|
create_object->set("actor", this_actor, JSON_STRING);
|
||||||
|
create_object->set("published", status->@("created_at"), JSON_STRING);
|
||||||
|
create_object->set("to", Json.Parse("[\"https://www.w3.org/ns/activitystreams#Public\"]"), JSON_ARRAY);
|
||||||
|
JsonArray* cc = Json.CreateArray();
|
||||||
|
StrPrint(scratch_buffer, "%s/followers", this_actor);
|
||||||
|
cc->append(Json.CreateItem(scratch_buffer, JSON_STRING));
|
||||||
|
create_object->set("cc", cc, JSON_ARRAY);
|
||||||
|
|
||||||
|
JsonObject* note_object = Json.CreateObject();
|
||||||
|
note_object->set("id", status->@("uri"), JSON_STRING);
|
||||||
|
note_object->set("type", "Note", JSON_STRING);
|
||||||
|
note_object->set("summary", NULL, JSON_NULL);
|
||||||
|
note_object->set("inReplyTo", NULL, JSON_NULL);
|
||||||
|
note_object->set("published", status->@("created_at"), JSON_STRING);
|
||||||
|
note_object->set("attributedTo", this_actor, JSON_STRING);
|
||||||
|
note_object->set("to", Json.Parse("[\"https://www.w3.org/ns/activitystreams#Public\"]"), JSON_ARRAY);
|
||||||
|
note_object->set("cc", cc, JSON_ARRAY);
|
||||||
|
note_object->set("sensitive", status->@("sensitive"), JSON_BOOLEAN);
|
||||||
|
note_object->set("atomUri", status->@("uri"), JSON_STRING);
|
||||||
|
note_object->set("inReplyToAtomUri", NULL, JSON_NULL);
|
||||||
|
note_object->set("content", status->@("content"), JSON_STRING);
|
||||||
|
JsonObject* content_map = Json.CreateObject();
|
||||||
|
content_map->set("en", status->@("content"), JSON_STRING);
|
||||||
|
note_object->set("contentMap", content_map, JSON_OBJECT);
|
||||||
|
note_object->set("attachment", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
note_object->set("tag", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
|
||||||
|
note_object->set("replies", SLON_EMPTY_JSON_OBJECT, JSON_OBJECT);
|
||||||
|
note_object->set("likes", SLON_EMPTY_JSON_OBJECT, JSON_OBJECT);
|
||||||
|
note_object->set("shares", SLON_EMPTY_JSON_OBJECT, JSON_OBJECT);
|
||||||
|
|
||||||
|
create_object->set("object", note_object, JSON_OBJECT);
|
||||||
|
U8* create_object_s = Json.Stringify(create_object);
|
||||||
|
|
||||||
|
U8 content_hash[512];
|
||||||
|
calc_sha_256(content_hash, create_object_s, StrLen(create_object_s));
|
||||||
|
U8* computed_digest = @base64_encode(content_hash, 32);
|
||||||
|
|
||||||
|
JsonObject* http_headers = Json.CreateObject();
|
||||||
|
|
||||||
|
HttpUrl* url = @http_parse_url(dest);
|
||||||
|
|
||||||
|
CDateStruct ds;
|
||||||
|
Date2Struct(&ds, Now + 1043910000);
|
||||||
|
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
|
||||||
|
ds.year, ds.hour, ds.min, ds.sec);
|
||||||
|
http_headers->set("Date", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
|
||||||
|
http_headers->set("Digest", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
|
||||||
|
String.Append(scratch_buffer, "host: %s\n", url->host);
|
||||||
|
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
|
||||||
|
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
|
||||||
|
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
|
||||||
|
|
||||||
|
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
|
||||||
|
|
||||||
|
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
|
||||||
|
|
||||||
|
U8* user = StrFind("/users/", this_actor) + 7;
|
||||||
|
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
|
||||||
|
if (!private_key_binary) {
|
||||||
|
I64 private_key_binary_size = 0;
|
||||||
|
private_key_binary = Json.CreateObject();
|
||||||
|
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
|
||||||
|
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
|
||||||
|
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 res;
|
||||||
|
|
||||||
|
// Import RSA key
|
||||||
|
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
|
||||||
|
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
|
||||||
|
AdamLog("@rsa_import: res: %d\n", res);
|
||||||
|
|
||||||
|
U8 sig[256];
|
||||||
|
U64 siglen = 256;
|
||||||
|
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
|
||||||
|
AdamLog("@rsa_create_signature: res: %d\n", res);
|
||||||
|
U8* computed_sig = @base64_encode(sig, 256);
|
||||||
|
|
||||||
|
StrCpy(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
|
||||||
|
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
|
||||||
|
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
|
||||||
|
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
|
||||||
|
http_headers->set("Signature", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
@http_response* resp = Http.Post(url, fetch_buffer, create_object_s, http_headers);
|
||||||
|
|
||||||
|
if (!resp) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (resp->state != HTTP_STATE_DONE) {
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdamLog("code: %d\n", resp->status.code);
|
||||||
|
|
||||||
|
Free(fetch_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_async_delete_status(JsonObject* status)
|
||||||
|
{
|
||||||
|
Sleep(2000);
|
||||||
|
U8 scratch_buffer[2048];
|
||||||
|
|
||||||
|
U8* dest = "https://techhub.social/users/ryeucrvtexw3/inbox";
|
||||||
|
U8* this_actor = StrNew(status->@("uri"), adam_task);
|
||||||
|
StrFind("/statuses/", this_actor)[0] = NULL;
|
||||||
|
|
||||||
|
JsonObject* delete_object = Json.CreateObject();
|
||||||
|
|
||||||
|
delete_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "%s#delete", status->@("uri"));
|
||||||
|
delete_object->set("id", scratch_buffer, JSON_STRING);
|
||||||
|
delete_object->set("type", "Delete", JSON_STRING);
|
||||||
|
delete_object->set("actor", this_actor, JSON_STRING);
|
||||||
|
delete_object->set("to", Json.Parse("[\"https://www.w3.org/ns/activitystreams#Public\"]"), JSON_ARRAY);
|
||||||
|
|
||||||
|
JsonObject* ts_object = Json.CreateObject();
|
||||||
|
ts_object->set("id", status->@("uri"), JSON_STRING);
|
||||||
|
ts_object->set("type", "Tombstone", JSON_STRING);
|
||||||
|
ts_object->set("atomUri", status->@("uri"), JSON_STRING);
|
||||||
|
|
||||||
|
delete_object->set("object", ts_object, JSON_OBJECT);
|
||||||
|
U8* delete_object_s = Json.Stringify(delete_object);
|
||||||
|
|
||||||
|
U8 content_hash[512];
|
||||||
|
calc_sha_256(content_hash, delete_object_s, StrLen(delete_object_s));
|
||||||
|
U8* computed_digest = @base64_encode(content_hash, 32);
|
||||||
|
|
||||||
|
JsonObject* http_headers = Json.CreateObject();
|
||||||
|
|
||||||
|
HttpUrl* url = @http_parse_url(dest);
|
||||||
|
|
||||||
|
CDateStruct ds;
|
||||||
|
Date2Struct(&ds, Now + 1043910000);
|
||||||
|
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
|
||||||
|
ds.year, ds.hour, ds.min, ds.sec);
|
||||||
|
http_headers->set("Date", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
|
||||||
|
http_headers->set("Digest", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
|
||||||
|
String.Append(scratch_buffer, "host: %s\n", url->host);
|
||||||
|
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
|
||||||
|
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
|
||||||
|
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
|
||||||
|
|
||||||
|
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
|
||||||
|
|
||||||
|
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
|
||||||
|
|
||||||
|
U8* user = StrFind("/users/", this_actor) + 7;
|
||||||
|
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
|
||||||
|
if (!private_key_binary) {
|
||||||
|
I64 private_key_binary_size = 0;
|
||||||
|
private_key_binary = Json.CreateObject();
|
||||||
|
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
|
||||||
|
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
|
||||||
|
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 res;
|
||||||
|
|
||||||
|
// Import RSA key
|
||||||
|
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
|
||||||
|
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
|
||||||
|
AdamLog("@rsa_import: res: %d\n", res);
|
||||||
|
|
||||||
|
U8 sig[256];
|
||||||
|
U64 siglen = 256;
|
||||||
|
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
|
||||||
|
AdamLog("@rsa_create_signature: res: %d\n", res);
|
||||||
|
U8* computed_sig = @base64_encode(sig, 256);
|
||||||
|
|
||||||
|
StrCpy(scratch_buffer, "");
|
||||||
|
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
|
||||||
|
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
|
||||||
|
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
|
||||||
|
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
|
||||||
|
http_headers->set("Signature", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
@http_response* resp = Http.Post(url, fetch_buffer, delete_object_s, http_headers);
|
||||||
|
|
||||||
|
if (!resp) {
|
||||||
|
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (resp->state != HTTP_STATE_DONE) {
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdamLog("code: %d\n", resp->status.code);
|
||||||
|
|
||||||
|
Free(fetch_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_create_status_fedi(JsonObject* status)
|
||||||
|
{
|
||||||
|
Spawn(&@slon_activitypub_async_create_status, status, "SlonAsyncCreateTask");
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_delete_status_fedi(JsonObject* status)
|
||||||
|
{
|
||||||
|
Spawn(&@slon_activitypub_async_delete_status, status, "SlonAsyncDeleteTask");
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_api_status_create_fedi = &@slon_activitypub_create_status_fedi;
|
||||||
|
@slon_api_status_delete_fedi = &@slon_activitypub_delete_status_fedi;
|
||||||
|
|
||||||
|
U0 @slon_activitypub_users_inbox(SlonHttpSession* session, U8* user)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer;
|
||||||
|
|
||||||
|
I64 i;
|
||||||
|
JsonObject* account = @slon_api_account_by_username(user);
|
||||||
|
|
||||||
|
Bool already_following = FALSE;
|
||||||
|
JsonArray* followers = NULL;
|
||||||
|
JsonArray* statuses = NULL;
|
||||||
|
|
||||||
|
JsonObject* status = NULL;
|
||||||
|
|
||||||
|
if (!StrICmp("follow", request_json->@("type"))) {
|
||||||
|
if (!db->o("followers")->@(user)) {
|
||||||
|
db->o("followers")->set(user, Json.CreateArray(), JSON_ARRAY);
|
||||||
|
}
|
||||||
|
followers = db->o("followers")->a(user);
|
||||||
|
for (i = 0; i < followers->length; i++) {
|
||||||
|
if (!StrCmp(request_json->@("actor"), followers->@(i))) {
|
||||||
|
already_following = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!already_following) {
|
||||||
|
followers->append(Json.CreateItem(request_json->@("actor"), JSON_STRING));
|
||||||
|
account->set("followers_count", account->@("followers_count") + 1);
|
||||||
|
@slon_db_save_to_disk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrICmp("like", request_json->@("type"))) {
|
||||||
|
U8* status_id = StrFind("/", StrFind("/statuses/", request_json->@("object")) + 1) + 1;
|
||||||
|
statuses = db->o("statuses")->a(account->@("id"));
|
||||||
|
for (i = 0; i < statuses->length; i++) {
|
||||||
|
status = statuses->@(i);
|
||||||
|
if (!StrICmp(status_id, status->@("id"))) {
|
||||||
|
status->set("favourites_count", status->@("favourites_count") + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@slon_db_save_statuses_to_disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject* o = Json.CreateObject();
|
||||||
|
o->set("user", user, JSON_STRING);
|
||||||
|
o->set("request", Json.Clone(request_json), JSON_OBJECT);
|
||||||
|
Spawn(&@slon_activitypub_async_accept_request, o, "SlonAsyncAcceptTask");
|
||||||
|
|
||||||
|
@slon_http_set_status_code(session, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_activitypub_users_post(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (!@slon_activitypub_http_signature_is_valid(session)) {
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* path = @slon_strnew(session, @slon_http_request_path(session));
|
||||||
|
I64 path_segments_count = 0;
|
||||||
|
U8** path_segments = String.Split(path, '/', &path_segments_count);
|
||||||
|
|
||||||
|
if (path_segments_count < 3) {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
goto slon_activitypub_users_post_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* user = path_segments[1];
|
||||||
|
JsonObject* actor = db->o("actors")->@(user);
|
||||||
|
if (!actor) {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
goto slon_activitypub_users_post_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* method = path_segments[2];
|
||||||
|
if (!StrICmp("inbox", method)) {
|
||||||
|
@slon_activitypub_users_inbox(session, user);
|
||||||
|
goto slon_activitypub_users_post_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
|
||||||
|
slon_activitypub_users_post_return:
|
||||||
|
@slon_free(session, path);
|
||||||
|
}
|
86
Slon/Modules/Api.HC
Normal file
86
Slon/Modules/Api.HC
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#define SLON_API_LOCAL_TIME_OFFSET 3550
|
||||||
|
#define SLON_AUTH_ACCOUNT_ID U8* account_id = Json.Get(session->auth, "account_id");
|
||||||
|
|
||||||
|
Bool @slon_api_authorized(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
return session->auth > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_api_generate_random_hex_string(SlonHttpSession* session, I64 size)
|
||||||
|
{
|
||||||
|
U8* str = @slon_calloc(session, (size + 1) * 2);
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < size; i++) {
|
||||||
|
String.Append(str, "%02x", RandU64 & 0xff);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_api_generate_unique_id(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
U8* unique_id = @slon_calloc(session, 64);
|
||||||
|
U64 id = ((CDate2Unix(Now) + SLON_API_LOCAL_TIME_OFFSET) * 1000) << 16;
|
||||||
|
id += RandU64 & 0xffff;
|
||||||
|
StrPrint(unique_id, "%d", id);
|
||||||
|
return unique_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_api_timestamp_from_cdate(SlonHttpSession* session, CDate* date)
|
||||||
|
{
|
||||||
|
CDateStruct ds;
|
||||||
|
Date2Struct(&ds, date);
|
||||||
|
U8* timestamp = @slon_calloc(session, 32);
|
||||||
|
StrPrint(timestamp, "%04d-%02d-%02dT%02d:%02d:%02d.000-05:00", ds.year, ds.mon, ds.day_of_mon, ds.hour, ds.min, ds.sec);
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @slon_api_boolean_from_string(U8* s)
|
||||||
|
{
|
||||||
|
// https://docs.joinmastodon.org/client/intro/#boolean
|
||||||
|
// True-or-false (Booleans)
|
||||||
|
// A boolean value is considered false for the values 0, f, F, false, FALSE, off, OFF; considered to not be provided for empty strings;
|
||||||
|
// and considered to be true for all other values. When using JSON data, use the literals true, false, and null instead.
|
||||||
|
return !(!StrICmp("0", s) || !StrICmp("f", s) || !StrICmp("false", s) || !StrICmp("off", s));
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject* @slon_api_account_by_email(U8* email)
|
||||||
|
{
|
||||||
|
if (!email || !StrLen(email))
|
||||||
|
return NULL;
|
||||||
|
JsonArray* accts = db->a("accounts");
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < accts->length; i++) {
|
||||||
|
if (!StrICmp(accts->o(i)->@("email"), email)) {
|
||||||
|
return accts->o(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject* @slon_api_account_by_id(U8* id)
|
||||||
|
{
|
||||||
|
if (!id || !StrLen(id))
|
||||||
|
return NULL;
|
||||||
|
JsonArray* accts = db->a("accounts");
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < accts->length; i++) {
|
||||||
|
if (!StrICmp(accts->o(i)->@("id"), id)) {
|
||||||
|
return accts->o(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject* @slon_api_account_by_username(U8* username)
|
||||||
|
{
|
||||||
|
if (!username || !StrLen(username))
|
||||||
|
return NULL;
|
||||||
|
JsonArray* accts = db->a("accounts");
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < accts->length; i++) {
|
||||||
|
if (!StrICmp(accts->o(i)->@("username"), username)) {
|
||||||
|
return accts->o(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
252
Slon/Modules/Db.HC
Normal file
252
Slon/Modules/Db.HC
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
#define SLON_DB_PATH "A:/db"
|
||||||
|
|
||||||
|
JsonObject* db = Json.CreateObject();
|
||||||
|
|
||||||
|
U0 @slon_db_load_accounts_from_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/accounts.json", SLON_DB_PATH);
|
||||||
|
db->set("accounts", Json.ParseFile(scratch_buffer), JSON_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_actors_from_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/actors.json", SLON_DB_PATH);
|
||||||
|
db->set("actors", Json.ParseFile(scratch_buffer), JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_apps_from_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/apps.json", SLON_DB_PATH);
|
||||||
|
db->set("apps", Json.ParseFile(scratch_buffer), JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_instance_from_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/instance.json", SLON_DB_PATH);
|
||||||
|
db->set("instance", Json.ParseFile(scratch_buffer), JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_oauth_from_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/oauth.json", SLON_DB_PATH);
|
||||||
|
db->set("oauth", Json.ParseFile(scratch_buffer), JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_private_keys_from_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/private_keys.json", SLON_DB_PATH);
|
||||||
|
db->set("private_keys", Json.ParseFile(scratch_buffer), JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_followers_from_disk()
|
||||||
|
{
|
||||||
|
JsonObject* followers = Json.CreateObject();
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/followers/*.json", SLON_DB_PATH);
|
||||||
|
CDirEntry* files = FilesFind(scratch_buffer);
|
||||||
|
CDirEntry* de = files;
|
||||||
|
JsonArray* follower_array = NULL;
|
||||||
|
while (de) {
|
||||||
|
follower_array = Json.ParseFile(de->full_name);
|
||||||
|
if (follower_array) {
|
||||||
|
StrFind(".json", de->name)[0] = NULL;
|
||||||
|
followers->set(de->name, follower_array, JSON_ARRAY);
|
||||||
|
}
|
||||||
|
de = de->next;
|
||||||
|
}
|
||||||
|
DirTreeDel(files);
|
||||||
|
db->set("followers", followers, JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_statuses_from_disk()
|
||||||
|
{
|
||||||
|
JsonObject* statuses = Json.CreateObject();
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/statuses/*.json", SLON_DB_PATH);
|
||||||
|
CDirEntry* files = FilesFind(scratch_buffer);
|
||||||
|
CDirEntry* de = files;
|
||||||
|
JsonArray* status_array = NULL;
|
||||||
|
while (de) {
|
||||||
|
status_array = Json.ParseFile(de->full_name);
|
||||||
|
if (status_array) {
|
||||||
|
StrFind(".json", de->name)[0] = NULL;
|
||||||
|
statuses->set(de->name, status_array, JSON_ARRAY);
|
||||||
|
}
|
||||||
|
de = de->next;
|
||||||
|
}
|
||||||
|
DirTreeDel(files);
|
||||||
|
db->set("statuses", statuses, JSON_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_accounts_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/accounts.json", SLON_DB_PATH);
|
||||||
|
Json.DumpToFile(scratch_buffer, db->a("accounts"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_actors_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/actors.json", SLON_DB_PATH);
|
||||||
|
Json.DumpToFile(scratch_buffer, db->o("actors"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_apps_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/apps.json", SLON_DB_PATH);
|
||||||
|
Json.DumpToFile(scratch_buffer, db->o("apps"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_instance_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/instance.json", SLON_DB_PATH);
|
||||||
|
Json.DumpToFile(scratch_buffer, db->o("instance"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_oauth_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/oauth.json", SLON_DB_PATH);
|
||||||
|
Json.DumpToFile(scratch_buffer, db->o("oauth"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_private_keys_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
StrPrint(scratch_buffer, "%s/private_keys.json", SLON_DB_PATH);
|
||||||
|
Json.DumpToFile(scratch_buffer, db->o("private_keys"));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_followers_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
JsonKey* key = db->o("followers")->keys;
|
||||||
|
while (key) {
|
||||||
|
StrPrint(scratch_buffer, "%s/followers/%s.json", SLON_DB_PATH, key->name);
|
||||||
|
Json.DumpToFile(scratch_buffer, key->value);
|
||||||
|
key = key->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_statuses_to_disk()
|
||||||
|
{
|
||||||
|
U8 scratch_buffer[256];
|
||||||
|
JsonKey* key = db->o("statuses")->keys;
|
||||||
|
while (key) {
|
||||||
|
StrPrint(scratch_buffer, "%s/statuses/%s.json", SLON_DB_PATH, key->name);
|
||||||
|
Json.DumpToFile(scratch_buffer, key->value);
|
||||||
|
key = key->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_save_to_disk()
|
||||||
|
{
|
||||||
|
@slon_db_save_accounts_to_disk();
|
||||||
|
@slon_db_save_actors_to_disk();
|
||||||
|
@slon_db_save_apps_to_disk();
|
||||||
|
@slon_db_save_followers_to_disk();
|
||||||
|
@slon_db_save_instance_to_disk();
|
||||||
|
@slon_db_save_oauth_to_disk();
|
||||||
|
@slon_db_save_private_keys_to_disk();
|
||||||
|
@slon_db_save_statuses_to_disk();
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_from_defaults()
|
||||||
|
{
|
||||||
|
db->set("accounts", Json.CreateArray(), JSON_ARRAY);
|
||||||
|
db->set("actors", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("apps", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("idempotency_keys", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("private_keys", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("private_keys_binary", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("public_keys", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("followers", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("instance", Json.ParseFile("M:/Slon/Static/defaults/instance.json"), JSON_OBJECT);
|
||||||
|
db->set("statuses", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
JsonObject* oauth = Json.CreateObject();
|
||||||
|
oauth->set("codes", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
oauth->set("requests", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
oauth->set("responses", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
oauth->set("tokens", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("oauth", oauth, JSON_OBJECT);
|
||||||
|
db->set("setup", FALSE, JSON_BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_load_from_disk()
|
||||||
|
{
|
||||||
|
@slon_db_load_accounts_from_disk();
|
||||||
|
@slon_db_load_actors_from_disk();
|
||||||
|
@slon_db_load_apps_from_disk();
|
||||||
|
db->set("idempotency_keys", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
@slon_db_load_private_keys_from_disk();
|
||||||
|
db->set("private_keys_binary", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
db->set("public_keys", Json.CreateObject(), JSON_OBJECT);
|
||||||
|
@slon_db_load_followers_from_disk();
|
||||||
|
@slon_db_load_instance_from_disk();
|
||||||
|
@slon_db_load_oauth_from_disk();
|
||||||
|
@slon_db_load_statuses_from_disk();
|
||||||
|
db->set("setup", TRUE, JSON_BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_instance_update_user_count()
|
||||||
|
{
|
||||||
|
JsonObject* stats = db->o("instance")->o("stats");
|
||||||
|
stats->set("user_count", db->a("accounts")->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_instance_decrement_status_count()
|
||||||
|
{
|
||||||
|
JsonObject* stats = db->o("instance")->o("stats");
|
||||||
|
stats->set("status_count", MaxI64(0, stats->@("status_count") - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_instance_increment_status_count()
|
||||||
|
{
|
||||||
|
JsonObject* stats = db->o("instance")->o("stats");
|
||||||
|
stats->set("status_count", stats->@("status_count") + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_actors_update_user(JsonObject* acct)
|
||||||
|
{
|
||||||
|
JsonObject* actors = db->o("actors");
|
||||||
|
JsonObject* actor = actors->o(acct->@("username"));
|
||||||
|
|
||||||
|
if (!actor) {
|
||||||
|
// FIXME: Handle this error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
actor->set("name", acct->@("display_name"));
|
||||||
|
actor->set("summary", acct->@("note"));
|
||||||
|
JsonObject* icon = actor->o("icon");
|
||||||
|
icon->set("url", acct->@("avatar"));
|
||||||
|
actor->set("attachment", acct->@("fields"));
|
||||||
|
@slon_db_save_actors_to_disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_db_init()
|
||||||
|
{
|
||||||
|
if (FileFind(SLON_DB_PATH)) {
|
||||||
|
@slon_log(LOG_DB, "loading db from disk");
|
||||||
|
@slon_db_load_from_disk;
|
||||||
|
} else {
|
||||||
|
@slon_log(LOG_DB, "no db found; loading defaults");
|
||||||
|
@slon_db_load_from_defaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@slon_db_init;
|
||||||
|
|
||||||
|
JsonArray* SLON_EMPTY_JSON_ARRAY = Json.CreateArray();
|
||||||
|
JsonObject* SLON_EMPTY_JSON_OBJECT = Json.CreateObject();
|
||||||
|
|
||||||
|
JsonObject* SLON_DEFAULT_ACCT_OBJECT = Json.ParseFile("M:/Slon/Static/defaults/account.json");
|
||||||
|
JsonObject* SLON_DEFAULT_ACTOR_OBJECT = Json.ParseFile("M:/Slon/Static/defaults/actor.json");
|
249
Slon/Modules/Http.HC
Normal file
249
Slon/Modules/Http.HC
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
#define SLON_HTTP_BUFFER_SIZE 1048576
|
||||||
|
#define SLON_HTTP_VERB_DELETE 1
|
||||||
|
#define SLON_HTTP_VERB_GET 2
|
||||||
|
#define SLON_HTTP_VERB_OPTIONS 3
|
||||||
|
#define SLON_HTTP_VERB_PATCH 4
|
||||||
|
#define SLON_HTTP_VERB_POST 5
|
||||||
|
|
||||||
|
#define SLON_MULTIPART_PARSER_CONSUME_BOUNDARY 0
|
||||||
|
#define SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION 1
|
||||||
|
#define SLON_MULTIPART_PARSER_CONSUME_CONTENT 2
|
||||||
|
#define SLON_MULTIPART_PARSER_DONE 3
|
||||||
|
|
||||||
|
#define SLON_SCRATCH_BUFFER_AND_REQUEST_JSON \
|
||||||
|
U8 scratch_buffer[256]; \
|
||||||
|
JsonObject* request_json = @slon_http_request_json(session);
|
||||||
|
|
||||||
|
#define SLON_DEBUG_PRINT_REQUEST_JSON \
|
||||||
|
JsonObject* request_json = @slon_http_request_json(session); \
|
||||||
|
U8* request_json_str = Json.Stringify(request_json); \
|
||||||
|
AdamLog("request_json: %s\n", request_json_str); \
|
||||||
|
Free(request_json_str);
|
||||||
|
|
||||||
|
JsonObject* SLON_HTTP_STATUS_CODES = Json.ParseFile("M:/Slon/Settings/status_codes.json");
|
||||||
|
|
||||||
|
class SlonHttpBuffer {
|
||||||
|
U8* data;
|
||||||
|
I64 size;
|
||||||
|
I64 capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SlonHttpRequest {
|
||||||
|
SlonHttpBuffer* buffer;
|
||||||
|
JsonObject* headers;
|
||||||
|
JsonObject* json;
|
||||||
|
U8* data;
|
||||||
|
I64 size;
|
||||||
|
U8* verb;
|
||||||
|
U8* raw_path;
|
||||||
|
U8* path;
|
||||||
|
Bool headers_have_been_parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SlonHttpResponse {
|
||||||
|
SlonHttpBuffer* buffer;
|
||||||
|
JsonObject* headers;
|
||||||
|
U8* data;
|
||||||
|
I64 size;
|
||||||
|
I64 status_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SlonHttpSession {
|
||||||
|
U64 s;
|
||||||
|
SlonHttpRequest* request;
|
||||||
|
SlonHttpResponse* response;
|
||||||
|
I64 bytes_used;
|
||||||
|
JsonObject* auth;
|
||||||
|
};
|
||||||
|
|
||||||
|
U64 @slon_calloc(SlonHttpSession* session, I64 size)
|
||||||
|
{
|
||||||
|
if (!session || !size)
|
||||||
|
return NULL;
|
||||||
|
U64 res = CAlloc(size, adam_task);
|
||||||
|
session->bytes_used += MSize2(res);
|
||||||
|
// AdamLog("@slon_calloc: requested %d, total used: %d\n", MSize2(res), session->bytes_used);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_free(SlonHttpSession* session, U64 ptr)
|
||||||
|
{
|
||||||
|
if (!session || !ptr)
|
||||||
|
return;
|
||||||
|
session->bytes_used -= MSize2(ptr);
|
||||||
|
// AdamLog("@slon_free: freed %d, total used: %d\n", MSize2(ptr), session->bytes_used);
|
||||||
|
Free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 @slon_malloc(SlonHttpSession* session, I64 size)
|
||||||
|
{
|
||||||
|
if (!session || !size)
|
||||||
|
return NULL;
|
||||||
|
U64 res = MAlloc(size, adam_task);
|
||||||
|
session->bytes_used += MSize2(res);
|
||||||
|
// AdamLog("@slon_malloc: requested %d, total used: %d\n", MSize2(res), session->bytes_used);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_strnew(SlonHttpSession* session, U8* str)
|
||||||
|
{
|
||||||
|
if (!session || !str)
|
||||||
|
return NULL;
|
||||||
|
U8* new = StrNew(str, adam_task);
|
||||||
|
session->bytes_used += MSize2(new);
|
||||||
|
// AdamLog("@slon_strnew: requested %d, total used: %d\n", MSize2(new), session->bytes_used);
|
||||||
|
// AdamLog("@slon_strnew: %s\n", new);
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_http_decode_urlencoded_string(SlonHttpSession* session, U8* str)
|
||||||
|
{
|
||||||
|
if (!StrFind("%", str) && !StrFind("+", str)) {
|
||||||
|
return @slon_strnew(session, str);
|
||||||
|
}
|
||||||
|
U8* decoded_string = @slon_calloc(session, StrLen(str));
|
||||||
|
I64 i = 0;
|
||||||
|
I64 j;
|
||||||
|
U32 code_point;
|
||||||
|
while (i < StrLen(str)) {
|
||||||
|
if (str[i] == '%') {
|
||||||
|
code_point = 0;
|
||||||
|
for (j = 2; j > 0; j--) {
|
||||||
|
if (str[i + j] >= '0' && str[i + j] <= '9')
|
||||||
|
code_point += (@t(j == 1, 16, 1) * (str[i + j] - '0'));
|
||||||
|
if (str[i + j] >= 'A' && str[i + j] <= 'F')
|
||||||
|
code_point += (@t(j == 1, 16, 1) * (10 + (str[i + j] - 'A')));
|
||||||
|
if (str[i + j] >= 'a' && str[i + j] <= 'f')
|
||||||
|
code_point += (@t(j == 1, 16, 1) * (10 + (str[i + j] - 'a')));
|
||||||
|
}
|
||||||
|
String.Append(decoded_string, "%c", code_point);
|
||||||
|
i += 3;
|
||||||
|
} else if (str[i] == '+') {
|
||||||
|
String.Append(decoded_string, " ");
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
String.Append(decoded_string, "%c", str[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decoded_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject* @slon_http_request_json(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (!session->request->json)
|
||||||
|
return SLON_EMPTY_JSON_OBJECT;
|
||||||
|
return session->request->json;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_set_header(SlonHttpSession* session, U8* key, U8* value)
|
||||||
|
{
|
||||||
|
Json.Set(session->response->headers, key, value, JSON_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_set_content_type(SlonHttpSession* session, U8* value)
|
||||||
|
{
|
||||||
|
@slon_http_set_header(session, "content-type", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_set_status_code(SlonHttpSession* session, I64 status_code)
|
||||||
|
{
|
||||||
|
session->response->status_code = status_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send_ap_json(SlonHttpSession* session, U64 json)
|
||||||
|
{
|
||||||
|
// a stringified copy of "json" is created, a strnew is sent, we clean up stringified copy, sender cleans up "json"
|
||||||
|
@slon_http_set_status_code(session, 200);
|
||||||
|
@slon_http_set_content_type(session, "application/activity+json; charset=utf-8");
|
||||||
|
U8* json_string = Json.Stringify(json);
|
||||||
|
session->response->data = @slon_strnew(session, json_string);
|
||||||
|
session->response->size = StrLen(session->response->data);
|
||||||
|
Free(json_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send_json(SlonHttpSession* session, U64 json)
|
||||||
|
{
|
||||||
|
// a stringified copy of "json" is created, a strnew is sent, we clean up stringified copy, sender cleans up "json"
|
||||||
|
@slon_http_set_status_code(session, 200);
|
||||||
|
@slon_http_set_content_type(session, "application/json; charset=utf-8");
|
||||||
|
U8* json_string = Json.Stringify(json);
|
||||||
|
session->response->data = @slon_strnew(session, json_string);
|
||||||
|
session->response->size = StrLen(session->response->data);
|
||||||
|
Free(json_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send_string(SlonHttpSession* session, U8* str)
|
||||||
|
{
|
||||||
|
// a strnew of "str" is sent, sender cleans up "str"
|
||||||
|
@slon_http_set_status_code(session, 200);
|
||||||
|
session->response->data = @slon_strnew(session, str);
|
||||||
|
session->response->size = StrLen(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send(SlonHttpSession* session, U64 data, I64 size)
|
||||||
|
{
|
||||||
|
// a malloc copy of "data" is sent, sender cleans up "data"
|
||||||
|
@slon_http_set_status_code(session, 200);
|
||||||
|
U8* data_new = @slon_malloc(session, size);
|
||||||
|
MemCpy(data_new, data, size);
|
||||||
|
session->response->data = data_new;
|
||||||
|
session->response->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send_file(SlonHttpSession* session, U8* path)
|
||||||
|
{
|
||||||
|
if (!session || !path)
|
||||||
|
return;
|
||||||
|
if (!FileFind(path))
|
||||||
|
return;
|
||||||
|
I64 size = 0;
|
||||||
|
U8* data = FileRead(path, &size);
|
||||||
|
@slon_http_send(session, data, size);
|
||||||
|
Free(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send_html_file(SlonHttpSession* session, U8* path)
|
||||||
|
{
|
||||||
|
@slon_http_set_content_type(session, "text/html");
|
||||||
|
@slon_http_send_file(session, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_http_send_json_file(SlonHttpSession* session, U8* path, U8* content_type = "application/json; charset=utf-8")
|
||||||
|
{
|
||||||
|
@slon_http_set_content_type(session, content_type);
|
||||||
|
@slon_http_send_file(session, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_http_request_path(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
return session->request->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @slon_http_request_verb(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
if (!StrCmp(session->request->verb, "DELETE"))
|
||||||
|
return SLON_HTTP_VERB_DELETE;
|
||||||
|
if (!StrCmp(session->request->verb, "GET"))
|
||||||
|
return SLON_HTTP_VERB_GET;
|
||||||
|
if (!StrCmp(session->request->verb, "OPTIONS"))
|
||||||
|
return SLON_HTTP_VERB_OPTIONS;
|
||||||
|
if (!StrCmp(session->request->verb, "PATCH"))
|
||||||
|
return SLON_HTTP_VERB_PATCH;
|
||||||
|
if (!StrCmp(session->request->verb, "POST"))
|
||||||
|
return SLON_HTTP_VERB_POST;
|
||||||
|
return 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_http_request_header(SlonHttpSession* session, U8* key)
|
||||||
|
{
|
||||||
|
U64 value = Json.Get(session->request->headers, key);
|
||||||
|
if (!value)
|
||||||
|
return "";
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @slon_http_request_has_query_string(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
return StrFind("?", session->request->raw_path) > 0 && !String.EndsWith("?", session->request->raw_path);
|
||||||
|
}
|
22
Slon/Modules/Log.HC
Normal file
22
Slon/Modules/Log.HC
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#define LOG_DB 100
|
||||||
|
#define LOG_HTTPD 101
|
||||||
|
|
||||||
|
U0 @slon_log(I64 module, U8* fmt, ...)
|
||||||
|
{
|
||||||
|
CDateStruct ds;
|
||||||
|
Date2Struct(&ds, Now);
|
||||||
|
AdamLog("[%02d:%02d]", ds.hour, ds.min);
|
||||||
|
switch (module) {
|
||||||
|
case LOG_DB:
|
||||||
|
AdamLog("[ slon/db] ");
|
||||||
|
break;
|
||||||
|
case LOG_HTTPD:
|
||||||
|
AdamLog("[slon/httpd] ");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AdamLog("[ slon/misc] ");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
AdamLog(fmt, argc, argv);
|
||||||
|
AdamLog("\n");
|
||||||
|
}
|
9
Slon/Modules/Meta.HC
Normal file
9
Slon/Modules/Meta.HC
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
U0 @slon_host_meta(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn request_json;
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><XRD xmlns=\"http://docs.oasis-open.org/ns/xri/xrd-1.0\"><Link rel=\"lrdd\" template=\"https://%s/.well-known/webfinger?resource={uri}\"/></XRD>", db->o("instance")->@("uri"));
|
||||||
|
@slon_http_set_content_type(session, "application/xrd+xml; charset=utf-8");
|
||||||
|
@slon_http_send_string(session, scratch_buffer);
|
||||||
|
}
|
192
Slon/Modules/OAuth.HC
Normal file
192
Slon/Modules/OAuth.HC
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
#define SLON_OAUTH_USERINFO_URL "https://app.simplelogin.io/oauth2/userinfo?access_token="
|
||||||
|
|
||||||
|
U0 @slon_oauth_well_known(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn request_json;
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "{\"issuer\":\"https://%s\",\"authorization_endpoint\":\"https://%s/oauth/authorize\",\"response_types_supported\":[\"code\"],\"app_registration_endpoint\":\"https://%s/api/v1/apps\"}",
|
||||||
|
db->o("instance")->@("uri"), db->o("instance")->@("uri"), db->o("instance")->@("uri"));
|
||||||
|
@slon_http_set_content_type(session, "application/json; charset=utf-8");
|
||||||
|
@slon_http_send_string(session, scratch_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_oauth_fetch_token(U8* client_id)
|
||||||
|
{
|
||||||
|
if (!client_id || !StrLen(client_id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
U8 url_string[256];
|
||||||
|
JsonObject* oauth_request = db->o("oauth")->o("requests")->@(client_id);
|
||||||
|
if (!oauth_request)
|
||||||
|
return;
|
||||||
|
|
||||||
|
U8* access_token = oauth_request->@("access_token");
|
||||||
|
if (!access_token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
StrPrint(url_string, "%s%s", SLON_OAUTH_USERINFO_URL, access_token);
|
||||||
|
@http_response* resp = fetch(url_string, fetch_buffer);
|
||||||
|
|
||||||
|
if (!resp)
|
||||||
|
goto oauth_free_and_return;
|
||||||
|
|
||||||
|
if (resp->body.length) {
|
||||||
|
// POSIX people think JSON should end with a new line, and the Jakt parser disagrees :^)
|
||||||
|
while (resp->body.data[StrLen(resp->body.data) - 1] == '\n')
|
||||||
|
resp->body.data[StrLen(resp->body.data) - 1] = NULL;
|
||||||
|
JsonObject* response = Json.Parse(resp->body.data);
|
||||||
|
db->o("oauth")->o("responses")->set(client_id, response, JSON_OBJECT);
|
||||||
|
}
|
||||||
|
// FIXME: Free resp
|
||||||
|
|
||||||
|
oauth_free_and_return:
|
||||||
|
Free(fetch_buffer);
|
||||||
|
Free(client_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @async_slon_oauth_fetch_token(U8* client_id)
|
||||||
|
{
|
||||||
|
Spawn(&@slon_oauth_fetch_token, StrNew(client_id, adam_task), "OauthFetchTokenTask");
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_oauth_generate_access_token(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
return @slon_api_generate_random_hex_string(session, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @slon_oauth_generate_authorization_code(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
return @slon_api_generate_random_hex_string(session, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_oauth_verify_access_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
|
||||||
|
U8* client_id = request_json->@("client_id");
|
||||||
|
U8* redirect_uri = request_json->@("redirect_uri");
|
||||||
|
JsonObject* app_object = db->o("apps")->@(client_id);
|
||||||
|
// If client_id or redirect_uri are empty, or if client app doesn't exist, Bad Request
|
||||||
|
if (!StrLen(client_id) || !StrLen(redirect_uri) || !app_object) {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
U8* client_secret = app_object->@("client_secret");
|
||||||
|
JsonObject* userinfo = db->o("oauth")->o("responses")->@(client_id);
|
||||||
|
if (userinfo) {
|
||||||
|
// If a userinfo with the client_id exists, read the userinfo Object.
|
||||||
|
U8* email = userinfo->@("email");
|
||||||
|
if (email && StrLen(email)) {
|
||||||
|
JsonObject* acct = @slon_api_account_by_email(email);
|
||||||
|
if (acct) {
|
||||||
|
|
||||||
|
// If the account exists,
|
||||||
|
// create a token that points to the account
|
||||||
|
U8* access_token = NULL;
|
||||||
|
Bool access_token_exists = TRUE;
|
||||||
|
while (access_token_exists) {
|
||||||
|
if (access_token) {
|
||||||
|
@slon_free(session, access_token);
|
||||||
|
}
|
||||||
|
access_token = @slon_oauth_generate_access_token(session);
|
||||||
|
access_token_exists = db->o("oauth")->o("tokens")->@(access_token) > 0;
|
||||||
|
}
|
||||||
|
I64 created_at = ToF64(CDate2Unix(Now));
|
||||||
|
|
||||||
|
JsonObject* token_object = Json.CreateObject();
|
||||||
|
token_object->set("access_token", access_token, JSON_STRING);
|
||||||
|
token_object->set("token_type", "Bearer", JSON_STRING);
|
||||||
|
token_object->set("scope", "read write follow push", JSON_STRING);
|
||||||
|
token_object->set("created_at", created_at, JSON_NUMBER);
|
||||||
|
token_object->set("account_id", acct->@("id"), JSON_STRING);
|
||||||
|
token_object->set("client_id", client_id, JSON_STRING);
|
||||||
|
token_object->set("email", email, JSON_STRING);
|
||||||
|
db->o("oauth")->o("tokens")->set(access_token, token_object, JSON_OBJECT);
|
||||||
|
// FIXME: We need to commit this to disk eventually? but not immediately
|
||||||
|
|
||||||
|
U8* authorization_code = NULL;
|
||||||
|
Bool authorization_code_exists = TRUE;
|
||||||
|
while (authorization_code_exists) {
|
||||||
|
if (authorization_code) {
|
||||||
|
@slon_free(session, authorization_code);
|
||||||
|
}
|
||||||
|
authorization_code = @slon_oauth_generate_authorization_code(session);
|
||||||
|
authorization_code_exists = db->o("oauth")->o("codes")->@(authorization_code) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject* code_object = Json.CreateObject();
|
||||||
|
code_object->set("access_token", access_token, JSON_STRING);
|
||||||
|
code_object->set("token_type", "Bearer", JSON_STRING);
|
||||||
|
code_object->set("scope", "read write follow push", JSON_STRING);
|
||||||
|
code_object->set("created_at", created_at, JSON_NUMBER);
|
||||||
|
code_object->set("account_id", acct->@("id"), JSON_STRING);
|
||||||
|
code_object->set("client_id", client_id, JSON_STRING);
|
||||||
|
code_object->set("client_secret", client_secret, JSON_STRING);
|
||||||
|
code_object->set("email", email, JSON_STRING);
|
||||||
|
db->o("oauth")->o("codes")->set(authorization_code, code_object, JSON_OBJECT);
|
||||||
|
@slon_db_save_oauth_to_disk;
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "%s?code=%s", redirect_uri, authorization_code);
|
||||||
|
JsonObject* redirect_uri_object = Json.CreateObject();
|
||||||
|
redirect_uri_object->set("redirect_uri", scratch_buffer, JSON_STRING);
|
||||||
|
@slon_http_send_json(session, redirect_uri_object);
|
||||||
|
Json.Delete(redirect_uri_object);
|
||||||
|
|
||||||
|
@slon_free(session, authorization_code);
|
||||||
|
@slon_free(session, access_token);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If the account does not exist, return Not Found
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Response doesn't contain an email, therefore user is Unauthorized.
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (!db->o("oauth")->o("requests")->@(client_id)) {
|
||||||
|
// If a request with the client_id does not exist, create one, and spawn a fetch() instance to retrieve the OAuth2 token.
|
||||||
|
db->o("oauth")->o("requests")->set(client_id, request_json, JSON_OBJECT);
|
||||||
|
@async_slon_oauth_fetch_token(client_id);
|
||||||
|
}
|
||||||
|
@slon_http_set_status_code(session, 202);
|
||||||
|
}
|
||||||
|
Json.Delete(app_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @slon_oauth_token_post(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer;
|
||||||
|
|
||||||
|
U8* client_id = request_json->@("client_id");
|
||||||
|
U8* client_secret = request_json->@("client_secret");
|
||||||
|
U8* code = request_json->@("code");
|
||||||
|
|
||||||
|
JsonObject* code_object = db->o("oauth")->o("codes")->@(code);
|
||||||
|
if (!StrLen(client_id) || !StrLen(client_secret) || !code_object) {
|
||||||
|
// If client_id is empty, or client_secret is empty, or the code doesn't exist, it's a Bad Request.
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* access_token = code_object->@("access_token");
|
||||||
|
if (!StrCmp(client_id, code_object->@("client_id")) && !StrCmp(client_secret, code_object->@("client_secret"))) {
|
||||||
|
JsonObject* token = db->o("oauth")->o("tokens")->@(access_token);
|
||||||
|
if (token) {
|
||||||
|
@slon_http_send_json(session, token);
|
||||||
|
} else {
|
||||||
|
// If the token doesn't exist, Page Expired?
|
||||||
|
@slon_http_set_status_code(session, 419);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If client_id and client_secret do not match, it's Unauthorized
|
||||||
|
@slon_http_set_status_code(session, 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json.Delete(code_object);
|
||||||
|
}
|
42
Slon/Modules/Web.HC
Normal file
42
Slon/Modules/Web.HC
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
U0 @slon_web_user_get(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
no_warn scratch_buffer, request_json;
|
||||||
|
|
||||||
|
I64 path_segments_count = 0;
|
||||||
|
U8** path_segments = String.Split(StrFind("@", @slon_http_request_path(session)) + 1, '/', &path_segments_count);
|
||||||
|
|
||||||
|
U8* user = path_segments[0];
|
||||||
|
|
||||||
|
if (path_segments_count == 1) {
|
||||||
|
JsonObject* actor = db->o("actors")->@(user);
|
||||||
|
if (!actor) {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
goto slon_web_user_get_return;
|
||||||
|
}
|
||||||
|
// gib profil pl0x
|
||||||
|
|
||||||
|
I64 html_file_size;
|
||||||
|
U8* html_file_data = FileRead("M:/Slon/Static/html/user.html", &html_file_size);
|
||||||
|
U8* user_file_data = Json.Stringify(actor);
|
||||||
|
|
||||||
|
U8* html_data = @slon_calloc(session, (html_file_size * 2) + (StrLen(user_file_data) * 2));
|
||||||
|
String.Append(html_data, html_file_data);
|
||||||
|
String.Append(html_data, "<script>getStatuses(");
|
||||||
|
String.Append(html_data, user_file_data);
|
||||||
|
String.Append(html_data, ");</script>");
|
||||||
|
@slon_http_set_content_type(session, "text/html");
|
||||||
|
@slon_http_send(session, html_data, StrLen(html_data));
|
||||||
|
|
||||||
|
Free(html_file_data);
|
||||||
|
Free(user_file_data);
|
||||||
|
@slon_free(session, html_data);
|
||||||
|
goto slon_web_user_get_return;
|
||||||
|
} else {
|
||||||
|
// do something here (statuses, followers, media, etc.)
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
slon_web_user_get_return:
|
||||||
|
Free(path_segments);
|
||||||
|
}
|
39
Slon/Modules/Webfinger.HC
Normal file
39
Slon/Modules/Webfinger.HC
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
U0 @slon_webfinger(SlonHttpSession* session)
|
||||||
|
{
|
||||||
|
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
|
||||||
|
U8* resource = request_json->@("resource");
|
||||||
|
StrPrint(scratch_buffer, "@%s", db->o("instance")->@("uri"));
|
||||||
|
if (resource && String.BeginsWith("acct:", resource) && String.EndsWith(scratch_buffer, resource)) {
|
||||||
|
resource = StrFind(":", resource) + 1;
|
||||||
|
StrFind("@", resource)[0] = NULL;
|
||||||
|
if (db->o("actors")->@(resource)) {
|
||||||
|
JsonObject* webfinger_object = Json.CreateObject();
|
||||||
|
|
||||||
|
StrPrint(scratch_buffer, "acct:%s@%s", resource, db->o("instance")->@("uri"));
|
||||||
|
webfinger_object->set("subject", scratch_buffer, JSON_STRING);
|
||||||
|
|
||||||
|
JsonArray* aliases = Json.CreateArray();
|
||||||
|
StrPrint(scratch_buffer, "https://%s/@%s", db->o("instance")->@("uri"), resource);
|
||||||
|
aliases->append(Json.CreateItem(scratch_buffer, JSON_STRING));
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s", db->o("instance")->@("uri"), resource);
|
||||||
|
aliases->append(Json.CreateItem(scratch_buffer, JSON_STRING));
|
||||||
|
webfinger_object->set("aliases", aliases, JSON_ARRAY);
|
||||||
|
|
||||||
|
JsonArray* links = Json.CreateArray();
|
||||||
|
JsonObject* link_object = Json.CreateObject();
|
||||||
|
link_object->set("rel", "self", JSON_STRING);
|
||||||
|
link_object->set("type", "application/activity+json", JSON_STRING);
|
||||||
|
StrPrint(scratch_buffer, "https://%s/users/%s", db->o("instance")->@("uri"), resource);
|
||||||
|
link_object->set("href", scratch_buffer, JSON_STRING);
|
||||||
|
links->append(Json.CreateItem(link_object, JSON_OBJECT));
|
||||||
|
webfinger_object->set("links", links, JSON_ARRAY);
|
||||||
|
|
||||||
|
@slon_http_send_json(session, webfinger_object);
|
||||||
|
Json.Delete(webfinger_object);
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 404);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@slon_http_set_status_code(session, 400);
|
||||||
|
}
|
||||||
|
}
|
1
Slon/Settings/status_codes.json
Normal file
1
Slon/Settings/status_codes.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"100": "Continue", "101": "Switching Protocols", "102": "Processing", "103": "Early Hints", "200": "OK", "201": "Created", "202": "Accepted", "203": "Non-Authoritative Information", "204": "No Content", "205": "Reset Content", "206": "Partial Content", "207": "Multi-Status", "208": "Already Reported", "218": "This is fine", "226": "IM Used", "300": "Multiple Choices", "301": "Moved Permanently", "302": "Found", "303": "See Other", "304": "Not Modified", "306": "Switch Proxy", "307": "Temporary Redirect", "308": "Resume Incomplete", "400": "Bad Request", "401": "Unauthorized", "402": "Payment Required", "403": "Forbidden", "404": "Not Found", "405": "Method Not Allowed", "406": "Not Acceptable", "407": "Proxy Authentication Required", "408": "Request Timeout", "409": "Conflict", "410": "Gone", "411": "Length Required", "412": "Precondition Failed", "413": "Request Entity Too Large", "414": "Request-URI Too Long", "415": "Unsupported Media Type", "416": "Requested Range Not Satisfiable", "417": "Expectation Failed", "418": "I'm a teapot", "419": "Page Expired", "420": "Method Failure", "421": "Misdirected Request", "422": "Unprocessable Entity", "423": "Locked", "424": "Failed Dependency", "426": "Upgrade Required", "428": "Precondition Required", "429": "Too Many Requests", "431": "Request Header Fields Too Large", "440": "Login Time-out", "444": "Connection Closed Without Response", "449": "Retry With", "450": "Blocked by Windows Parental Controls", "451": "Unavailable For Legal Reasons", "494": "Request Header Too Large", "495": "SSL Certificate Error", "496": "SSL Certificate Required", "497": "HTTP Request Sent to HTTPS Port", "498": "Invalid Token", "499": "Client Closed Request", "500": "Internal Server Error", "501": "Not Implemented", "502": "Bad Gateway", "503": "Service Unavailable", "504": "Gateway Timeout", "505": "HTTP Version Not Supported", "506": "Variant Also Negotiates", "507": "Insufficient Storage", "508": "Loop Detected", "509": "Bandwidth Limit Exceeded", "510": "Not Extended", "511": "Network Authentication Required", "520": "Unknown Error", "521": "Web Server Is Down", "522": "Connection Timed Out", "523": "Origin Is Unreachable", "524": "A Timeout Occurred", "525": "SSL Handshake Failed", "526": "Invalid SSL Certificate", "527": "Railgun Listener to Origin Error", "530": "Origin DNS Error", "598": "Network Read Timeout Error"}
|
117
Slon/Static/css/main.css
Normal file
117
Slon/Static/css/main.css
Normal file
File diff suppressed because one or more lines are too long
8
Slon/Static/defaults/account.json
Normal file
8
Slon/Static/defaults/account.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"username": "",
|
||||||
|
"display_name": "",
|
||||||
|
"email": "",
|
||||||
|
"note": "",
|
||||||
|
"avatar": "",
|
||||||
|
"header": ""
|
||||||
|
}
|
44
Slon/Static/defaults/actor.json
Normal file
44
Slon/Static/defaults/actor.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"featured": {
|
||||||
|
"@id": "toot:featured",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"featuredTags": {
|
||||||
|
"@id": "toot:featuredTags",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"alsoKnownAs": {
|
||||||
|
"@id": "as:alsoKnownAs",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"movedTo": {
|
||||||
|
"@id": "as:movedTo",
|
||||||
|
"@type": "@id"
|
||||||
|
},
|
||||||
|
"schema": "http://schema.org#",
|
||||||
|
"PropertyValue": "schema:PropertyValue",
|
||||||
|
"value": "schema:value",
|
||||||
|
"discoverable": "toot:discoverable",
|
||||||
|
"suspended": "toot:suspended",
|
||||||
|
"memorial": "toot:memorial",
|
||||||
|
"indexable": "toot:indexable",
|
||||||
|
"attributionDomains": {
|
||||||
|
"@id": "toot:attributionDomains",
|
||||||
|
"@type": "@id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Person",
|
||||||
|
"manuallyApprovesFollowers": false,
|
||||||
|
"discoverable": false,
|
||||||
|
"indexable": false,
|
||||||
|
"memorial": false,
|
||||||
|
"tag": [],
|
||||||
|
"endpoints": {}
|
||||||
|
}
|
14
Slon/Static/defaults/instance.json
Normal file
14
Slon/Static/defaults/instance.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"uri": "error.checksum.fail",
|
||||||
|
"title": "error.checksum.fail",
|
||||||
|
"short_description": "A fediverse instance running on TempleOS",
|
||||||
|
"description": "A fediverse instance running on TempleOS",
|
||||||
|
"email": "alec@checksum.fail",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"registrations": false,
|
||||||
|
"stats": {
|
||||||
|
"user_count": 0,
|
||||||
|
"status_count": 0,
|
||||||
|
"domain_count": 0
|
||||||
|
}
|
||||||
|
}
|
168
Slon/Static/html/admin/main.html
Normal file
168
Slon/Static/html/admin/main.html
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
<!doctypehtml>
|
||||||
|
<link href=https://cdn.jsdelivr.net/npm/bulma@1.0.3/css/bulma.min.css rel=stylesheet>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-x {
|
||||||
|
width: 640px
|
||||||
|
}
|
||||||
|
|
||||||
|
.next {
|
||||||
|
text-align: right
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
height: 16px
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
padding-left: 32px;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: top
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
width: 240px
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<aside class="menu is-inline-block">
|
||||||
|
<p class="menu-label">Info</p>
|
||||||
|
<ul class="menu-list">
|
||||||
|
<li><a onclick="infoStats()" id="menuitem-stats">Statistics</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="menu-label">Manage</p>
|
||||||
|
<ul class="menu-list">
|
||||||
|
<li><a onclick="manageAccounts()" id="menuitem-accounts">Accounts</a></li>
|
||||||
|
<li><a onclick="manageInstance()" id="menuitem-instance">Instance</a></li>
|
||||||
|
</ul>
|
||||||
|
<p class="menu-label">Diagnostics</p>
|
||||||
|
<ul class="menu-list">
|
||||||
|
<li><a onclick="diagsLogs()" id="menuitem-logs">Logs</a></li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
<div id="content" class="container main-content is-inline-block">
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function clearActiveLinks() {
|
||||||
|
document.querySelectorAll("a").forEach(function (a) { a.className = ""; });
|
||||||
|
}
|
||||||
|
function setActiveLink(link) {
|
||||||
|
document.getElementById("menuitem-" + link).className = "is-active";
|
||||||
|
}
|
||||||
|
function setContent(html) {
|
||||||
|
document.getElementById("content").innerHTML = html;
|
||||||
|
}
|
||||||
|
async function infoStats() {
|
||||||
|
clearActiveLinks();
|
||||||
|
const request = new Request("/info/stats");
|
||||||
|
const response = await fetch(request);
|
||||||
|
const stats = await response.json();
|
||||||
|
let html = "<h4 class=\"title is-4\">Statistics</h4><div class=spacer></div><div>Uptime: " + formatTime(stats["uptime"]) + "</div>";
|
||||||
|
setContent(html);
|
||||||
|
setActiveLink("stats");
|
||||||
|
}
|
||||||
|
async function manageAccounts() {
|
||||||
|
clearActiveLinks();
|
||||||
|
const request = new Request("/manage/accounts");
|
||||||
|
const response = await fetch(request);
|
||||||
|
const accounts = await response.json();
|
||||||
|
let html = "<h4 class=\"title is-4\">Accounts</h4><div class=spacer></div>";
|
||||||
|
if (accounts.length) {
|
||||||
|
html += "<table class=table><thead><tr><th>id</th><th>username</th></tr></head><tbody>";
|
||||||
|
for (let i = 0; i < accounts.length; i++) {
|
||||||
|
html += "<tr><td>" + accounts[i]["id"] + "</td><td>" + accounts[i]["username"] + "</td><tr>";
|
||||||
|
}
|
||||||
|
html += "</tbody></table>";
|
||||||
|
} else {
|
||||||
|
html += "No users";
|
||||||
|
}
|
||||||
|
html += "<br><br><input onclick=manageNewUser() class=button type=button value=\"New User\">";
|
||||||
|
setContent(html);
|
||||||
|
setActiveLink("accounts");
|
||||||
|
}
|
||||||
|
function manageNewUser() {
|
||||||
|
clearActiveLinks();
|
||||||
|
let html = "<h4 class=\"title is-4\">New User</h4><div class=spacer></div>";
|
||||||
|
|
||||||
|
html += "<form action=\"javascript:saveNewUser()\"><div>";
|
||||||
|
|
||||||
|
html += "<div class=\"section is-inline-block\" style=\"width:420px;vertical-align:top\">";
|
||||||
|
html += "<label class=label>Username</label><div class=control><input id=username class=input placeholder=baoh required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "<label class=label>Display Name</label><div class=control><input id=display_name class=input placeholder=\"Ikuro Hashizawa\" required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "<label class=label>Email</label><div class=control><input id=email class=input type=email placeholder=\"cooldude42069@checksum.fail\" required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "<label class=label>Bio</label><div class=control><input id=bio class=input placeholder=\"ima firin mah lazer cannon\" required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "</div>";
|
||||||
|
html += "<div class=\"section is-inline-block\" style=\"width:420px;vertical-align:top\">";
|
||||||
|
html += "<label class=label>Avatar</label><div class=control><input id=avatar class=input placeholder=\"https://full.path.to/my/avatar.png\" required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "<label class=label>Header</label><div class=control><input id=header class=input placeholder=\"https://full.path.to/my/header.png\" required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "<label class=label>Private Key (must be in DER format)</label><div class=control><input onchange=updateBase64(this) id=privatekey type=file required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "<label class=label>Public Key (must be in PEM format)</label><div class=control><input onchange=updateBase64(this) id=publickey type=file required autocomplete=off></div><div class=spacer></div>";
|
||||||
|
html += "</div>";
|
||||||
|
|
||||||
|
html += "</div>";
|
||||||
|
html += "<div class=\"control next\"><input class=\"button is-link\" type=submit value=Save></div>"
|
||||||
|
html += "</div></form>";
|
||||||
|
|
||||||
|
setContent(html);
|
||||||
|
setActiveLink("accounts");
|
||||||
|
}
|
||||||
|
async function saveNewUser() {
|
||||||
|
let data = {};
|
||||||
|
let fields = document.getElementsByTagName("input");
|
||||||
|
for (var i = 0; i < fields.length; i++) {
|
||||||
|
switch (fields[i].type) {
|
||||||
|
case "checkbox":
|
||||||
|
data[fields[i].id] = fields[i].checked;
|
||||||
|
break;
|
||||||
|
case "file":
|
||||||
|
data[fields[i].id] = fields[i].base64;
|
||||||
|
break;
|
||||||
|
case "submit":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data[fields[i].id] = fields[i].value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const request = new Request("/new/account", {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
const response = await fetch(request);
|
||||||
|
const json = await response.json();
|
||||||
|
if (!Object.keys(json).length) {
|
||||||
|
manageAccounts();
|
||||||
|
} else {
|
||||||
|
alert(JSON.stringify(json));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateBase64(el) {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.readAsDataURL(el.files[0]);
|
||||||
|
reader.addEventListener(
|
||||||
|
"load",
|
||||||
|
() => {
|
||||||
|
el.base64 = reader.result.split(";base64,")[1];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const formatTime = milliseconds => {
|
||||||
|
const seconds = Math.floor((milliseconds / 1000) % 60);
|
||||||
|
const minutes = Math.floor((milliseconds / 1000 / 60) % 60);
|
||||||
|
const hours = Math.floor((milliseconds / 1000 / 60 / 60) % 24);
|
||||||
|
return [
|
||||||
|
hours.toString().padStart(2, "0"),
|
||||||
|
minutes.toString().padStart(2, "0"),
|
||||||
|
seconds.toString().padStart(2, "0")
|
||||||
|
].join(":");
|
||||||
|
}
|
||||||
|
addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
infoStats();
|
||||||
|
})
|
||||||
|
document.querySelectorAll("input[type=file]").forEach(function (e) {
|
||||||
|
a.className = "";
|
||||||
|
});
|
||||||
|
</script>
|
83
Slon/Static/html/admin/setup_instance.html
Normal file
83
Slon/Static/html/admin/setup_instance.html
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<!doctypehtml>
|
||||||
|
<link href=https://cdn.jsdelivr.net/npm/bulma@1.0.3/css/bulma.min.css rel=stylesheet>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 640px
|
||||||
|
}
|
||||||
|
|
||||||
|
.next {
|
||||||
|
text-align: right
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
height: 16px
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class=container>
|
||||||
|
<nav class=panel>
|
||||||
|
<form action="javascript:setupInstance()">
|
||||||
|
<p class=panel-heading>Setup
|
||||||
|
<div class=section>
|
||||||
|
<p>Enter the following information to set up your Slon instance.
|
||||||
|
<div class=spacer></div>
|
||||||
|
<label class=label>URI</label>
|
||||||
|
<div class=control><input id=uri class=input placeholder=my-slon-instance.foo required
|
||||||
|
autocomplete=off></div>
|
||||||
|
<div style=height:16px></div>
|
||||||
|
|
||||||
|
<label class=label>Title</label>
|
||||||
|
<div class=control><input id=title class=input placeholder="My Slon Instance" required
|
||||||
|
autocomplete=off></div>
|
||||||
|
<div class=spacer></div>
|
||||||
|
|
||||||
|
<label class=label>Description</label>
|
||||||
|
<div class=control><input id=description class=input
|
||||||
|
placeholder="A fediverse instance running on TempleOS" required autocomplete=off></div>
|
||||||
|
<div class=spacer></div>
|
||||||
|
|
||||||
|
<label class=label>Administrator Email</label>
|
||||||
|
<div class=control><input id=email class=input placeholder=alec@checksum.fail required type=email
|
||||||
|
autocomplete=off>
|
||||||
|
</div>
|
||||||
|
<div class=spacer></div>
|
||||||
|
|
||||||
|
<label class=checkbox><input id=registrations type=checkbox> Enable
|
||||||
|
registrations</label>
|
||||||
|
<div class="control next"><input class="button is-link" type=submit value=Next></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
document.getElementById("uri").focus();
|
||||||
|
})
|
||||||
|
async function setupInstance() {
|
||||||
|
let data = {};
|
||||||
|
let fields = document.getElementsByTagName("input");
|
||||||
|
for (var i = 0; i < fields.length; i++) {
|
||||||
|
switch (fields[i].type) {
|
||||||
|
case "checkbox":
|
||||||
|
data[fields[i].id] = fields[i].checked;
|
||||||
|
break;
|
||||||
|
case "submit":
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data[fields[i].id] = fields[i].value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const request = new Request("/setup/instance", {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
const response = await fetch(request);
|
||||||
|
const json = await response.json();
|
||||||
|
window.location = "/";
|
||||||
|
}
|
||||||
|
</script>
|
29
Slon/Static/html/user.html
Normal file
29
Slon/Static/html/user.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!doctypehtml>
|
||||||
|
<title></title>
|
||||||
|
<link href=https://error.checksum.fail/css/12391289038129038.css rel=stylesheet>
|
||||||
|
<link href=https://linusg.github.io/serenityos-emoji-font/SerenityOS-Emoji.css rel=stylesheet>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-family: "SerenityOS Emoji", "TempleOS", -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif
|
||||||
|
}
|
||||||
|
|
||||||
|
.pix {
|
||||||
|
image-rendering: pixelated
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class=page-container>
|
||||||
|
<div class=page-header></div>
|
||||||
|
<div class=page-content>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<center><font style="color: #fff">Loading Statuses...</font><img class="text-cursor"></center>
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class=page-footer>This server is powered by <a href=https://slon-project.org>Slon</a>, a
|
||||||
|
Mastodon-compatible fediverse instance for TempleOS, written in HolyC</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
|
||||||
|
<script>dayjs.extend(window.dayjs_plugin_relativeTime)</script>
|
||||||
|
<script src=https://error.checksum.fail/js/header.js></script>
|
||||||
|
<script src=https://error.checksum.fail/js/12389290138092193.js></script>
|
3
Slon/Static/js/header.js
Normal file
3
Slon/Static/js/header.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
let lastFrameTime=0,fps=0;function calculateFPS(){requestAnimationFrame(()=>{const a=performance.now();fps=1E3/(a-lastFrameTime);lastFrameTime=a;calculateFPS()})}
|
||||||
|
function updatePageHeader(){var a=new Date,c=a.toString().substring(0,3);const d=String(a.getMonth()+1).padStart(2,"0"),e=String(a.getDate()).padStart(2,"0");a=a.toString().substring(16,24);const f="CPU <b>"+(10*Math.random()).toFixed(0)+"</b>";if(navigator.userAgent.toLowerCase().includes("firefox"))var b=3735929054;else b=performance.memory,b=b.jsHeapSizeLimit-b.usedJSHeapSize;c=c+" "+d+"/"+e+" "+a+" FPS:"+fps.toFixed(0)+" Mem:"+b.toString(16).toUpperCase()+" "+f;document.getElementsByClassName("page-header")[0].innerHTML=
|
||||||
|
c}calculateFPS();updatePageHeader();setInterval(updatePageHeader,300);
|
132
Slon/Static/js/statuses.js
Normal file
132
Slon/Static/js/statuses.js
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
function updateStatusContainers() {
|
||||||
|
let els = document.getElementsByClassName('status-container');
|
||||||
|
for (var n = 0; n < els.length; n++) {
|
||||||
|
let pc = els[n];
|
||||||
|
let post_html = ""
|
||||||
|
|
||||||
|
let horizontal_fill_count = ((pc.offsetWidth - 32) / 16);
|
||||||
|
let url = pc.getElementsByTagName('url')[0].textContent;
|
||||||
|
let marqueeHref = url == "Unnamed Task" ? window.location : url;
|
||||||
|
|
||||||
|
post_html += "<b>\u2554";
|
||||||
|
for (var i = 0; i < (horizontal_fill_count / 2) - ((240 / 16) / 2); i++) {
|
||||||
|
post_html += "\u2550";
|
||||||
|
}
|
||||||
|
|
||||||
|
post_html += "<a href=\"" + marqueeHref + "\"><marquee scrollamount=16>" + url + "...</marquee></a>";
|
||||||
|
for (var i = 0; i < (horizontal_fill_count / 2) - ((240 / 16) / 2) - 2; i++) {
|
||||||
|
post_html += "\u2550";
|
||||||
|
}
|
||||||
|
post_html += "[X]\u2557<br></b>";
|
||||||
|
|
||||||
|
let height = Math.ceil(pc.getElementsByClassName('status-content')[0].offsetHeight / 16);
|
||||||
|
let desc = "Term";
|
||||||
|
|
||||||
|
for (var y = 0; y < height; y++) {
|
||||||
|
let ch = y < 4 ? desc[y] : "\u2551";
|
||||||
|
post_html += "<b>" + ch + "<div style=\"display: inline-block; height: 16px; width: " + ((horizontal_fill_count + 1) * 16).toString() + "px\"></div>\u2551</b><br>";
|
||||||
|
}
|
||||||
|
|
||||||
|
post_html += "<b>\u255a";
|
||||||
|
for (var i = 0; i < (horizontal_fill_count + 1); i++) {
|
||||||
|
post_html += "\u2550";
|
||||||
|
}
|
||||||
|
post_html += "\u255d<br></b>";
|
||||||
|
|
||||||
|
pc.innerHTML += post_html;
|
||||||
|
pc.style.display = "inline";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function smolDate(a){return a.split(" ago")[0].replace("a ","1").replace("an ","1").replace("days","d").replace("day","d").replace("hours","h").replace("hour","h").replace("minutes","m").replace("minute","m").replace("seconds","s").replace("second","s").replace("few","").replace(" ","")};
|
||||||
|
|
||||||
|
function updateStatuses(user, statuses) {
|
||||||
|
let pageContent = document.getElementsByClassName("page-content")[0];
|
||||||
|
let elements = document.createElement('div');
|
||||||
|
|
||||||
|
let container = document.createElement('div');
|
||||||
|
container.className = "status-container";
|
||||||
|
// Render user profile
|
||||||
|
{
|
||||||
|
let content = document.createElement('div');
|
||||||
|
content.className = "status-content";
|
||||||
|
let content_html = "";
|
||||||
|
content_html += "<div class=status-avatar style=margin:16px;background:url(" + user["icon"]["url"] + ");width:72px;height:72px;background-size:contain;background-repeat:no-repeat></div>";
|
||||||
|
content_html += "<div class=status-header>" + user["preferredUsername"] + "<br><a href=" + user["url"] + ">@" + user["preferredUsername"] + "@" + location.host + "</a></div>"
|
||||||
|
content_html += "<div class=status-text><font style=color:#0>" + user["summary"] + "</font></div>";
|
||||||
|
content_html += "<div class=status-text>Joined " + new Date(Date.parse(user["published"])).toString().substr(0,15) + "</div>";
|
||||||
|
content.innerHTML = content_html;
|
||||||
|
let url = document.createElement('url');
|
||||||
|
url.textContent = window.location;
|
||||||
|
let menuImg = document.createElement('img');
|
||||||
|
menuImg.className = "text-menu";
|
||||||
|
content.appendChild(menuImg);
|
||||||
|
container.appendChild(content);
|
||||||
|
container.appendChild(url);
|
||||||
|
elements.appendChild(container);
|
||||||
|
let spacer = document.createElement('div');
|
||||||
|
spacer.style.height = "16px";
|
||||||
|
elements.appendChild(spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.className = "statuses";
|
||||||
|
statuses.sort((a,b) => b.id - a.id);
|
||||||
|
for (var i = 0; i < statuses.length; i++) {
|
||||||
|
let status = statuses[i];
|
||||||
|
let container = document.createElement('div');
|
||||||
|
container.className = "status-container";
|
||||||
|
let content = document.createElement('div');
|
||||||
|
content.className = "status-content";
|
||||||
|
let content_html = "";
|
||||||
|
if (status["visibility"] == "public") {
|
||||||
|
content_html += "<span title=Public>🌎</span> ";
|
||||||
|
}
|
||||||
|
content_html += "<span class=status-timestamp>" + smolDate(dayjs(status["created_at"]).fromNow()) + "</span><br></div>";
|
||||||
|
content_html += "<div class=status-text>" + status["content"] + "</div>";
|
||||||
|
content_html += "<span class=status-counts>💬 " + status["replies_count"] + " 🔁 " + status["reblogs_count"] + " ⭐ " + status["favourites_count"] + "</span>";
|
||||||
|
content_html += "<div class=status-footer>via <a href=" + status["application"]["website"] + ">" + status["application"]["name"] + "</a></div>";
|
||||||
|
content.innerHTML = content_html;
|
||||||
|
let url = document.createElement('url');
|
||||||
|
url.textContent = status["url"];
|
||||||
|
let menuImg = document.createElement('img');
|
||||||
|
menuImg.className = "text-menu";
|
||||||
|
content.appendChild(menuImg);
|
||||||
|
container.appendChild(content);
|
||||||
|
container.appendChild(url);
|
||||||
|
elements.appendChild(container);
|
||||||
|
if (i < statuses.length - 1) {
|
||||||
|
let spacer = document.createElement('div');
|
||||||
|
spacer.style.height = "16px";
|
||||||
|
elements.appendChild(spacer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!statuses.length) {
|
||||||
|
let container = document.createElement('div');
|
||||||
|
container.className = "status-container";
|
||||||
|
let content = document.createElement('div');
|
||||||
|
content.className = "status-content";
|
||||||
|
let content_html = "";
|
||||||
|
content_html += "<div class=nostatus><font style=color:#0000a8>&FileRead &StatusRead &StatusPrint &ExeCmdLine <img class=text-error> No statuses found.\n " + Math.random().toFixed(6) + "s ans=0x00000000=0<br>C:/Home><img class=text-cursor></font></div>";
|
||||||
|
content.innerHTML = content_html;
|
||||||
|
let url = document.createElement('url');
|
||||||
|
url.textContent = "Unnamed Task";
|
||||||
|
let menuImg = document.createElement('img');
|
||||||
|
menuImg.className = "text-menu";
|
||||||
|
content.appendChild(menuImg);
|
||||||
|
container.appendChild(content);
|
||||||
|
container.appendChild(url);
|
||||||
|
elements.appendChild(container);
|
||||||
|
}
|
||||||
|
pageContent.innerHTML = "";
|
||||||
|
pageContent.appendChild(elements);
|
||||||
|
updateStatusContainers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatuses(user) {
|
||||||
|
fetch("https://error.checksum.fail/api/v1/accounts/" + user["accountId"] + "/statuses", {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {'Accept': 'application/json' }
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => updateStatuses(user, data));
|
||||||
|
}
|
12
Slon/Static/oauth/authorize.html
Normal file
12
Slon/Static/oauth/authorize.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/IdentityModel/oidc-client-js@1.8/dist/oidc-client.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var retry_count = 0, retry_timeout = 2E3; function retryVerifyAccess() { 3 < ++retry_count ? alert("Retry limit exceeded") : setTimeout(verifyAccess, retry_timeout) }
|
||||||
|
async function verifyAccess() { try { var b = localStorage.getItem("authorize"); if (null != b) { const a = await fetch("https://" + window.location.host + "/oauth/verify_access?" + b + window.location.hash.substring(1)); if (a.ok) { const c = await a.json(); window.location = c.redirect_uri } else 202 == a.status ? retryVerifyAccess() : alert("Unexpected response status: " + a.status.toString()) } } catch (a) { retryVerifyAccess() } }
|
||||||
|
var settings = { authority: "https://app.simplelogin.io/", client_id: "templeosfediverseserver-yssuxmkust", response_type: "id_token token", scope: "openid", redirect_uri: "https://" + window.location.host + "/oauth/authorize" }, mgr = new Oidc.UserManager(settings); window.location.search && localStorage.setItem("authorize", window.location.search.replace("?", "").replace("state", "client_state") + "&"); window.location.hash ? verifyAccess() : mgr.signinRedirect();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
25
System/Api/Dns.HC
Normal file
25
System/Api/Dns.HC
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#define DNS_REQUEST_PTR 0x300010
|
||||||
|
|
||||||
|
MemSet(DNS_REQUEST_PTR, NULL, sizeof(U64));
|
||||||
|
|
||||||
|
class DnsRequest {
|
||||||
|
U64 host;
|
||||||
|
U64 pointer_to_u32;
|
||||||
|
};
|
||||||
|
|
||||||
|
U32 @dns_query(U8* host)
|
||||||
|
{
|
||||||
|
U32 res = 0;
|
||||||
|
if (!host)
|
||||||
|
return U32_MAX;
|
||||||
|
DnsRequest* request = CAlloc(sizeof(DnsRequest), Fs->code_heap);
|
||||||
|
request->host = StrNew(host, adam_task);
|
||||||
|
request->pointer_to_u32 = &res;
|
||||||
|
U64* request_ptr = DNS_REQUEST_PTR;
|
||||||
|
while (*request_ptr)
|
||||||
|
Sleep(1);
|
||||||
|
LXchgU32(request_ptr, request);
|
||||||
|
while (!res)
|
||||||
|
Sleep(1);
|
||||||
|
return res;
|
||||||
|
}
|
32
System/Api/Icmp.HC
Normal file
32
System/Api/Icmp.HC
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#define ICMP_REQUEST_PTR 0x300020
|
||||||
|
|
||||||
|
MemSet(ICMP_REQUEST_PTR, NULL, sizeof(U64));
|
||||||
|
|
||||||
|
class IcmpRequest {
|
||||||
|
U64 addr;
|
||||||
|
U64 iden;
|
||||||
|
U64 seq;
|
||||||
|
U64 pointer_to_u32;
|
||||||
|
};
|
||||||
|
|
||||||
|
U32 @icmp_echo_request(U32 addr, U16 iden, U16 seq, IcmpRequest* request, I64 count)
|
||||||
|
{
|
||||||
|
U32 res = 0; // low 16 = ttl, hi 16 = payload size
|
||||||
|
request->addr = addr;
|
||||||
|
request->iden = iden;
|
||||||
|
request->seq = seq;
|
||||||
|
request->pointer_to_u32 = &res;
|
||||||
|
I64 start_jiffies = cnts.jiffies;
|
||||||
|
U64* request_ptr = ICMP_REQUEST_PTR;
|
||||||
|
if (!count)
|
||||||
|
*request_ptr = NULL;
|
||||||
|
while (*request_ptr) {
|
||||||
|
if (!(cnts.jiffies < start_jiffies + 1000))
|
||||||
|
return res;
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
LXchgU32(request_ptr, request);
|
||||||
|
while (!res && cnts.jiffies < start_jiffies + 1000)
|
||||||
|
Sleep(1);
|
||||||
|
return res;
|
||||||
|
}
|
9
System/Api/Ipv4.HC
Normal file
9
System/Api/Ipv4.HC
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
U32 @ipv4_address(I64 o3, I64 o2, I64 o1, I64 o0)
|
||||||
|
{
|
||||||
|
U32 addr = NULL;
|
||||||
|
addr.u8[3] = o3;
|
||||||
|
addr.u8[2] = o2;
|
||||||
|
addr.u8[1] = o1;
|
||||||
|
addr.u8[0] = o0;
|
||||||
|
return addr;
|
||||||
|
}
|
144
System/Api/MD5.HC
Normal file
144
System/Api/MD5.HC
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* Simple MD5 implementation
|
||||||
|
*
|
||||||
|
* https://gist.github.com/creationix/4710780
|
||||||
|
*/
|
||||||
|
|
||||||
|
U32 md5_r[64] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
||||||
|
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
||||||
|
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
||||||
|
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };
|
||||||
|
// Use binary integer part of the sines of integers (in radians) as constants//
|
||||||
|
// Initialize variables:
|
||||||
|
U32 md5_k[64] = {
|
||||||
|
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
|
||||||
|
0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
|
||||||
|
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
|
||||||
|
0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
||||||
|
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
|
||||||
|
0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
|
||||||
|
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
|
||||||
|
0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
||||||
|
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
|
||||||
|
0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
|
||||||
|
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
|
||||||
|
};
|
||||||
|
|
||||||
|
// leftrotate function
|
||||||
|
U32 LEFTROTATE(U32 x, U32 c) { return (((x) << (c)) | ((x) >> (32 - (c)))); }
|
||||||
|
|
||||||
|
U0 md5(U8* initial_msg, U32 initial_len, U32* md5_h)
|
||||||
|
{
|
||||||
|
|
||||||
|
// These vars will contain the hash
|
||||||
|
U32 md5_h0, md5_h1, md5_h2, md5_h3;
|
||||||
|
|
||||||
|
// Message (to prepare)
|
||||||
|
U8* msg = NULL;
|
||||||
|
|
||||||
|
// Note: All variables are unsigned 32 bit and wrap modulo 2^32 when
|
||||||
|
// calculating
|
||||||
|
|
||||||
|
// r specifies the per-round shift amounts
|
||||||
|
|
||||||
|
md5_h0 = 0x67452301;
|
||||||
|
md5_h1 = 0xefcdab89;
|
||||||
|
md5_h2 = 0x98badcfe;
|
||||||
|
md5_h3 = 0x10325476;
|
||||||
|
|
||||||
|
// Pre-processing: adding a single 1 bit
|
||||||
|
// append "1" bit to message
|
||||||
|
/* Notice: the input bytes are considered as bits strings,
|
||||||
|
where the first bit is the most significant bit of the byte.[37] */
|
||||||
|
|
||||||
|
// Pre-processing: padding with zeros
|
||||||
|
// append "0" bit until message length in bit ≡ 448 (mod 512)
|
||||||
|
// append length mod (2 pow 64) to message
|
||||||
|
|
||||||
|
U32 new_len;
|
||||||
|
for (new_len = initial_len * 8 + 1; new_len % 512 != 448; new_len++)
|
||||||
|
;
|
||||||
|
new_len /= 8;
|
||||||
|
|
||||||
|
msg = CAlloc(new_len + 64, adam_task); // also appends "0" bits
|
||||||
|
// (we alloc also 64 extra bytes...)
|
||||||
|
MemCpy(msg, initial_msg, initial_len);
|
||||||
|
msg[initial_len] = 128; // write the "1" bit
|
||||||
|
|
||||||
|
U32 bits_len = 8 * initial_len; // note, we append the len
|
||||||
|
MemCpy(msg + new_len, &bits_len, 4); // in bits at the end of the buffer
|
||||||
|
|
||||||
|
// Process the message in successive 512-bit chunks:
|
||||||
|
// for each 512-bit chunk of message:
|
||||||
|
U32 offset;
|
||||||
|
for (offset = 0; offset < new_len; offset += (512 / 8)) {
|
||||||
|
|
||||||
|
// break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15
|
||||||
|
U32* w = (msg + offset)(U32*);
|
||||||
|
|
||||||
|
// Initialize hash value for this chunk:
|
||||||
|
U32 a = md5_h0;
|
||||||
|
U32 b = md5_h1;
|
||||||
|
U32 c = md5_h2;
|
||||||
|
U32 d = md5_h3;
|
||||||
|
|
||||||
|
// Main loop:
|
||||||
|
U32 i;
|
||||||
|
for (i = 0; i < 64; i++) {
|
||||||
|
|
||||||
|
U32 f, g;
|
||||||
|
|
||||||
|
if (i < 16) {
|
||||||
|
f = (b & c) | ((~b) & d);
|
||||||
|
g = i;
|
||||||
|
} else if (i < 32) {
|
||||||
|
f = (d & b) | ((~d) & c);
|
||||||
|
g = (5 * i + 1) % 16;
|
||||||
|
} else if (i < 48) {
|
||||||
|
f = b ^ c ^ d;
|
||||||
|
g = (3 * i + 5) % 16;
|
||||||
|
} else {
|
||||||
|
f = c ^ (b | (~d));
|
||||||
|
g = (7 * i) % 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 temp = d;
|
||||||
|
d = c;
|
||||||
|
c = b;
|
||||||
|
// printf("rotateLeft(%x + %x + %x + %x, %d)\n", a, f, k[i], w[g], r[i]);
|
||||||
|
b = b + LEFTROTATE((a + f + md5_k[i] + w[g]), md5_r[i]);
|
||||||
|
a = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this chunk's hash to result so far:
|
||||||
|
|
||||||
|
md5_h0 += a;
|
||||||
|
md5_h1 += b;
|
||||||
|
md5_h2 += c;
|
||||||
|
md5_h3 += d;
|
||||||
|
}
|
||||||
|
|
||||||
|
md5_h[0] = md5_h0;
|
||||||
|
md5_h[1] = md5_h1;
|
||||||
|
md5_h[2] = md5_h2;
|
||||||
|
md5_h[3] = md5_h3;
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
Free(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* md5_string(U8* buf, I64 size)
|
||||||
|
{
|
||||||
|
U32 md5_h[4];
|
||||||
|
md5(buf, size, &md5_h[0]);
|
||||||
|
U8* str = CAlloc(33, adam_task);
|
||||||
|
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[0].u8[0],
|
||||||
|
md5_h[0].u8[1], md5_h[0].u8[2], md5_h[0].u8[3]);
|
||||||
|
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[1].u8[0],
|
||||||
|
md5_h[1].u8[1], md5_h[1].u8[2], md5_h[1].u8[3]);
|
||||||
|
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[2].u8[0],
|
||||||
|
md5_h[2].u8[1], md5_h[2].u8[2], md5_h[2].u8[3]);
|
||||||
|
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[3].u8[0],
|
||||||
|
md5_h[3].u8[1], md5_h[3].u8[2], md5_h[3].u8[3]);
|
||||||
|
return str;
|
||||||
|
}
|
32
System/Api/NetInfo.HC
Normal file
32
System/Api/NetInfo.HC
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#define NETINFO_REQUEST_PTR 0x300030
|
||||||
|
|
||||||
|
MemSet(NETINFO_REQUEST_PTR, NULL, sizeof(U64));
|
||||||
|
|
||||||
|
class NetInfoRequest {
|
||||||
|
U64 mac_address;
|
||||||
|
U64 ipv4_address;
|
||||||
|
U64 ipv4_netmask;
|
||||||
|
U64 ipv4_network;
|
||||||
|
U64 ipv4_gateway;
|
||||||
|
U64 dns_server_address;
|
||||||
|
U64 dns_server_port;
|
||||||
|
U64 rx_bytes;
|
||||||
|
U64 rx_frames;
|
||||||
|
U64 tx_bytes;
|
||||||
|
U64 tx_frames;
|
||||||
|
U64 pointer_to_u32;
|
||||||
|
};
|
||||||
|
|
||||||
|
NetInfoRequest* @net_info_request()
|
||||||
|
{
|
||||||
|
U32 res = 0;
|
||||||
|
NetInfoRequest* req = CAlloc(sizeof(NetInfoRequest), Fs->code_heap);
|
||||||
|
req->pointer_to_u32 = &res;
|
||||||
|
U64* request_ptr = NETINFO_REQUEST_PTR;
|
||||||
|
while (*request_ptr)
|
||||||
|
Sleep(1);
|
||||||
|
LXchgU32(request_ptr, req);
|
||||||
|
while (!res)
|
||||||
|
Sleep(1);
|
||||||
|
return req;
|
||||||
|
}
|
210
System/Api/Tcp.HC
Normal file
210
System/Api/Tcp.HC
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
#define TCP_SOCKET_REQUEST_PTR 0x300000
|
||||||
|
#define TCP_BIND_REQUEST_PTR 0x300040
|
||||||
|
#define TCP_ACCEPT_REQUEST_PTR 0x300050
|
||||||
|
|
||||||
|
MemSet(TCP_SOCKET_REQUEST_PTR, NULL, sizeof(U64));
|
||||||
|
|
||||||
|
// TcpSocket states
|
||||||
|
|
||||||
|
#define TCP_SOCKET_STATE_IDLE 0
|
||||||
|
#define TCP_SOCKET_STATE_ESTABLISHED 1
|
||||||
|
#define TCP_SOCKET_STATE_CLOSED 2
|
||||||
|
#define TCP_SOCKET_STATE_CONNECTING 4
|
||||||
|
|
||||||
|
class TcpSocket {
|
||||||
|
U64 remote_addr;
|
||||||
|
U64 remote_port;
|
||||||
|
U64 state;
|
||||||
|
U64 receive_buffer_ptr; // Pointer to receive buffer in physical memory
|
||||||
|
U64 receive_buffer_size;
|
||||||
|
U64 receive_buffer_filled; // Number of bytes Net has put into buffer
|
||||||
|
U64 receive_buffer_kick; // Net sets this to 1 when it has data available for
|
||||||
|
// us, we set back to 0 when ready to receive
|
||||||
|
U64 send_buffer_ptr;
|
||||||
|
U64 send_buffer_size;
|
||||||
|
U64 send_buffer_filled;
|
||||||
|
U64 send_buffer_kick; // We set this to 1 when we have data available to net,
|
||||||
|
// Net sets back to 0 when ready to receive
|
||||||
|
U0(*close)
|
||||||
|
();
|
||||||
|
U64(*receive)
|
||||||
|
(U64 buf, U64 length);
|
||||||
|
U0(*send)
|
||||||
|
(U64 buf, U64 length);
|
||||||
|
};
|
||||||
|
|
||||||
|
class TcpBind {
|
||||||
|
U64 port;
|
||||||
|
U64 function;
|
||||||
|
U64 response_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
U8 @tcp_close_wrapper_function[16]
|
||||||
|
= { 0x55, 0x48, 0x8B, 0xEC, 0x68, 0x78,
|
||||||
|
0x56, 0x34, 0x12, 0xE8, 0x02, 0x6D,
|
||||||
|
0x02, 0x00, 0x5D, 0xC3 };
|
||||||
|
|
||||||
|
U8 @tcp_receive_wrapper_function[32] = {
|
||||||
|
0x55, 0x48, 0x8B, 0xEC, 0x56, 0x57, 0x48, 0x8B, 0x75, 0x18, 0x48,
|
||||||
|
0x8B, 0x7D, 0x10, 0x56, 0x57, 0x68, 0x78, 0x56, 0x34, 0x12, 0xE8,
|
||||||
|
0x5E, 0x62, 0x02, 0x00, 0x5F, 0x5E, 0x5D, 0xC2, 0x10, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
U8 @tcp_send_wrapper_function[32] = {
|
||||||
|
0x55, 0x48, 0x8B, 0xEC, 0x56, 0x57, 0x48, 0x8B, 0x75, 0x18, 0x48,
|
||||||
|
0x8B, 0x7D, 0x10, 0x56, 0x57, 0x68, 0x78, 0x56, 0x34, 0x12, 0xE8,
|
||||||
|
0x5E, 0x62, 0x02, 0x00, 0x5F, 0x5E, 0x5D, 0xC2, 0x10, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
U0 @tcp_socket_send(TcpSocket* s, U64 buf, U64 length)
|
||||||
|
{
|
||||||
|
while (s->send_buffer_kick)
|
||||||
|
Sleep(1);
|
||||||
|
U64 pos = 0;
|
||||||
|
U64 bytes_to_send = 0;
|
||||||
|
while (pos < length) {
|
||||||
|
if ((length - pos) > s->send_buffer_size)
|
||||||
|
bytes_to_send = s->send_buffer_size;
|
||||||
|
else
|
||||||
|
bytes_to_send = length - pos;
|
||||||
|
MemCpy(s->send_buffer_ptr, buf + pos, bytes_to_send);
|
||||||
|
s->send_buffer_filled = bytes_to_send;
|
||||||
|
s->send_buffer_kick = 1;
|
||||||
|
pos += bytes_to_send;
|
||||||
|
while (s->send_buffer_kick)
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 @tcp_socket_receive(TcpSocket* s, U64 buf, U64 size)
|
||||||
|
{
|
||||||
|
s->receive_buffer_size = size;
|
||||||
|
s->receive_buffer_kick = 0;
|
||||||
|
while (!s->receive_buffer_kick) {
|
||||||
|
if (s->state == TCP_SOCKET_STATE_CLOSED)
|
||||||
|
return NULL;
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
U64 bytes_received = s->receive_buffer_filled;
|
||||||
|
if (bytes_received > 0) {
|
||||||
|
MemCpy(buf, s->receive_buffer_ptr, bytes_received);
|
||||||
|
}
|
||||||
|
return bytes_received;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @tcp_wait_for_connection_established(TcpSocket* s)
|
||||||
|
{
|
||||||
|
while (s->state != TCP_SOCKET_STATE_ESTABLISHED)
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @tcp_socket_close(TcpSocket* s)
|
||||||
|
{
|
||||||
|
if (s->close)
|
||||||
|
Free(s->close);
|
||||||
|
if (s->receive)
|
||||||
|
Free(s->receive);
|
||||||
|
if (s->send)
|
||||||
|
Free(s->send);
|
||||||
|
s->state = TCP_SOCKET_STATE_CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
TcpSocket* @tcp_socket_create(U8* host, U64 port)
|
||||||
|
{
|
||||||
|
U64 addr = @dns_query(host);
|
||||||
|
TcpSocket* s = CAlloc(sizeof(TcpSocket), adam_task->code_heap);
|
||||||
|
s->remote_addr = addr;
|
||||||
|
s->remote_port = port;
|
||||||
|
|
||||||
|
U64 a;
|
||||||
|
|
||||||
|
s->close = MAlloc(16, adam_task->code_heap);
|
||||||
|
MemCpy(s->close, @tcp_close_wrapper_function, 16);
|
||||||
|
a = s->close;
|
||||||
|
a += 0x05;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->close;
|
||||||
|
a += 0x09;
|
||||||
|
@patch_call_rel32(a, &@tcp_socket_close);
|
||||||
|
|
||||||
|
s->receive = MAlloc(25, adam_task->code_heap);
|
||||||
|
MemCpy(s->receive, @tcp_receive_wrapper_function, 32);
|
||||||
|
a = s->receive;
|
||||||
|
a += 0x11;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->receive;
|
||||||
|
a += 0x15;
|
||||||
|
@patch_call_rel32(a, &@tcp_socket_receive);
|
||||||
|
|
||||||
|
s->send = MAlloc(32, adam_task->code_heap);
|
||||||
|
MemCpy(s->send, @tcp_send_wrapper_function, 32);
|
||||||
|
a = s->send;
|
||||||
|
a += 0x11;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->send;
|
||||||
|
a += 0x15;
|
||||||
|
@patch_call_rel32(a, &@tcp_socket_send);
|
||||||
|
|
||||||
|
U64* request_ptr = TCP_SOCKET_REQUEST_PTR;
|
||||||
|
while (*request_ptr)
|
||||||
|
Sleep(1);
|
||||||
|
LXchgU32(request_ptr, s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 @tcp_socket_bind(U64 port, U64 function)
|
||||||
|
{
|
||||||
|
if (!port || !function)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
TcpBind* b = CAlloc(sizeof(TcpBind), adam_task->code_heap);
|
||||||
|
b->port = port;
|
||||||
|
b->function = function; // U0 my_spawn_wrapper_function(TcpSocket* s)
|
||||||
|
|
||||||
|
U64* request_ptr = TCP_BIND_REQUEST_PTR;
|
||||||
|
while (*request_ptr)
|
||||||
|
Sleep(1);
|
||||||
|
LXchgU32(request_ptr, b);
|
||||||
|
while (*request_ptr)
|
||||||
|
Sleep(1);
|
||||||
|
U64 res = b->response_code;
|
||||||
|
Free(b);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
TcpSocket* @tcp_socket_accept(TcpSocket* s)
|
||||||
|
{
|
||||||
|
if (!s || !s->remote_addr || !s->remote_port)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
U64 a;
|
||||||
|
|
||||||
|
s->close = MAlloc(16, adam_task->code_heap);
|
||||||
|
MemCpy(s->close, @tcp_close_wrapper_function, 16);
|
||||||
|
a = s->close;
|
||||||
|
a += 0x05;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->close;
|
||||||
|
a += 0x09;
|
||||||
|
@patch_call_rel32(a, &@tcp_socket_close);
|
||||||
|
|
||||||
|
s->receive = MAlloc(25, adam_task->code_heap);
|
||||||
|
MemCpy(s->receive, @tcp_receive_wrapper_function, 32);
|
||||||
|
a = s->receive;
|
||||||
|
a += 0x11;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->receive;
|
||||||
|
a += 0x15;
|
||||||
|
@patch_call_rel32(a, &@tcp_socket_receive);
|
||||||
|
|
||||||
|
s->send = MAlloc(32, adam_task->code_heap);
|
||||||
|
MemCpy(s->send, @tcp_send_wrapper_function, 32);
|
||||||
|
a = s->send;
|
||||||
|
a += 0x11;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->send;
|
||||||
|
a += 0x15;
|
||||||
|
@patch_call_rel32(a, &@tcp_socket_send);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
99
System/Api/Tls.HC
Normal file
99
System/Api/Tls.HC
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
#define TLS_CONNECT_TASK_STACK_SIZE 524288
|
||||||
|
#define TLS_CLIENT_MESSAGE_BUFFER_SIZE 0xFFFF
|
||||||
|
|
||||||
|
class TlsSocket : TcpSocket {
|
||||||
|
U64 ctx;
|
||||||
|
U8 client_message[TLS_CLIENT_MESSAGE_BUFFER_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
U0 @tls_send_pending(TlsSocket* s)
|
||||||
|
{
|
||||||
|
U32 out_buffer_len = 0;
|
||||||
|
U8* out_buffer = @tls_get_write_buffer(s->ctx, &out_buffer_len);
|
||||||
|
if (out_buffer && out_buffer_len) {
|
||||||
|
@tcp_socket_send(s, out_buffer, out_buffer_len);
|
||||||
|
@tls_buffer_clear(s->ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @tls_socket_send(TlsSocket* s, U64 buf, U64 size)
|
||||||
|
{
|
||||||
|
@tls_write(s->ctx, buf, size);
|
||||||
|
@tls_send_pending(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 @tls_socket_receive(TlsSocket* s, U8* buf, I64 size)
|
||||||
|
{
|
||||||
|
I64 len = @tcp_socket_receive(s, s->client_message, TLS_CLIENT_MESSAGE_BUFFER_SIZE);
|
||||||
|
if (len) {
|
||||||
|
@tls_consume_stream(s->ctx, s->client_message, len, NULL);
|
||||||
|
@tls_send_pending(s);
|
||||||
|
}
|
||||||
|
return @tls_read(s->ctx, buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @tls12_connect(TlsSocket* s)
|
||||||
|
{
|
||||||
|
I64 len;
|
||||||
|
@tls_client_connect(s->ctx);
|
||||||
|
@tls_send_pending(s);
|
||||||
|
while (!@tls_established(s->ctx)) {
|
||||||
|
len = @tcp_socket_receive(s, &s->client_message, TLS_CLIENT_MESSAGE_BUFFER_SIZE);
|
||||||
|
if (len) {
|
||||||
|
@tls_consume_stream(s->ctx, &s->client_message, len, NULL);
|
||||||
|
@tls_send_pending(s);
|
||||||
|
}
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TlsSocket* @tls_socket_create(U8* server_name, U64 port = 443)
|
||||||
|
{
|
||||||
|
U64 addr = @dns_query(server_name);
|
||||||
|
TlsSocket* s = CAlloc(sizeof(TlsSocket), adam_task->code_heap);
|
||||||
|
s->remote_addr = addr;
|
||||||
|
s->remote_port = port;
|
||||||
|
|
||||||
|
U64 a;
|
||||||
|
|
||||||
|
s->close = MAlloc(16, adam_task->code_heap);
|
||||||
|
MemCpy(s->close, @tcp_close_wrapper_function, 16);
|
||||||
|
a = s->close;
|
||||||
|
a += 0x05;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->close;
|
||||||
|
a += 0x09;
|
||||||
|
@patch_call_rel32(a, &@tcp_socket_close);
|
||||||
|
|
||||||
|
s->receive = MAlloc(25, adam_task->code_heap);
|
||||||
|
MemCpy(s->receive, @tcp_receive_wrapper_function, 32);
|
||||||
|
a = s->receive;
|
||||||
|
a += 0x11;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->receive;
|
||||||
|
a += 0x15;
|
||||||
|
@patch_call_rel32(a, &@tls_socket_receive);
|
||||||
|
|
||||||
|
s->send = MAlloc(32, adam_task->code_heap);
|
||||||
|
MemCpy(s->send, @tcp_send_wrapper_function, 32);
|
||||||
|
a = s->send;
|
||||||
|
a += 0x11;
|
||||||
|
MemSetU32(a, s, 1);
|
||||||
|
a = s->send;
|
||||||
|
a += 0x15;
|
||||||
|
@patch_call_rel32(a, &@tls_socket_send);
|
||||||
|
|
||||||
|
U64* request_ptr = TCP_SOCKET_REQUEST_PTR;
|
||||||
|
while (*request_ptr)
|
||||||
|
Sleep(1);
|
||||||
|
LXchgU32(request_ptr, s);
|
||||||
|
|
||||||
|
while (s->state != TCP_SOCKET_STATE_ESTABLISHED)
|
||||||
|
Sleep(1);
|
||||||
|
|
||||||
|
s->ctx = @tls_create_context(0, TLS_V12);
|
||||||
|
@tls_sni_set(s->ctx, StrNew(server_name, adam_task->code_heap));
|
||||||
|
Spawn(&@tls12_connect, s, , , , TLS_CONNECT_TASK_STACK_SIZE);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
10
System/Config/Net.json
Normal file
10
System/Config/Net.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"tcpip.ipv4_address": "10.20.0.10",
|
||||||
|
"tcpip.ipv4_netmask": "255.255.255.0",
|
||||||
|
"tcpip.ipv4_network": "10.20.0.0",
|
||||||
|
"tcpip.ipv4_gateway": "10.20.0.254",
|
||||||
|
"tcpip.ipv4_dns_server_address": "8.8.8.8",
|
||||||
|
"tcpip.ipv4_dns_server_port": "53",
|
||||||
|
"tcpip.mss_size": "1360",
|
||||||
|
"eof": "eof"
|
||||||
|
}
|
473
System/Drivers/Virtio-blk.HC
Normal file
473
System/Drivers/Virtio-blk.HC
Normal file
|
@ -0,0 +1,473 @@
|
||||||
|
// Virtio.HC
|
||||||
|
|
||||||
|
//
|
||||||
|
// PCI virtio I/O registers.
|
||||||
|
//
|
||||||
|
|
||||||
|
#define VIRTIO_PCI_HOST_FEATURES 0 // Features supported by the host
|
||||||
|
#define VIRTIO_PCI_GUEST_FEATURES 4 // Features activated by the guest
|
||||||
|
#define VIRTIO_PCI_QUEUE_PFN 8 // PFN for the currently selected queue
|
||||||
|
#define VIRTIO_PCI_QUEUE_SIZE 12 // Queue size for the currently selected queue
|
||||||
|
#define VIRTIO_PCI_QUEUE_SEL 14 // Queue selector
|
||||||
|
#define VIRTIO_PCI_QUEUE_NOTIFY 16 // Queue notifier
|
||||||
|
#define VIRTIO_PCI_STATUS 18 // Device status register
|
||||||
|
#define VIRTIO_PCI_ISR 19 // Interrupt status register
|
||||||
|
#define VIRTIO_PCI_CONFIG 20 // Configuration data block
|
||||||
|
|
||||||
|
//
|
||||||
|
// PCI virtio status register bits
|
||||||
|
//
|
||||||
|
|
||||||
|
#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
|
||||||
|
#define VIRTIO_CONFIG_S_DRIVER 2
|
||||||
|
#define VIRTIO_CONFIG_S_DRIVER_OK 4
|
||||||
|
#define VIRTIO_CONFIG_S_FAILED 0x80
|
||||||
|
|
||||||
|
//
|
||||||
|
// Ring descriptor flags
|
||||||
|
//
|
||||||
|
|
||||||
|
#define VRING_DESC_F_NEXT 1 // Buffer continues via the next field
|
||||||
|
#define VRING_DESC_F_WRITE 2 // Buffer is write-only (otherwise read-only)
|
||||||
|
#define VRING_DESC_F_INDIRECT 4 // Buffer contains a list of buffer descriptors
|
||||||
|
|
||||||
|
class @virtio_queue_buf
|
||||||
|
{
|
||||||
|
U64 address;
|
||||||
|
U32 length;
|
||||||
|
U16 flags;
|
||||||
|
U16 next;
|
||||||
|
};
|
||||||
|
class @virtio_avail
|
||||||
|
{
|
||||||
|
U16 flags;
|
||||||
|
U16 index;
|
||||||
|
U16 ring[256];
|
||||||
|
U16 int_index;
|
||||||
|
};
|
||||||
|
class @virtio_used_item
|
||||||
|
{
|
||||||
|
U32 index;
|
||||||
|
U32 length;
|
||||||
|
};
|
||||||
|
class @virtio_used
|
||||||
|
{
|
||||||
|
U16 flags;
|
||||||
|
U16 index;
|
||||||
|
@virtio_used_item ring[256];
|
||||||
|
U16 int_index;
|
||||||
|
};
|
||||||
|
class @virtio_queue
|
||||||
|
{
|
||||||
|
@virtio_queue_buf buffers[256];
|
||||||
|
@virtio_avail available;
|
||||||
|
U8 padding[3578];
|
||||||
|
@virtio_used used;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @virtio_avail_buf
|
||||||
|
{
|
||||||
|
U32 index;
|
||||||
|
U64 address;
|
||||||
|
U32 length;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @virtio_buf_info
|
||||||
|
{
|
||||||
|
U8* buffer;
|
||||||
|
U64 size;
|
||||||
|
U8 flags;
|
||||||
|
|
||||||
|
// If the user wants to keep same buffer as passed in this struct, use "true".
|
||||||
|
// otherwise, the supplied buffer will be copied in the queues' buffer
|
||||||
|
Bool copy;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Virtio-blk.HC
|
||||||
|
|
||||||
|
#define BDT_VIRTIO_BLK 10
|
||||||
|
|
||||||
|
#define VIRTIO_BLK_T_IN 0
|
||||||
|
#define VIRTIO_BLK_T_OUT 1
|
||||||
|
#define VIRTIO_BLK_T_FLUSH 4
|
||||||
|
|
||||||
|
#define VIRTIO_BLK_MAX_BLK 0x400000 // Limit blkdev to 2G max, set to NULL to use entire disk (not recommended for RedSea)
|
||||||
|
|
||||||
|
class @virtio_blk
|
||||||
|
{
|
||||||
|
U16 port;
|
||||||
|
U32 blks;
|
||||||
|
@virtio_queue* vq;
|
||||||
|
I64 vq_size;
|
||||||
|
I64 vq_index;
|
||||||
|
U8 status;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @virtio_blk_request
|
||||||
|
{
|
||||||
|
U32 type;
|
||||||
|
U32 priority;
|
||||||
|
U64 sector;
|
||||||
|
};
|
||||||
|
|
||||||
|
@virtio_blk virtio_blk;
|
||||||
|
MemSet(&virtio_blk, 0, sizeof(@virtio_blk));
|
||||||
|
|
||||||
|
I64 VirtioBlkInit()
|
||||||
|
{
|
||||||
|
I64 j;
|
||||||
|
|
||||||
|
// Scan for device
|
||||||
|
j = PCIClassFind(0x010000, 0);
|
||||||
|
if (j < 0) {
|
||||||
|
"\n[virtio-blk] No device found\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
virtio_blk.port = PCIReadU32(j.u8[2],
|
||||||
|
j.u8[1], j.u8[0], 0x10)
|
||||||
|
& 0xFFFFFFFC;
|
||||||
|
|
||||||
|
virtio_blk.blks = InU32(virtio_blk.port + VIRTIO_PCI_CONFIG);
|
||||||
|
|
||||||
|
// Reset Device
|
||||||
|
OutU8(virtio_blk.port + VIRTIO_PCI_STATUS, 0);
|
||||||
|
|
||||||
|
// Found Driver
|
||||||
|
OutU8(virtio_blk.port + VIRTIO_PCI_STATUS, InU8(virtio_blk.port + VIRTIO_PCI_STATUS) | VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER);
|
||||||
|
|
||||||
|
// Set up virt queue
|
||||||
|
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_SEL, 0);
|
||||||
|
virtio_blk.vq_size = InU16(virtio_blk.port + VIRTIO_PCI_QUEUE_SIZE); // 256
|
||||||
|
virtio_blk.vq = CAllocAligned(sizeof(@virtio_queue), 4096, adam_task->code_heap);
|
||||||
|
OutU32(virtio_blk.port + VIRTIO_PCI_QUEUE_PFN, virtio_blk.vq / 4096);
|
||||||
|
|
||||||
|
// Init OK
|
||||||
|
OutU8(virtio_blk.port + VIRTIO_PCI_STATUS, InU8(virtio_blk.port + VIRTIO_PCI_STATUS) | VIRTIO_CONFIG_S_DRIVER_OK);
|
||||||
|
virtio_blk.vq_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DskVIO.HC
|
||||||
|
|
||||||
|
U0 VIOFlush()
|
||||||
|
{
|
||||||
|
I64 j;
|
||||||
|
I64 vq_idx;
|
||||||
|
@virtio_blk_request* brq = CAlloc(sizeof(@virtio_blk_request), adam_task);
|
||||||
|
brq->type = VIRTIO_BLK_T_FLUSH;
|
||||||
|
brq->sector = NULL;
|
||||||
|
vq_idx = virtio_blk.vq->available.index % 256;
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].address = brq;
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].length = sizeof(@virtio_blk_request);
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].flags = VRING_DESC_F_NEXT;
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].next = (virtio_blk.vq_index + 1) % 256;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].address = &virtio_blk.status;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].length = 1;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].flags = VRING_DESC_F_WRITE;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].next = 0;
|
||||||
|
virtio_blk.vq->available.ring[vq_idx] = virtio_blk.vq_index % 256;
|
||||||
|
virtio_blk.vq_index += 2;
|
||||||
|
j = virtio_blk.vq->used.index;
|
||||||
|
virtio_blk.vq->available.index++;
|
||||||
|
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
|
||||||
|
while (j == virtio_blk.vq->used.index) {
|
||||||
|
Yield;
|
||||||
|
}
|
||||||
|
Free(brq);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VIORBlks(CDrv* dv, U8* buf, I64 blk, I64 cnt)
|
||||||
|
{
|
||||||
|
no_warn dv;
|
||||||
|
I64 i, j;
|
||||||
|
I64 vq_idx;
|
||||||
|
U64 addr;
|
||||||
|
@virtio_blk_request* brq = CAlloc(sizeof(@virtio_blk_request), adam_task);
|
||||||
|
for (i = 0; i < cnt; i++) {
|
||||||
|
brq->type = VIRTIO_BLK_T_IN;
|
||||||
|
brq->sector = blk + i;
|
||||||
|
vq_idx = virtio_blk.vq->available.index % 256;
|
||||||
|
addr = buf + (BLK_SIZE * i);
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].address = brq;
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].length = sizeof(@virtio_blk_request);
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].flags = VRING_DESC_F_NEXT;
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].next = (virtio_blk.vq_index + 1) % 256;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].address = addr;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].length = BLK_SIZE;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].flags = VRING_DESC_F_WRITE | VRING_DESC_F_NEXT;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].next = (virtio_blk.vq_index + 2) % 256;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].address = &virtio_blk.status;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].length = 1;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].flags = VRING_DESC_F_WRITE;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].next = 0;
|
||||||
|
virtio_blk.vq->available.ring[vq_idx] = virtio_blk.vq_index % 256;
|
||||||
|
virtio_blk.vq_index += 3;
|
||||||
|
j = virtio_blk.vq->used.index;
|
||||||
|
virtio_blk.vq->available.index++;
|
||||||
|
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
|
||||||
|
while (j == virtio_blk.vq->used.index) {
|
||||||
|
Yield;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Free(brq);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool VIOWBlks(CDrv* dv, U8* buf, I64 blk, I64 cnt)
|
||||||
|
{
|
||||||
|
no_warn dv;
|
||||||
|
I64 i, j;
|
||||||
|
I64 vq_idx;
|
||||||
|
U64 addr;
|
||||||
|
@virtio_blk_request* brq = CAlloc(sizeof(@virtio_blk_request), adam_task);
|
||||||
|
for (i = 0; i < cnt; i++) {
|
||||||
|
brq->type = VIRTIO_BLK_T_OUT;
|
||||||
|
brq->sector = blk + i;
|
||||||
|
vq_idx = virtio_blk.vq->available.index % 256;
|
||||||
|
addr = buf + (BLK_SIZE * i);
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].address = brq;
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].length = sizeof(@virtio_blk_request);
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].flags = VRING_DESC_F_NEXT;
|
||||||
|
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].next = (virtio_blk.vq_index + 1) % 256;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].address = addr;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].length = BLK_SIZE;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].flags = VRING_DESC_F_NEXT;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].next = (virtio_blk.vq_index + 2) % 256;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].address = &virtio_blk.status;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].length = 1;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].flags = VRING_DESC_F_WRITE;
|
||||||
|
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].next = 0;
|
||||||
|
virtio_blk.vq->available.ring[vq_idx] = virtio_blk.vq_index % 256;
|
||||||
|
virtio_blk.vq_index += 3;
|
||||||
|
j = virtio_blk.vq->used.index;
|
||||||
|
virtio_blk.vq->available.index++;
|
||||||
|
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
|
||||||
|
while (j == virtio_blk.vq->used.index) {
|
||||||
|
Yield;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Free(brq);
|
||||||
|
VIOFlush;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 RedSeaTryInit(CDrv* dv)
|
||||||
|
{
|
||||||
|
CRedSeaBoot br;
|
||||||
|
Bool unlock;
|
||||||
|
try {
|
||||||
|
unlock = DrvLock(dv);
|
||||||
|
BlkRead(dv, &br, dv->drv_offset, 1);
|
||||||
|
if (br.signature != MBR_PT_REDSEA || br.signature2 != 0xAA55)
|
||||||
|
return;
|
||||||
|
dv->fs_type = FSt_REDSEA;
|
||||||
|
CallExtStr("RedSeaFreeFreeLst", dv);
|
||||||
|
dv->spc = 1;
|
||||||
|
dv->size = br.sects;
|
||||||
|
dv->data_area = dv->drv_offset + br.bitmap_sects;
|
||||||
|
dv->root_clus = br.root_clus;
|
||||||
|
dv->fat1 = dv->fat2 = dv->drv_offset + 1;
|
||||||
|
CallExtStr("DrvFATBlkAlloc", dv);
|
||||||
|
if (unlock)
|
||||||
|
DrvUnlock(dv);
|
||||||
|
} catch if (unlock)
|
||||||
|
DrvUnlock(dv);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 MountVirtioBlk()
|
||||||
|
{ // Mount Virtio-blk device
|
||||||
|
CDrv* dv = DrvMakeFreeSlot(DrvNextFreeLet('A'));
|
||||||
|
CBlkDev* bd = BlkDevNextFreeSlot(dv->drv_let, BDT_RAM);
|
||||||
|
CRedSeaBoot* bs = CAlloc(BLK_SIZE, adam_task);
|
||||||
|
bd->max_blk = 512;
|
||||||
|
BlkDevAdd(bd, , TRUE, TRUE);
|
||||||
|
bd->type = BDT_VIRTIO_BLK;
|
||||||
|
if (VIRTIO_BLK_MAX_BLK) {
|
||||||
|
bd->max_blk = Min(VIRTIO_BLK_MAX_BLK, virtio_blk.blks);
|
||||||
|
} else {
|
||||||
|
bd->max_blk = virtio_blk.blks;
|
||||||
|
}
|
||||||
|
Free(bd->RAM_dsk);
|
||||||
|
dv->size = bd->max_blk + 1 - bd->drv_offset;
|
||||||
|
VIORBlks(dv, bs, 0, 1);
|
||||||
|
dv->root_clus = bs->root_clus;
|
||||||
|
dv->data_area = bs->bitmap_sects;
|
||||||
|
dv->next_free = NULL;
|
||||||
|
dv->last_free = NULL;
|
||||||
|
Free(bs);
|
||||||
|
RedSeaTryInit(dv);
|
||||||
|
return dv->drv_let;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DskBlk2.HC
|
||||||
|
|
||||||
|
Bool BlkRead2(CDrv* dv, U8* buf, I64 blk, I64 cnt)
|
||||||
|
{ // Read blk cnt from Drv to buf.
|
||||||
|
Bool res = TRUE, unlock;
|
||||||
|
CBlkDev* bd = dv->bd;
|
||||||
|
if (cnt <= 0)
|
||||||
|
return TRUE;
|
||||||
|
DrvChk(dv);
|
||||||
|
try {
|
||||||
|
unlock = DrvLock(dv);
|
||||||
|
CallExtStr("BlkDevInit", bd);
|
||||||
|
if (dv->drv_offset && blk < dv->drv_offset || blk + cnt > dv->drv_offset + dv->size)
|
||||||
|
throw('Drv');
|
||||||
|
if (bd->flags & BDF_READ_CACHE)
|
||||||
|
CallExtStr("RCache", dv, &buf, &blk, &cnt);
|
||||||
|
if (cnt > 0) {
|
||||||
|
switch (bd->type) {
|
||||||
|
case BDT_RAM:
|
||||||
|
MemCpy(buf, bd->RAM_dsk + blk << BLK_SIZE_BITS, cnt << BLK_SIZE_BITS);
|
||||||
|
break;
|
||||||
|
case BDT_ISO_FILE_READ:
|
||||||
|
case BDT_ISO_FILE_WRITE:
|
||||||
|
FBlkRead(bd->file_dsk, buf, blk, cnt);
|
||||||
|
break;
|
||||||
|
case BDT_ATA:
|
||||||
|
case BDT_ATAPI:
|
||||||
|
res = CallExtStr("ATARBlks", dv, buf, blk, cnt);
|
||||||
|
break;
|
||||||
|
case BDT_VIRTIO_BLK:
|
||||||
|
res = VIORBlks(dv, buf, blk, cnt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bd->last_time = tS;
|
||||||
|
if (bd->flags & BDF_READ_CACHE)
|
||||||
|
CallExtStr("DskCacheAdd", dv, buf, blk, cnt);
|
||||||
|
}
|
||||||
|
if (unlock)
|
||||||
|
DrvUnlock(dv);
|
||||||
|
} catch if (unlock)
|
||||||
|
DrvUnlock(dv);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool BlkWrite2(CDrv* dv, U8* buf, I64 blk, I64 cnt)
|
||||||
|
{ // Write blk cnt from buf to Drv.
|
||||||
|
Bool res = TRUE, unlock;
|
||||||
|
CBlkDev* bd = dv->bd;
|
||||||
|
if (cnt <= 0)
|
||||||
|
return TRUE;
|
||||||
|
DrvChk(dv);
|
||||||
|
try {
|
||||||
|
unlock = DrvLock(dv);
|
||||||
|
CallExtStr("BlkDevInit", bd);
|
||||||
|
if (bd->flags & BDF_READ_ONLY && !(bd->flags & BDF_READ_ONLY_OVERRIDE))
|
||||||
|
throw('BlkDev');
|
||||||
|
if (dv->drv_offset && blk < dv->drv_offset || blk + cnt > dv->drv_offset + dv->size)
|
||||||
|
throw('Drv');
|
||||||
|
if (cnt > 0) {
|
||||||
|
switch (bd->type) {
|
||||||
|
case BDT_RAM:
|
||||||
|
MemCpy(bd->RAM_dsk + blk << BLK_SIZE_BITS, buf, cnt << BLK_SIZE_BITS);
|
||||||
|
break;
|
||||||
|
case BDT_ISO_FILE_READ:
|
||||||
|
case BDT_ISO_FILE_WRITE:
|
||||||
|
FBlkWrite(bd->file_dsk, buf, blk, cnt);
|
||||||
|
break;
|
||||||
|
case BDT_ATA:
|
||||||
|
case BDT_ATAPI:
|
||||||
|
res = CallExtStr("ATAWBlks", dv, buf, blk, cnt);
|
||||||
|
break;
|
||||||
|
case BDT_VIRTIO_BLK:
|
||||||
|
res = VIOWBlks(dv, buf, blk, cnt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
bd->last_time = tS;
|
||||||
|
if (bd->flags & BDF_READ_CACHE)
|
||||||
|
CallExtStr("DskCacheAdd", dv, buf, blk, cnt);
|
||||||
|
}
|
||||||
|
if (unlock)
|
||||||
|
DrvUnlock(dv);
|
||||||
|
} catch if (unlock)
|
||||||
|
DrvUnlock(dv);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@patch_jmp_rel32(&BlkRead, &BlkRead2);
|
||||||
|
@patch_jmp_rel32(&BlkWrite, &BlkWrite2);
|
||||||
|
|
||||||
|
// DskBlkDev2.HC
|
||||||
|
|
||||||
|
CBlkDev* BlkDevChk2(CBlkDev* bd, Bool except = TRUE)
|
||||||
|
{ // Check for valid BlkDev. Throw exception.
|
||||||
|
if (bd->type == BDT_VIRTIO_BLK)
|
||||||
|
return bd;
|
||||||
|
if (!bd || bd->bd_signature != BD_SIGNATURE_VAL || !(BDT_NULL < bd->type < BDT_TYPES_NUM)) {
|
||||||
|
if (except)
|
||||||
|
throw('BlkDev');
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
} else
|
||||||
|
return bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
@patch_jmp_rel32(&BlkDevChk, &BlkDevChk2);
|
||||||
|
|
||||||
|
// DskDrv2.HC
|
||||||
|
|
||||||
|
DefineLstLoad("ST_BLKDEV_TYPES2",
|
||||||
|
"NULL\0RAM\0ATA\0FILE_READ\0FILE_WRITE\0ATAPI\0NULL\0NULL\0NULL\0NULL\0VIRTIO\0");
|
||||||
|
|
||||||
|
U8 DrvTextAttrGet2(U8 drv_let = 0)
|
||||||
|
{ // Get color of drive.
|
||||||
|
U8* blkdev_text_attr2 = blkdev_text_attr;
|
||||||
|
U8* drv_text_attr2 = drv_text_attr;
|
||||||
|
I64 dta_size = 3;
|
||||||
|
drv_let = Let2Let(drv_let);
|
||||||
|
if (drv_let == 'A')
|
||||||
|
return BLACK << 4 | WHITE;
|
||||||
|
if ('A' <= drv_let <= 'Z')
|
||||||
|
return blkdev_text_attr2[Let2BlkDevType(drv_let)] << 4 | drv_text_attr2[drv_let % dta_size];
|
||||||
|
else
|
||||||
|
return BLACK << 4 | WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 DrvRep2()
|
||||||
|
{ // Drive report.
|
||||||
|
CDrv* dv;
|
||||||
|
CBlkDev* bd;
|
||||||
|
I64 ch, i, drv_let, attr;
|
||||||
|
U8* st;
|
||||||
|
"\nDefined Drives:\n";
|
||||||
|
for (i = 0, dv = blkdev.drvs; i < DRVS_NUM; i++, dv++) {
|
||||||
|
if (dv->dv_signature == DRV_SIGNATURE_VAL) {
|
||||||
|
bd = dv->bd;
|
||||||
|
drv_let = Drv2Let(dv);
|
||||||
|
if (Bt(&dv->fs_type, FStf_DISABLE))
|
||||||
|
ch = '-';
|
||||||
|
else if (drv_let == blkdev.boot_drv_let)
|
||||||
|
ch = ':';
|
||||||
|
else
|
||||||
|
ch = '+';
|
||||||
|
attr = DrvTextAttrGet(drv_let);
|
||||||
|
"\dFG,%d\d\dBG,%d\d%C %-8Z %-10Z %04X %04X %02X\n",
|
||||||
|
attr & 15, attr >> 4, drv_let, dv->fs_type &FSG_TYPE_MASK, "ST_DRV_TYPES",
|
||||||
|
bd->type, "ST_BLKDEV_TYPES2", bd->base0, bd->base1, bd->unit;
|
||||||
|
if (st = DrvModelNum(drv_let)) {
|
||||||
|
"Model#:%s\n", st;
|
||||||
|
Free(st);
|
||||||
|
}
|
||||||
|
if (st = DrvSerialNum(drv_let)) {
|
||||||
|
"Serial#:%s\n", st;
|
||||||
|
Free(st);
|
||||||
|
}
|
||||||
|
if (bd->type == BDT_ISO_FILE_READ || bd->type == BDT_ISO_FILE_WRITE)
|
||||||
|
"File=\"%s\"\n", bd->file_dsk_name;
|
||||||
|
"%016X-%016X\n\dFG\d\dBG\d", dv->drv_offset, dv->drv_offset + dv->size - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"Home Dir:\"%s\"\n", blkdev.home_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@patch_jmp_rel32(&DrvTextAttrGet, &DrvTextAttrGet2);
|
||||||
|
@patch_jmp_rel32(&DrvRep, &DrvRep2);
|
||||||
|
|
||||||
|
VirtioBlkInit;
|
||||||
|
MountVirtioBlk;
|
||||||
|
|
||||||
|
if (Let2Drv('A', 0) && !Let2Drv('A')->root_clus) {
|
||||||
|
"[virtio-blk] RedSea filesystem not initialized, formatting.\n";
|
||||||
|
Fmt('A', , FALSE, FSt_REDSEA);
|
||||||
|
Cd("M:/System/");
|
||||||
|
}
|
51
System/FFI/Base.HC
Normal file
51
System/FFI/Base.HC
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#define PUSH_SYSV_REGS \
|
||||||
|
asm {PUSH RCX PUSH RDX PUSH RBX PUSH RBP PUSH RSI PUSH RDI PUSH R8 PUSH R9 PUSH \
|
||||||
|
R10 PUSH R11 PUSH R12 PUSH R13 PUSH R14 PUSH R15}
|
||||||
|
#define POP_SYSV_REGS \
|
||||||
|
p0 = p0; \
|
||||||
|
p1 = p1; \
|
||||||
|
p2 = p2; \
|
||||||
|
p3 = p3; \
|
||||||
|
p4 = p4; \
|
||||||
|
p5 = p5; \
|
||||||
|
asm {POP R15 POP R14 POP R13 POP R12 POP R11 POP R10 POP R9 POP R8 POP RDI POP \
|
||||||
|
RSI POP RBP POP RBX POP RDX POP RCX}
|
||||||
|
#define GET_SYSV_ARGS \
|
||||||
|
asm {PUSH R9 PUSH R8 PUSH RCX PUSH RDX PUSH RSI PUSH RDI} \
|
||||||
|
I64 reg RDI p0; \
|
||||||
|
I64 reg RSI p1; \
|
||||||
|
I64 reg RDX p2; \
|
||||||
|
I64 reg RCX p3; \
|
||||||
|
I64 reg R8 p4; \
|
||||||
|
I64 reg R9 p5; \
|
||||||
|
asm {POP RDI POP RSI POP RDX POP RCX POP R8 POP R9}
|
||||||
|
|
||||||
|
#define MOV_ANS_RAX asm { MOV[&ans], RAX }
|
||||||
|
#define MOV_PARAM0_RDI asm {MOV [¶m0], RDI}
|
||||||
|
|
||||||
|
I64 param0;
|
||||||
|
I64 elf_argc;
|
||||||
|
U8** elf_argv;
|
||||||
|
|
||||||
|
asm {
|
||||||
|
_ELF_CALL::
|
||||||
|
PUSH RBP
|
||||||
|
MOV RBP,RSP
|
||||||
|
MOV RAX,U64 SF_ARG1[RBP]
|
||||||
|
MOV RDI,U64 SF_ARG2[RBP]
|
||||||
|
MOV RSI,U64 SF_ARG3[RBP]
|
||||||
|
TEST RAX,RAX
|
||||||
|
JZ @@05
|
||||||
|
CALL RAX
|
||||||
|
@@05: POP RBP
|
||||||
|
RET1 8
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _main()
|
||||||
|
{
|
||||||
|
MOV_PARAM0_RDI
|
||||||
|
CallInd(_ELF_CALL, param0, elf_argc, elf_argv);
|
||||||
|
UserTaskCont;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _exit() { UserTaskCont; }
|
301
System/FFI/ELF64.HC
Normal file
301
System/FFI/ELF64.HC
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
#define EI_NIDENT 16
|
||||||
|
#define EM_X86_64 0x3E
|
||||||
|
#define ET_EXEC 2
|
||||||
|
#define ET_DYN 3
|
||||||
|
|
||||||
|
U0 @elf64_debug_print(U8 fmt, ...)
|
||||||
|
{
|
||||||
|
// FIXME: Remove unnecessary debug_print statements and PrintErr for errors.
|
||||||
|
no_warn fmt, argc, argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Elf64_Ehdr {
|
||||||
|
U8 e_ident[EI_NIDENT]; /* Magic number and other info */
|
||||||
|
U16 e_type; /* Object file type */
|
||||||
|
U16 e_machine; /* Architecture */
|
||||||
|
U32 e_version; /* Object file version */
|
||||||
|
U64 e_entry; /* Entry point virtual address */
|
||||||
|
U64 e_phoff; /* Program header table file offset */
|
||||||
|
U64 e_shoff; /* Section header table file offset */
|
||||||
|
U32 e_flags; /* Processor-specific flags */
|
||||||
|
U16 e_ehsize; /* ELF header size in bytes */
|
||||||
|
U16 e_phentsize; /* Program header table entry size */
|
||||||
|
U16 e_phnum; /* Program header table entry count */
|
||||||
|
U16 e_shentsize; /* Section header table entry size */
|
||||||
|
U16 e_shnum; /* Section header table entry count */
|
||||||
|
U16 e_shstrndx; /* Section header string table index */
|
||||||
|
};
|
||||||
|
|
||||||
|
class Elf64_Shdr {
|
||||||
|
U32 sh_name; /* Section name (string tbl index) */
|
||||||
|
U32 sh_type; /* Section type */
|
||||||
|
U64 sh_flags; /* Section flags */
|
||||||
|
U64 sh_addr; /* Section virtual addr at execution */
|
||||||
|
U64 sh_offset; /* Section file offset */
|
||||||
|
U64 sh_size; /* Section size in bytes */
|
||||||
|
U32 sh_link; /* Link to another section */
|
||||||
|
U32 sh_info; /* Additional section information */
|
||||||
|
U64 sh_addralign; /* Section alignment */
|
||||||
|
U64 sh_entsize; /* Entry size if section holds table */
|
||||||
|
};
|
||||||
|
|
||||||
|
class Elf64_Sym {
|
||||||
|
U32 st_name; /* Symbol name (string tbl index) */
|
||||||
|
U8 st_info; /* Symbol type and binding */
|
||||||
|
U8 st_other; /* Symbol visibility */
|
||||||
|
U16 st_shndx; /* Section index */
|
||||||
|
U64 st_value; /* Symbol value */
|
||||||
|
U64 st_size; /* Symbol size */
|
||||||
|
};
|
||||||
|
|
||||||
|
class PLT_entry {
|
||||||
|
U8 pad[0x10];
|
||||||
|
};
|
||||||
|
|
||||||
|
class RELA_entry {
|
||||||
|
U64 r_offset;
|
||||||
|
U64 r_info;
|
||||||
|
I64 r_addend;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Elf {
|
||||||
|
union {
|
||||||
|
U8* u8;
|
||||||
|
Elf64_Ehdr* ehdr;
|
||||||
|
} I64 size;
|
||||||
|
U8* dynstr;
|
||||||
|
Elf64_Sym* dynsym;
|
||||||
|
PLT_entry* plt;
|
||||||
|
RELA_entry* rela_dyn;
|
||||||
|
RELA_entry* rela_plt;
|
||||||
|
Elf64_Sym* strtab;
|
||||||
|
Elf64_Sym* symtab;
|
||||||
|
I64 rela_dyn_size;
|
||||||
|
I64 rela_plt_size;
|
||||||
|
I64 strtab_size;
|
||||||
|
I64 symtab_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
U0(*_start)
|
||||||
|
();
|
||||||
|
|
||||||
|
U0 unimplemented_symbol()
|
||||||
|
{
|
||||||
|
I32 s = 0xDEADF00D;
|
||||||
|
PrintWarn("Unimplemented symbol: %s\n", s);
|
||||||
|
Dbg;
|
||||||
|
while (1)
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool is_valid_elf(Elf* elf)
|
||||||
|
{
|
||||||
|
Bool res = TRUE;
|
||||||
|
if (MemCmp(elf->u8 + 1, "ELF", 3)) {
|
||||||
|
@elf64_debug_print("Invalid signature (not ELF).\n");
|
||||||
|
res = FALSE;
|
||||||
|
}
|
||||||
|
if (elf->ehdr->e_type != ET_EXEC && elf->ehdr->e_type != ET_DYN) {
|
||||||
|
@elf64_debug_print("Invalid object file type.\n");
|
||||||
|
res = FALSE;
|
||||||
|
}
|
||||||
|
if (elf->ehdr->e_machine != EM_X86_64) {
|
||||||
|
@elf64_debug_print("Invalid architecture.\n");
|
||||||
|
res = FALSE;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 process_elf_section_header_table(Elf* elf)
|
||||||
|
{
|
||||||
|
Elf64_Shdr* shdr = elf->u8 + elf->ehdr->e_shoff;
|
||||||
|
Elf64_Shdr* shdr_shstrtab = shdr + elf->ehdr->e_shstrndx;
|
||||||
|
U8* shstrtab = elf->u8 + shdr_shstrtab->sh_offset;
|
||||||
|
I64 i = 0;
|
||||||
|
while (i < elf->ehdr->e_shnum) {
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".symtab")) {
|
||||||
|
@elf64_debug_print("found symtab at 0x%08x, size = %d\n", shdr->sh_offset,
|
||||||
|
shdr->sh_size);
|
||||||
|
elf->symtab = elf->u8 + shdr->sh_offset;
|
||||||
|
elf->symtab_size = shdr->sh_size;
|
||||||
|
}
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".strtab")) {
|
||||||
|
@elf64_debug_print("found strtab at 0x%08x, size = %d\n", shdr->sh_offset,
|
||||||
|
shdr->sh_size);
|
||||||
|
elf->strtab = elf->u8 + shdr->sh_offset;
|
||||||
|
elf->strtab_size = shdr->sh_size;
|
||||||
|
}
|
||||||
|
if (shdr->sh_addr) {
|
||||||
|
MemCpy(shdr->sh_addr, elf->u8 + shdr->sh_offset, shdr->sh_size);
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".dynstr"))
|
||||||
|
elf->dynstr = shdr->sh_addr;
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".dynsym"))
|
||||||
|
elf->dynsym = shdr->sh_addr;
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".plt"))
|
||||||
|
elf->plt = shdr->sh_addr;
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".rela.dyn")) {
|
||||||
|
elf->rela_dyn = shdr->sh_addr;
|
||||||
|
elf->rela_dyn_size = shdr->sh_size / shdr->sh_entsize;
|
||||||
|
}
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".rela.plt")) {
|
||||||
|
elf->rela_plt = shdr->sh_addr;
|
||||||
|
elf->rela_plt_size = shdr->sh_size / shdr->sh_entsize;
|
||||||
|
}
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".bss") || !StrCmp(shstrtab + shdr->sh_name, ".tbss")) {
|
||||||
|
MemSet(shdr->sh_addr, NULL, shdr->sh_size);
|
||||||
|
@elf64_debug_print(
|
||||||
|
"Zeroed out section '%s' at physical address 0x%06x, size = %d bytes\n",
|
||||||
|
shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
|
||||||
|
} else
|
||||||
|
@elf64_debug_print(
|
||||||
|
"MemCpy section '%s' to physical address 0x%06x, size = %d bytes\n",
|
||||||
|
shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
|
||||||
|
if (!StrCmp(shstrtab + shdr->sh_name, ".bss")) {
|
||||||
|
MemSet(shdr->sh_addr, NULL, shdr->sh_size);
|
||||||
|
@elf64_debug_print("MemSet section '%s' at physical address 0x%06x to NULL, "
|
||||||
|
"size = %d bytes\n",
|
||||||
|
shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shdr++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 process_elf_rela_dyn_entries(Elf* elf)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
U8* entry_name;
|
||||||
|
RELA_entry* rela_dyn = elf->rela_dyn;
|
||||||
|
for (i = 0; i < elf->rela_dyn_size; i++) {
|
||||||
|
entry_name = elf->dynstr + elf->dynsym[(rela_dyn->r_info >> 32)].st_name;
|
||||||
|
@elf64_debug_print("rela_dyn->r_offset = %08x\n", rela_dyn->r_offset);
|
||||||
|
@elf64_debug_print("entry name = '%s'\n", entry_name);
|
||||||
|
if (!StrCmp(entry_name, "__libc_start_main")) {
|
||||||
|
*(rela_dyn->r_offset)(U64*) = &_main;
|
||||||
|
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: &_main\n",
|
||||||
|
entry_name);
|
||||||
|
}
|
||||||
|
if (!StrCmp(entry_name, "stdin")) {
|
||||||
|
*(rela_dyn->r_offset)(U64*) = 0;
|
||||||
|
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 0);
|
||||||
|
}
|
||||||
|
if (!StrCmp(entry_name, "stdout")) {
|
||||||
|
*(rela_dyn->r_offset)(U64*) = 1;
|
||||||
|
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 1);
|
||||||
|
}
|
||||||
|
if (!StrCmp(entry_name, "stderr")) {
|
||||||
|
*(rela_dyn->r_offset)(U64*) = 2;
|
||||||
|
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 2);
|
||||||
|
}
|
||||||
|
rela_dyn++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CHashClass* get_symbol_hash_entry(U8* entry_name)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
CHashSrcSym* sym;
|
||||||
|
CHashTable* tbl = Fs->hash_table;
|
||||||
|
while (tbl) {
|
||||||
|
for (i = 0; i < tbl->mask; i++) {
|
||||||
|
sym = tbl->body[i];
|
||||||
|
while (sym) {
|
||||||
|
if (sym->type == HTT_CLASS)
|
||||||
|
if (!StrCmp(sym->str, entry_name))
|
||||||
|
return sym;
|
||||||
|
sym = sym->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbl = tbl->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 get_symbol_address(U8* entry_name)
|
||||||
|
{
|
||||||
|
CHash* h = HashFind(entry_name, Fs->hash_table, Fs->hash_table->mask);
|
||||||
|
if (!h)
|
||||||
|
return NULL;
|
||||||
|
switch (h->type) {
|
||||||
|
case HTT_GLBL_VAR:
|
||||||
|
return h(CHashGlblVar*)->data_addr;
|
||||||
|
break;
|
||||||
|
case HTT_FUN:
|
||||||
|
return h(CHashFun*)->exe_addr;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 process_elf_rela_plt_entries(Elf* elf)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
U32 handler;
|
||||||
|
U32* patch;
|
||||||
|
U8* entry_name;
|
||||||
|
Bool symbol_exists;
|
||||||
|
PLT_entry* plt = elf->plt;
|
||||||
|
RELA_entry* rela_plt = elf->rela_plt;
|
||||||
|
plt++;
|
||||||
|
for (i = 0; i < elf->rela_plt_size; i++) {
|
||||||
|
symbol_exists = FALSE;
|
||||||
|
entry_name = elf->dynstr + elf->dynsym[(rela_plt->r_info >> 32)].st_name;
|
||||||
|
handler = MAlloc(sizeof(unimplemented_symbol), adam_task->code_heap);
|
||||||
|
MemCpy(handler, &unimplemented_symbol, sizeof(unimplemented_symbol));
|
||||||
|
patch = handler + 0x0A;
|
||||||
|
*patch = entry_name;
|
||||||
|
@patch_jmp_rel32(plt, handler);
|
||||||
|
@patch_call_rel32(handler + 0x16, &PrintErr);
|
||||||
|
//@patch_call_rel32(handler + 0x21, &_exit);
|
||||||
|
if (!StrCmp(entry_name, "__libc_start_main")) {
|
||||||
|
symbol_exists = TRUE;
|
||||||
|
@patch_jmp_rel32(plt, &_main);
|
||||||
|
@elf64_debug_print("Set value for .rela.plt entry '%s' to &_main\n", entry_name);
|
||||||
|
}
|
||||||
|
if (get_symbol_address(entry_name)) {
|
||||||
|
symbol_exists = TRUE;
|
||||||
|
@patch_jmp_rel32(plt, get_symbol_address(entry_name));
|
||||||
|
@elf64_debug_print("Set value for .rela.plt entry '%s' to &%s\n", entry_name,
|
||||||
|
entry_name);
|
||||||
|
}
|
||||||
|
if (!symbol_exists)
|
||||||
|
@elf64_debug_print(
|
||||||
|
"Set value for .rela.plt entry '%s' to &unimplemented_symbol\n",
|
||||||
|
entry_name);
|
||||||
|
rela_plt++;
|
||||||
|
plt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 load_elf(...)
|
||||||
|
{
|
||||||
|
if (argc < 1) {
|
||||||
|
PrintErr("Not enough arguments.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!FileFind(argv[0])) {
|
||||||
|
PrintErr("File not found: %s\n", argv[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Elf elf;
|
||||||
|
elf.u8 = FileRead(argv[0], &elf.size);
|
||||||
|
@elf64_debug_print("Load file '%s', size = %d bytes\n", argv[0], elf.size);
|
||||||
|
|
||||||
|
if (!is_valid_elf(&elf)) {
|
||||||
|
PrintErr("File is not a valid ELF x86-64 executable.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_elf_section_header_table(&elf);
|
||||||
|
process_elf_rela_dyn_entries(&elf);
|
||||||
|
process_elf_rela_plt_entries(&elf);
|
||||||
|
|
||||||
|
_start = elf.ehdr->e_entry;
|
||||||
|
elf_argc = argc;
|
||||||
|
elf_argv = argv;
|
||||||
|
}
|
317
System/FFI/LibC.HC
Normal file
317
System/FFI/LibC.HC
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
#define stdin 0
|
||||||
|
#define stdout 1
|
||||||
|
#define stderr 2
|
||||||
|
|
||||||
|
U0 bcmp()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
MemCmp(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 calloc()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
CAlloc(p0 * p1, adam_task->code_heap);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 free()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
Free(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @isatty()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 isatty()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
// Dbg;
|
||||||
|
@isatty;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @fwrite(U8* ptr, I64 size, I64 nmemb, U64 stream)
|
||||||
|
{
|
||||||
|
U8* tmp;
|
||||||
|
switch (stream) {
|
||||||
|
case stdout:
|
||||||
|
case stderr:
|
||||||
|
tmp = CAlloc((size * nmemb) + 1, adam_task->code_heap);
|
||||||
|
MemCpy(tmp, ptr, (size * nmemb));
|
||||||
|
#ifdef QEMU_RUN_TESTS
|
||||||
|
QemuDebugMsg(tmp);
|
||||||
|
#endif
|
||||||
|
DocPutS(adam_task->put_doc, tmp);
|
||||||
|
Free(tmp);
|
||||||
|
// if (!MemCmp(tmp, "VERIFICATION FAILED", 19))
|
||||||
|
// Break;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 fwrite()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@fwrite(p0, p1, p2, p3);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 @getentropy(U8* buffer, U64 length)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < length; i++)
|
||||||
|
buffer[i] = RandU64;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 getentropy()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@getentropy(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 htonl()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
EndianU32(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 ntohl()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
EndianU32(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 htons()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
EndianU16(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 ntohs()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
EndianU16(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 malloc()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
MAlloc(p0, adam_task->code_heap);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 memcmp()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
MemCmp(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 memcpy()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
MemCpy(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @memmove(U8* dest, U8* src, I64 n)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
U8* from = src;
|
||||||
|
U8* to = dest;
|
||||||
|
if (from == to || n == 0)
|
||||||
|
return dest;
|
||||||
|
if (to > from && to - from < n) {
|
||||||
|
/* to overlaps with from */
|
||||||
|
/* <from......> */
|
||||||
|
/* <to........> */
|
||||||
|
/* copy in reverse, to avoid overwriting from */
|
||||||
|
for (i = n - 1; i >= 0; i--)
|
||||||
|
to[i] = from[i];
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
if (from > to && from - to < n) {
|
||||||
|
/* to overlaps with from */
|
||||||
|
/* <from......> */
|
||||||
|
/* <to........> */
|
||||||
|
/* copy forwards, to avoid overwriting from */
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
to[i] = from[i];
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
MemCpy(dest, src, n);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 memmove()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@memmove(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 memset()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
MemSet(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 putc()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PutChars(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 rand()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
RandU64;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @realloc(U8* ptr, I64 size)
|
||||||
|
{
|
||||||
|
U8* new;
|
||||||
|
if (!ptr) {
|
||||||
|
new = MAlloc(size, adam_task->code_heap);
|
||||||
|
} else {
|
||||||
|
new = MAlloc(size, adam_task->code_heap);
|
||||||
|
MemCpy(new, ptr, size);
|
||||||
|
Free(ptr);
|
||||||
|
}
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 realloc()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@realloc(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: It is non-obvious how to take a [u8] and convert it to a
|
||||||
|
// formatted string in Jakt, so we have to do this hack for
|
||||||
|
// now. Hopefully, this will change soon.
|
||||||
|
U0 sprintf()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
StrPrint(p0, p1, p2, p3, p4, p5);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @strncmp(U8* s1, U8* s2, I32 n)
|
||||||
|
{
|
||||||
|
U64 u1, u2;
|
||||||
|
|
||||||
|
while (n-- > 0) {
|
||||||
|
u1 = *s1++;
|
||||||
|
u2 = *s2++;
|
||||||
|
u1 = u1 & 0xff;
|
||||||
|
u2 = u2 & 0xff;
|
||||||
|
if (u1 != u2)
|
||||||
|
return u1 - u2;
|
||||||
|
if (u1 == '\0')
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 strncmp()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@strncmp(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 strcmp()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
StrCmp(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 strlen()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
StrLen(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 tos_nist_offset = 5020;
|
||||||
|
#define NIST_TIME_OFFSET (tos_nist_offset - local_time_offset / CDATE_FREQ)
|
||||||
|
|
||||||
|
public
|
||||||
|
I64 CDate2Unix(CDate dt)
|
||||||
|
{ // TempleOS datetime to Unix timestamp.
|
||||||
|
return ToI64((dt - Str2Date("1/1/1970")) / CDATE_FREQ + NIST_TIME_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @time(I64* ptr)
|
||||||
|
{
|
||||||
|
no_warn ptr;
|
||||||
|
return CDate2Unix(Now);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 time()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@time(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 toupper()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
ToUpper(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 __assert_fail()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
"%s:%d: %s: %s\n", p1, p2, p3, p0;
|
||||||
|
Break;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
35
System/FFI/New.HC
Normal file
35
System/FFI/New.HC
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
U0 _ZdlPv()
|
||||||
|
{
|
||||||
|
// operator delete(void*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
Free(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _ZdlPvm()
|
||||||
|
{
|
||||||
|
// operator delete(void*, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
Free(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Znwm()
|
||||||
|
{
|
||||||
|
// operator new(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
MAlloc(p0, adam_task);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _ZnwmRKSt9nothrow_t()
|
||||||
|
{
|
||||||
|
// operator new(unsigned long, std::nothrow_t const&)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
MAlloc(p0, adam_task);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
288
System/Jakt/DC.HC
Normal file
288
System/Jakt/DC.HC
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
U0 _Z8dc_aliasm()
|
||||||
|
{
|
||||||
|
// dc_alias(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
DCAlias(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z7dc_blotmmmm()
|
||||||
|
{
|
||||||
|
// dc_blot(unsigned long, unsigned long, unsigned long, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GrBlot(p0, p1, p2, p3);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @dc_buffer(CDC* dc) { return dc->body; }
|
||||||
|
|
||||||
|
U0 _Z9dc_bufferm()
|
||||||
|
{
|
||||||
|
// dc_buffer(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_buffer(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @dc_color(CDC* dc) { return dc->color; }
|
||||||
|
|
||||||
|
U0 _Z8dc_colorm()
|
||||||
|
{
|
||||||
|
// dc_color(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_color(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @dc_copy(CDC* dest, I64 x, I64 y, CDC* src)
|
||||||
|
{
|
||||||
|
|
||||||
|
// If position is off-screen, return
|
||||||
|
if (x > dest->width - 1 || y > dest->height - 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If device context dimensions match, MemCpy and return
|
||||||
|
if (dest->width_internal == src->width_internal && dest->height == src->height) {
|
||||||
|
MemCpy(dest->body, src->body, dest->width_internal * dest->height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CDC* dc1 = DCAlias(dest);
|
||||||
|
CDC* dc2 = DCAlias(src);
|
||||||
|
|
||||||
|
I64 src_line = 0;
|
||||||
|
I64 src_row = 0;
|
||||||
|
I64 clip_y = 0;
|
||||||
|
|
||||||
|
// Handle horizontal clipping left
|
||||||
|
while (x < 0) {
|
||||||
|
dc2->x0++;
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle vertical clipping top
|
||||||
|
while (y < 0) {
|
||||||
|
dc2->body += src->width_internal;
|
||||||
|
dc2->y0++;
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default, clip line to copy as width-left off screen
|
||||||
|
src_line = src->width - dc2->x0;
|
||||||
|
|
||||||
|
if (-dc2->x0 + x + src->width >= dest->width) {
|
||||||
|
src_line -= ((-dc2->x0 + x + src->width) - dest->width);
|
||||||
|
}
|
||||||
|
|
||||||
|
dc2->body += dc2->x0;
|
||||||
|
clip_y = y;
|
||||||
|
|
||||||
|
while (src_row < (src->height - dc2->y0) && clip_y < dest->height) {
|
||||||
|
MemCpy(dc1->body + (y * dest->width) + x, dc2->body, src_line);
|
||||||
|
dc2->body += src->width_internal;
|
||||||
|
dc1->body += dest->width_internal;
|
||||||
|
clip_y++;
|
||||||
|
src_row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(dc2);
|
||||||
|
Free(dc1);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z7dc_copymmmm()
|
||||||
|
{
|
||||||
|
// dc_copy(unsigned long, unsigned long, unsigned long, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_copy(p0, p1, p2, p3);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z10dc_destroym()
|
||||||
|
{
|
||||||
|
// dc_destroy(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
DCDel(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z14dc_draw_circlemlll()
|
||||||
|
{
|
||||||
|
// dc_draw_circle(unsigned long, long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GrCircle3(p0, p1, p2, 0, p3);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z19dc_draw_filled_rectmllll()
|
||||||
|
{
|
||||||
|
// dc_draw_filled_rect(unsigned long, long, long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GrRect(p0, p1, p2, p3, p4);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z12dc_draw_linemllll()
|
||||||
|
{
|
||||||
|
// dc_draw_line(unsigned long, long, long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GrLine3(p0, p1, p2, 0, p3, p4, 0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13dc_draw_pixelmll()
|
||||||
|
{
|
||||||
|
// dc_draw_pixel(unsigned long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GrPlot(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z7dc_fillmm()
|
||||||
|
{
|
||||||
|
// dc_fill(unsigned long, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
DCFill(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
CDC* @dc_gr_dc() { return gr.dc; }
|
||||||
|
|
||||||
|
U0 _Z8dc_gr_dcv()
|
||||||
|
{
|
||||||
|
// dc_gr_dc()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_gr_dc();
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @dc_height(CDC* dc) { return dc->height; }
|
||||||
|
|
||||||
|
U0 _Z9dc_heightm()
|
||||||
|
{
|
||||||
|
// dc_height(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_height(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z17dc_load_from_filePKc()
|
||||||
|
{
|
||||||
|
// dc_load_from_file(char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GRRead(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z6dc_newmm()
|
||||||
|
{
|
||||||
|
// dc_new(unsigned long, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
DCNew(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z11dc_pixel_atmll()
|
||||||
|
{
|
||||||
|
// dc_pixel_at(unsigned long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GrPeek(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z16dc_replace_colormmm()
|
||||||
|
{
|
||||||
|
// dc_replace_color(unsigned long, unsigned long, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
DCColorChg(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13dc_screenshotv()
|
||||||
|
{
|
||||||
|
// dc_screenshot()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
DCScrnCapture(1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z15dc_save_to_filePKcm()
|
||||||
|
{
|
||||||
|
// dc_save_to_file(char const*, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GRWrite(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @dc_set_color(CDC* dc, I64 color) { dc->color = color; }
|
||||||
|
|
||||||
|
U0 _Z12dc_set_colorml()
|
||||||
|
{
|
||||||
|
// dc_set_color(unsigned long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_set_color(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @dc_set_thickness(CDC* dc, I64 thickness) { dc->thick = thickness; }
|
||||||
|
|
||||||
|
U0 _Z16dc_set_thicknessml()
|
||||||
|
{
|
||||||
|
// dc_set_thickness(unsigned long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_set_thickness(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @dc_thickness(CDC* dc) { return dc->thick; }
|
||||||
|
|
||||||
|
U0 _Z12dc_thicknessm()
|
||||||
|
{
|
||||||
|
// dc_thickness(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_thickness(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @dc_width(CDC* dc) { return dc->width; }
|
||||||
|
|
||||||
|
U0 _Z8dc_widthm()
|
||||||
|
{
|
||||||
|
// dc_width(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_width(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @dc_width_internal(CDC* dc) { return dc->width_internal; }
|
||||||
|
|
||||||
|
U0 _Z17dc_width_internalm()
|
||||||
|
{
|
||||||
|
// dc_width_internal(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@dc_width_internal(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
53
System/Jakt/IOPort.HC
Normal file
53
System/Jakt/IOPort.HC
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
U0 _Z14ioport_read_u8t()
|
||||||
|
{
|
||||||
|
// ioport_read_u8(unsigned short)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
InU8(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z15ioport_read_u16t()
|
||||||
|
{
|
||||||
|
// ioport_read_u16(unsigned short)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
InU16(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z15ioport_read_u32t()
|
||||||
|
{
|
||||||
|
// ioport_read_u32(unsigned short)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
InU32(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z15ioport_write_u8th()
|
||||||
|
{
|
||||||
|
// ioport_write_u8(unsigned short, unsigned char)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
OutU8(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z16ioport_write_u16tt()
|
||||||
|
{
|
||||||
|
// ioport_write_u16(unsigned short, unsigned short)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
OutU16(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z16ioport_write_u32tj()
|
||||||
|
{
|
||||||
|
// ioport_write_u32(unsigned short, unsigned int)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
OutU32(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
72
System/Jakt/Input.HC
Normal file
72
System/Jakt/Input.HC
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
U0 _Z16input_get_stringPKc()
|
||||||
|
{
|
||||||
|
// input_get_string(char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
GetStr(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @input_key_down(U8 scancode) { return Bt(kbd.down_bitmap, scancode); }
|
||||||
|
|
||||||
|
U0 _Z14input_key_downh()
|
||||||
|
{
|
||||||
|
// input_key_down(unsigned char)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@input_key_down(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @input_mouse_left() { return ms.lb; }
|
||||||
|
|
||||||
|
U0 _Z16input_mouse_leftv()
|
||||||
|
{
|
||||||
|
// input_mouse_left()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@input_mouse_left();
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @input_mouse_right() { return ms.rb; }
|
||||||
|
|
||||||
|
U0 _Z17input_mouse_rightv()
|
||||||
|
{
|
||||||
|
// input_mouse_right()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@input_mouse_right();
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @input_mouse_x() { return ms.pos.x; }
|
||||||
|
|
||||||
|
U0 _Z13input_mouse_xv()
|
||||||
|
{
|
||||||
|
// input_mouse_x()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@input_mouse_x();
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @input_mouse_y() { return ms.pos.y; }
|
||||||
|
|
||||||
|
U0 _Z13input_mouse_yv()
|
||||||
|
{
|
||||||
|
// input_mouse_y()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@input_mouse_y();
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z17input_press_a_keyv()
|
||||||
|
{
|
||||||
|
// input_press_a_key()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PressAKey;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
289
System/Jakt/OS.HC
Normal file
289
System/Jakt/OS.HC
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
U0 _Z8os_blinkPKc()
|
||||||
|
{
|
||||||
|
// os_blink(char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
F64 frequency = Str2F64(p0);
|
||||||
|
Print("called os_blink(%.1f)\n", frequency);
|
||||||
|
Blink(frequency);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U64 @os_call(U8* function_name, U64 arg)
|
||||||
|
{
|
||||||
|
if (!function_name)
|
||||||
|
return NULL;
|
||||||
|
if (!StrLen(function_name))
|
||||||
|
return NULL;
|
||||||
|
CHash* h = HashFind(function_name, Fs->hash_table, Fs->hash_table->mask);
|
||||||
|
if (!h)
|
||||||
|
return NULL;
|
||||||
|
if (h->type & HTT_FUN == HTT_FUN) {
|
||||||
|
CallInd(h(CHashFun*)->exe_addr, arg);
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z7os_callmm()
|
||||||
|
{
|
||||||
|
// os_call(unsigned long, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@os_call(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z16os_device_callocj()
|
||||||
|
{
|
||||||
|
// os_device_calloc(unsigned int)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
CAllocAligned(p0, 4096, adam_task->code_heap);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z7os_exitv()
|
||||||
|
{
|
||||||
|
// os_exit()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
UserTaskCont;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @os_file_picker(U8* path, U8* glob)
|
||||||
|
{
|
||||||
|
U8* full_path = CAlloc(StrLen(path) + StrLen(glob) + 4, adam_task);
|
||||||
|
CatPrint(full_path, "%s/%s", path, glob);
|
||||||
|
|
||||||
|
CDirEntry* de = FilesFind(full_path);
|
||||||
|
Free(full_path);
|
||||||
|
|
||||||
|
CDirEntry* tmpde;
|
||||||
|
U8* file_list = NULL;
|
||||||
|
U8* selected_file = NULL;
|
||||||
|
I64 list_pos = 0;
|
||||||
|
I64 list_size = 0;
|
||||||
|
|
||||||
|
tmpde = de;
|
||||||
|
while (tmpde) {
|
||||||
|
list_size += StrLen(tmpde->name) + 2;
|
||||||
|
tmpde = tmpde->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_list = CAlloc(list_size, adam_task);
|
||||||
|
|
||||||
|
tmpde = de;
|
||||||
|
while (tmpde) {
|
||||||
|
StrCpy(file_list + list_pos, tmpde->name);
|
||||||
|
list_pos += StrLen(tmpde->name) + 1;
|
||||||
|
tmpde = tmpde->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 list_index = Adam("PopUpPickLst(0x%08x);\n", file_list);
|
||||||
|
Free(file_list);
|
||||||
|
list_pos = 0;
|
||||||
|
|
||||||
|
if (list_index < 0) {
|
||||||
|
DirTreeDel(de);
|
||||||
|
return StrNew("", adam_task);
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpde = de;
|
||||||
|
while (tmpde) {
|
||||||
|
if (list_index == list_pos) {
|
||||||
|
selected_file = CAlloc(StrLen(path) + StrLen(tmpde->name) + 4, adam_task);
|
||||||
|
CatPrint(selected_file, "%s/%s", path, tmpde->name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
StrCpy(file_list + list_pos, tmpde->name);
|
||||||
|
list_pos++;
|
||||||
|
tmpde = tmpde->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirTreeDel(de);
|
||||||
|
return selected_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z14os_file_pickerPKcS0_()
|
||||||
|
{
|
||||||
|
// os_file_picker(char const*, char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@os_file_picker(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @os_files_list(U8* path)
|
||||||
|
{
|
||||||
|
U8* full_path = CAlloc(StrLen(path) + 4, adam_task);
|
||||||
|
CatPrint(full_path, "%s", path);
|
||||||
|
|
||||||
|
CDirEntry* de = FilesFind(full_path);
|
||||||
|
Free(full_path);
|
||||||
|
|
||||||
|
CDateStruct ds;
|
||||||
|
CDirEntry* tmpde;
|
||||||
|
U8* file_list = NULL;
|
||||||
|
I64 list_size = 0;
|
||||||
|
|
||||||
|
tmpde = de;
|
||||||
|
while (tmpde) {
|
||||||
|
list_size += StrLen(tmpde->name) + 48; // Should be enough for filename, date,
|
||||||
|
// filesize + semicolon separators
|
||||||
|
tmpde = tmpde->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!list_size)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
file_list = CAlloc(list_size, adam_task);
|
||||||
|
|
||||||
|
tmpde = de;
|
||||||
|
I64 counter = 0;
|
||||||
|
|
||||||
|
while (tmpde) {
|
||||||
|
if (counter > 0) {
|
||||||
|
StrCpy(file_list + StrLen(file_list), "|");
|
||||||
|
}
|
||||||
|
StrCpy(file_list + StrLen(file_list), tmpde->name);
|
||||||
|
if (tmpde->attr & RS_ATTR_DIR)
|
||||||
|
StrCpy(file_list + StrLen(file_list), "/");
|
||||||
|
StrCpy(file_list + StrLen(file_list), ";");
|
||||||
|
Date2Struct(&ds, tmpde->datetime);
|
||||||
|
StrPrint(file_list + StrLen(file_list), "%04d-%02d-%02d %02d:%02d", ds.year,
|
||||||
|
ds.mon, ds.day_of_mon, ds.hour, ds.min);
|
||||||
|
StrCpy(file_list + StrLen(file_list), ";");
|
||||||
|
StrPrint(file_list + StrLen(file_list), "%d", tmpde->size);
|
||||||
|
tmpde = tmpde->next;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirTreeDel(de);
|
||||||
|
return file_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z14os_path_existsPKc()
|
||||||
|
{
|
||||||
|
// os_path_exists(char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
FileFind(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13os_files_listPKc()
|
||||||
|
{
|
||||||
|
// os_files_list(char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@os_files_list(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @os_is_vm()
|
||||||
|
{
|
||||||
|
CRAXRBCRCXRDX res;
|
||||||
|
CPUId(0x40000000, &res);
|
||||||
|
if (res.rbx == 0x4B4D564B)
|
||||||
|
return TRUE;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z8os_is_vmv()
|
||||||
|
{
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@os_is_vm;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @os_pc_speaker(F64 frequency)
|
||||||
|
{
|
||||||
|
I64 period;
|
||||||
|
if (!frequency)
|
||||||
|
OutU8(0x61, InU8(0x61) & ~3);
|
||||||
|
else {
|
||||||
|
period = ClampI64(SYS_TIMER_FREQ / frequency, 1, U16_MAX);
|
||||||
|
OutU8(0x43, 0xB6);
|
||||||
|
OutU8(0x42, period);
|
||||||
|
OutU8(0x42, period.u8[1]);
|
||||||
|
OutU8(0x61, 3 | InU8(0x61));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13os_pc_speakerPKc()
|
||||||
|
{
|
||||||
|
// os_pc_speaker(char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
F64 frequency = Str2F64(p0);
|
||||||
|
@os_pc_speaker(frequency);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z9os_randomv()
|
||||||
|
{
|
||||||
|
// os_random()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
RandU64;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z19os_read_entire_filePKcPl()
|
||||||
|
{
|
||||||
|
// os_read_entire_file(char const*, long*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
FileRead(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @os_screenshot()
|
||||||
|
{
|
||||||
|
CDC* dc = DCScrnCapture(, adam_task);
|
||||||
|
Image.Write("B:/screenshot.png", dc);
|
||||||
|
DCDel(dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13os_screenshotv()
|
||||||
|
{
|
||||||
|
// os_screenshot()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@os_screenshot;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @os_to_uppercase(U8* instr)
|
||||||
|
{
|
||||||
|
if (!instr)
|
||||||
|
return NULL;
|
||||||
|
if (!StrLen(instr))
|
||||||
|
return NULL;
|
||||||
|
U8* outstr = CAlloc(StrLen(instr) + 1, adam_task);
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < StrLen(instr); i++)
|
||||||
|
outstr[i] = ToUpper(instr[i]);
|
||||||
|
return outstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z15os_to_uppercasePKc()
|
||||||
|
{
|
||||||
|
// os_to_uppercase(char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@os_to_uppercase(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z20os_write_entire_filePKcPhl()
|
||||||
|
{
|
||||||
|
// os_write_entire_file(char const*, unsigned char*, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
FileWrite(p0, p1, p2);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
62
System/Jakt/PCI.HC
Normal file
62
System/Jakt/PCI.HC
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
U0 _Z8pci_findl()
|
||||||
|
{
|
||||||
|
// pci_find(long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PCIClassFind(p0, 0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z11pci_read_u8llll()
|
||||||
|
{
|
||||||
|
// pci_read_u8(long, long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PCIReadU8(p0, p1, p2, p3);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z12pci_read_u16llll()
|
||||||
|
{
|
||||||
|
// pci_read_u16(long, long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PCIReadU16(p0, p1, p2, p3);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z12pci_read_u32llll()
|
||||||
|
{
|
||||||
|
// pci_read_u32(long, long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PCIReadU32(p0, p1, p2, p3);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z12pci_write_u8llllh()
|
||||||
|
{
|
||||||
|
// pci_write_u8(long, long, long, long, unsigned char)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PCIWriteU8(p0, p1, p2, p3, p4);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13pci_write_u16llllt()
|
||||||
|
{
|
||||||
|
// pci_write_u16(long, long, long, long, unsigned short)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PCIWriteU16(p0, p1, p2, p3, p4);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13pci_write_u32llllj()
|
||||||
|
{
|
||||||
|
// pci_write_u32(long, long, long, long, unsigned int)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
PCIWriteU32(p0, p1, p2, p3, p4);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
37
System/Jakt/Time.HC
Normal file
37
System/Jakt/Time.HC
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
U0 _Z9time_busyl()
|
||||||
|
{
|
||||||
|
// time_busy(long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
Busy(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @time_jiffies() { return cnts.jiffies; }
|
||||||
|
|
||||||
|
U0 _Z12time_jiffiesv()
|
||||||
|
{
|
||||||
|
// time_jiffies()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@time_jiffies;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z8time_nowv()
|
||||||
|
{
|
||||||
|
// time_now()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
Now;
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z10time_sleepl()
|
||||||
|
{
|
||||||
|
// time_sleep(long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
Sleep(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
94
System/Jakt/Window.HC
Normal file
94
System/Jakt/Window.HC
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
U0 @window_draw_it(CTask* task, CDC* dc)
|
||||||
|
{
|
||||||
|
if (task->user_data)
|
||||||
|
@dc_copy(dc, task->pix_left, task->pix_top, task->user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
CTask* @window_user()
|
||||||
|
{
|
||||||
|
CTask* task = Spawn(&UserCmdLine, , , 0);
|
||||||
|
TaskWait(task);
|
||||||
|
XTalk(task,
|
||||||
|
"while (1) { StrCpy(Fs->task_title, Fs->task_name); Sleep(1); };\n");
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTask* @window_create()
|
||||||
|
{
|
||||||
|
CTask* task = @window_user;
|
||||||
|
task->draw_it = &@window_draw_it;
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z13window_createv()
|
||||||
|
{
|
||||||
|
// window_create()
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@window_create();
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z14window_destroym()
|
||||||
|
{
|
||||||
|
// window_destroy(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
Kill(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @window_is_focused(CTask* task) { return task == sys_focus_task; }
|
||||||
|
|
||||||
|
U0 _Z17window_is_focusedm()
|
||||||
|
{
|
||||||
|
// window_is_focused(unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@window_is_focused(p0);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @window_set_coordinates(CTask* task, I64 top, I64 left, I64 bottom,
|
||||||
|
I64 right)
|
||||||
|
{
|
||||||
|
task->win_top = top;
|
||||||
|
task->win_left = left;
|
||||||
|
task->win_bottom = bottom;
|
||||||
|
task->win_right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z22window_set_coordinatesmllll()
|
||||||
|
{
|
||||||
|
// window_set_coordinates(unsigned long, long, long, long, long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@window_set_coordinates(p0, p1, p2, p3, p4);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @window_set_context(CTask* task, CDC* dc) { task->user_data = dc; }
|
||||||
|
|
||||||
|
U0 _Z18window_set_contextmm()
|
||||||
|
{
|
||||||
|
// window_set_context(unsigned long, unsigned long)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@window_set_context(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @window_set_title(CTask* task, U8* title)
|
||||||
|
{
|
||||||
|
StrCpy(task->task_name, title);
|
||||||
|
StrCpy(task->task_title, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 _Z16window_set_titlemPKc()
|
||||||
|
{
|
||||||
|
// window_set_title(unsigned long, char const*)
|
||||||
|
PUSH_SYSV_REGS
|
||||||
|
GET_SYSV_ARGS
|
||||||
|
@window_set_title(p0, p1);
|
||||||
|
POP_SYSV_REGS
|
||||||
|
}
|
88
System/Libraries/Base64.HC
Normal file
88
System/Libraries/Base64.HC
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
|
||||||
|
U8* @base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
U8* @base64_decode(U8* input, I64* output_length)
|
||||||
|
{
|
||||||
|
I64 input_length = StrLen(input);
|
||||||
|
if (input_length % 4 != 0) {
|
||||||
|
return NULL; // Invalid Base64 input length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the expected output length
|
||||||
|
*output_length = (3 * input_length) / 4;
|
||||||
|
if (input[input_length - 1] == '=') {
|
||||||
|
(*output_length)--;
|
||||||
|
}
|
||||||
|
if (input[input_length - 2] == '=') {
|
||||||
|
(*output_length)--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate memory for the decoded data
|
||||||
|
U8* decoded_data = CAlloc(*output_length, adam_task);
|
||||||
|
if (decoded_data == NULL) {
|
||||||
|
return NULL; // Memory allocation failed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize variables for decoding process
|
||||||
|
I32 i, j = 0;
|
||||||
|
U32 sextet_bits = 0;
|
||||||
|
I64 sextet_count = 0;
|
||||||
|
U32 base64_value;
|
||||||
|
U8* char_pointer;
|
||||||
|
U8 input_find_buf[2];
|
||||||
|
input_find_buf[1] = NULL;
|
||||||
|
|
||||||
|
// Loop through the Base64 input and decode it
|
||||||
|
for (i = 0; i < input_length; i++) {
|
||||||
|
// Convert Base64 character to a 6-bit value
|
||||||
|
base64_value = 0;
|
||||||
|
if (input[i] == '=') {
|
||||||
|
base64_value = 0;
|
||||||
|
} else {
|
||||||
|
input_find_buf[0] = input[i];
|
||||||
|
char_pointer = StrFirstOcc(@base64_chars, input_find_buf);
|
||||||
|
if (char_pointer == NULL) {
|
||||||
|
Free(decoded_data);
|
||||||
|
return NULL; // Invalid Base64 character
|
||||||
|
}
|
||||||
|
base64_value = char_pointer - @base64_chars;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine 6-bit values into a 24-bit sextet
|
||||||
|
sextet_bits = (sextet_bits << 6) | base64_value;
|
||||||
|
sextet_count++;
|
||||||
|
|
||||||
|
// When a sextet is complete, decode it into three bytes
|
||||||
|
if (sextet_count == 4) {
|
||||||
|
decoded_data[j++] = (sextet_bits >> 16) & 0xFF;
|
||||||
|
decoded_data[j++] = (sextet_bits >> 8) & 0xFF;
|
||||||
|
decoded_data[j++] = sextet_bits & 0xFF;
|
||||||
|
sextet_bits = 0;
|
||||||
|
sextet_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @base64_encode(U8* input, I64 input_length)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
U8 buf[3];
|
||||||
|
I64 c = 0;
|
||||||
|
U8* output = CAlloc(input_length * 2, adam_task);
|
||||||
|
|
||||||
|
for (i = 0; i < input_length; i += 3) {
|
||||||
|
buf[0] = input[i];
|
||||||
|
buf[1] = @t((i + 1 < input_length), input[i + 1], 0);
|
||||||
|
buf[2] = @t((i + 2 < input_length), input[i + 2], 0);
|
||||||
|
|
||||||
|
output[c++] = @base64_chars[(buf[0] & 0xfc) >> 2];
|
||||||
|
output[c++] = @base64_chars[((buf[0] & 0x03) << 4) + ((buf[1] & 0xf0) >> 4)];
|
||||||
|
output[c++] = @t((i + 1 < input_length), @base64_chars[((buf[1] & 0x0f) << 2) + ((buf[2] & 0xc0) >> 6)], '=');
|
||||||
|
output[c++] = @t((i + 2 < input_length), @base64_chars[buf[2] & 0x3f], '=');
|
||||||
|
}
|
||||||
|
|
||||||
|
output[c] = '\0';
|
||||||
|
return output;
|
||||||
|
}
|
628
System/Libraries/Http.HC
Normal file
628
System/Libraries/Http.HC
Normal file
|
@ -0,0 +1,628 @@
|
||||||
|
#define HTTP_TMP_DIRECTORY "B:/Tmp"
|
||||||
|
#define HTTP_CACHE_DIRECTORY "B:/Tmp/Cache"
|
||||||
|
#define HTTP_FETCH_BUFFER_SIZE 1024 << 15
|
||||||
|
|
||||||
|
#define SEDECIM_USER_AGENT_STRING "Mozilla/5.0 (compatible; Sedecim/1.0; TempleOS) (KHTML, like Gecko)"
|
||||||
|
|
||||||
|
class @http_buffer
|
||||||
|
{
|
||||||
|
I64 length;
|
||||||
|
U8* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @http_status
|
||||||
|
{
|
||||||
|
U8* protocol;
|
||||||
|
I64 code;
|
||||||
|
U8* text;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @http_response
|
||||||
|
{
|
||||||
|
I64 state;
|
||||||
|
TlsSocket* s;
|
||||||
|
@http_status status;
|
||||||
|
JsonObject* headers;
|
||||||
|
@http_buffer body;
|
||||||
|
Bool headers_parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @http_url
|
||||||
|
{
|
||||||
|
U8* scheme;
|
||||||
|
U8* host;
|
||||||
|
I64 port;
|
||||||
|
U8* path;
|
||||||
|
U8* query;
|
||||||
|
U8* fragment;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @http_request
|
||||||
|
{
|
||||||
|
@http_url* url;
|
||||||
|
U8* buf;
|
||||||
|
U8* data;
|
||||||
|
I64 type;
|
||||||
|
JsonObject* headers;
|
||||||
|
@http_response* response;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HttpResponse @http_response
|
||||||
|
#define HttpUrl @http_url
|
||||||
|
|
||||||
|
#define HTTP_FETCH_BUFFER_SIZE 16777216
|
||||||
|
|
||||||
|
#define HTTP_PARSE_SCHEME 0
|
||||||
|
#define HTTP_PARSE_SCHEME_FS 1
|
||||||
|
#define HTTP_PARSE_HOST 2
|
||||||
|
#define HTTP_PARSE_PORT 3
|
||||||
|
#define HTTP_PARSE_PATH 4
|
||||||
|
#define HTTP_PARSE_QUERY 5
|
||||||
|
#define HTTP_PARSE_FRAGMENT 6
|
||||||
|
|
||||||
|
#define HTTP_MIN_REQUEST_BUFFER_SIZE 16384
|
||||||
|
#define HTTP_PARSE_URL_FIFO_SIZE 1024
|
||||||
|
|
||||||
|
#define HTTP_REQ_GET 0
|
||||||
|
#define HTTP_REQ_HEAD 1
|
||||||
|
#define HTTP_REQ_POST 2
|
||||||
|
#define HTTP_REQ_PUT 3
|
||||||
|
|
||||||
|
#define HTTP_STATE_UNSENT 0
|
||||||
|
#define HTTP_STATE_OPENED 1
|
||||||
|
#define HTTP_STATE_HEADERS_RECEIVED 2
|
||||||
|
#define HTTP_STATE_LOADING 3
|
||||||
|
#define HTTP_STATE_DONE 4
|
||||||
|
|
||||||
|
U8* @http_string_from_fifo(CFifoU8* f)
|
||||||
|
{
|
||||||
|
U8 ch;
|
||||||
|
I64 i = 0;
|
||||||
|
U8* str = CAlloc(FifoU8Cnt(f) + 1, adam_task);
|
||||||
|
while (FifoU8Cnt(f)) {
|
||||||
|
FifoU8Rem(f, &ch);
|
||||||
|
str[i] = ch;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
FifoU8Flush(f);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @http_free_url(@http_url* url)
|
||||||
|
{
|
||||||
|
if (!url)
|
||||||
|
return;
|
||||||
|
if (url->scheme)
|
||||||
|
Free(url->scheme);
|
||||||
|
if (url->host)
|
||||||
|
Free(url->host);
|
||||||
|
if (url->path)
|
||||||
|
Free(url->path);
|
||||||
|
if (url->query)
|
||||||
|
Free(url->query);
|
||||||
|
if (url->fragment)
|
||||||
|
Free(url->fragment);
|
||||||
|
Free(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @http_free_response(@http_response* resp)
|
||||||
|
{
|
||||||
|
if (!resp)
|
||||||
|
return;
|
||||||
|
// FIXME: Free response headers JSON object
|
||||||
|
Free(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@http_url* @http_parse_url(U8* str)
|
||||||
|
{
|
||||||
|
if (!str)
|
||||||
|
return NULL;
|
||||||
|
U8* buf = NULL;
|
||||||
|
U8 hex[3];
|
||||||
|
I64 i = 0;
|
||||||
|
I64 state = HTTP_PARSE_SCHEME;
|
||||||
|
CFifoU8* consume_fifo = FifoU8New(HTTP_PARSE_URL_FIFO_SIZE, adam_task);
|
||||||
|
@http_url* url = CAlloc(sizeof(@http_url), adam_task);
|
||||||
|
while (1) {
|
||||||
|
switch (str[i]) {
|
||||||
|
case 0:
|
||||||
|
switch (state) {
|
||||||
|
case HTTP_PARSE_HOST:
|
||||||
|
url->host = @http_string_from_fifo(consume_fifo);
|
||||||
|
url->path = StrNew("/", adam_task);
|
||||||
|
goto done_parsing_url;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_PORT:
|
||||||
|
buf = @http_string_from_fifo(consume_fifo);
|
||||||
|
url->port = Str2I64(buf);
|
||||||
|
Free(buf);
|
||||||
|
url->path = StrNew("/", adam_task);
|
||||||
|
goto done_parsing_url;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_PATH:
|
||||||
|
url->path = @http_string_from_fifo(consume_fifo);
|
||||||
|
goto done_parsing_url;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_QUERY:
|
||||||
|
url->query = @http_string_from_fifo(consume_fifo);
|
||||||
|
goto done_parsing_url;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_FRAGMENT:
|
||||||
|
url->fragment = @http_string_from_fifo(consume_fifo);
|
||||||
|
goto done_parsing_url;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
goto done_parsing_url;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '#':
|
||||||
|
switch (state) {
|
||||||
|
case HTTP_PARSE_PATH:
|
||||||
|
url->path = @http_string_from_fifo(consume_fifo);
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
state = HTTP_PARSE_FRAGMENT;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_QUERY:
|
||||||
|
url->query = @http_string_from_fifo(consume_fifo);
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
state = HTTP_PARSE_FRAGMENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
switch (state) {
|
||||||
|
case HTTP_PARSE_PATH:
|
||||||
|
url->path = @http_string_from_fifo(consume_fifo);
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
state = HTTP_PARSE_QUERY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
switch (state) {
|
||||||
|
case HTTP_PARSE_SCHEME:
|
||||||
|
state = HTTP_PARSE_SCHEME_FS;
|
||||||
|
goto keep_consuming_url_chars;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_SCHEME_FS:
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
url->scheme = @http_string_from_fifo(consume_fifo);
|
||||||
|
if (!StrCmp(url->scheme, "http://"))
|
||||||
|
url->port = 80;
|
||||||
|
if (!StrCmp(url->scheme, "https://"))
|
||||||
|
url->port = 443;
|
||||||
|
state = HTTP_PARSE_HOST;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_HOST:
|
||||||
|
url->host = @http_string_from_fifo(consume_fifo);
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
state = HTTP_PARSE_PATH;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_PORT:
|
||||||
|
buf = @http_string_from_fifo(consume_fifo);
|
||||||
|
url->port = Str2I64(buf);
|
||||||
|
Free(buf);
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
state = HTTP_PARSE_PATH;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_PATH:
|
||||||
|
goto keep_consuming_url_chars;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
switch (state) {
|
||||||
|
case HTTP_PARSE_SCHEME:
|
||||||
|
case HTTP_PARSE_PATH:
|
||||||
|
case HTTP_PARSE_QUERY:
|
||||||
|
case HTTP_PARSE_FRAGMENT:
|
||||||
|
goto keep_consuming_url_chars;
|
||||||
|
break;
|
||||||
|
case HTTP_PARSE_HOST:
|
||||||
|
url->host = @http_string_from_fifo(consume_fifo);
|
||||||
|
state = HTTP_PARSE_PORT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
keep_consuming_url_chars:
|
||||||
|
switch (state) {
|
||||||
|
case HTTP_PARSE_PATH:
|
||||||
|
case HTTP_PARSE_QUERY:
|
||||||
|
switch (str[i]) {
|
||||||
|
case '0' ... '9':
|
||||||
|
case 'A' ... 'Z':
|
||||||
|
case 'a' ... 'z':
|
||||||
|
case '?':
|
||||||
|
case '&':
|
||||||
|
case '/':
|
||||||
|
case '=':
|
||||||
|
// !'()*-._~
|
||||||
|
case '!':
|
||||||
|
case '\'':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '*':
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '_':
|
||||||
|
case '~':
|
||||||
|
case '%':
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FifoU8Ins(consume_fifo, '%');
|
||||||
|
StrPrint(hex, "%02X", str[i]);
|
||||||
|
FifoU8Ins(consume_fifo, hex[0]);
|
||||||
|
FifoU8Ins(consume_fifo, hex[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FifoU8Ins(consume_fifo, str[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
done_parsing_url:
|
||||||
|
FifoU8Flush(consume_fifo);
|
||||||
|
FifoU8Del(consume_fifo);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @http_parse_response_headers(@http_response* resp, U8* buffer, I64 length)
|
||||||
|
{
|
||||||
|
if (!resp || !buffer || !length)
|
||||||
|
return;
|
||||||
|
U64 response_data_ptr = StrFind("\r\n\r\n", buffer);
|
||||||
|
if (!response_data_ptr)
|
||||||
|
return;
|
||||||
|
resp->body.data = response_data_ptr + 4;
|
||||||
|
resp->body.data[-4] = NULL;
|
||||||
|
JsonObject* headers = Json.CreateObject();
|
||||||
|
U8** lines = NULL;
|
||||||
|
I64 lines_count = 0;
|
||||||
|
I64 i;
|
||||||
|
I64 j;
|
||||||
|
U8* key_ptr = NULL;
|
||||||
|
U8* value_ptr = NULL;
|
||||||
|
lines = String.Split(buffer, '\n', &lines_count);
|
||||||
|
|
||||||
|
U8* resp_protocol = lines[0];
|
||||||
|
U8* resp_status_code = StrFind(" ", resp_protocol) + 1;
|
||||||
|
U8* resp_text = StrFind(" ", resp_status_code + 1);
|
||||||
|
(*StrFind(" ", resp_protocol)) = NULL;
|
||||||
|
(*StrFind(" ", resp_status_code)) = NULL;
|
||||||
|
|
||||||
|
resp->status.protocol = StrNew(resp_protocol, adam_task);
|
||||||
|
resp->status.code = Str2I64(resp_status_code);
|
||||||
|
resp->status.text = StrNew(resp_text, adam_task);
|
||||||
|
|
||||||
|
for (i = 1; i < lines_count; i++) {
|
||||||
|
for (j = 0; j < StrLen(lines[i]); j++) {
|
||||||
|
if (lines[i][j] == ':' && lines[i][j + 1] == ' ') {
|
||||||
|
lines[i][j] = NULL;
|
||||||
|
key_ptr = lines[i];
|
||||||
|
value_ptr = lines[i] + j + 2;
|
||||||
|
(*StrFind("\r", value_ptr)) = NULL;
|
||||||
|
Json.Set(headers, key_ptr, StrNew(value_ptr, adam_task), JSON_STRING);
|
||||||
|
goto @http_next_header_line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@http_next_header_line:
|
||||||
|
}
|
||||||
|
resp->headers = headers;
|
||||||
|
resp->headers_parsed = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @http_detect_response_headers(U8* buf, I64 len)
|
||||||
|
{
|
||||||
|
if (len < 4)
|
||||||
|
return FALSE;
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < len - 4; i++) {
|
||||||
|
if (!MemCmp(buf + i, "\r\n\r\n", 4))
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @http_req(@http_request* req)
|
||||||
|
{
|
||||||
|
if (!req)
|
||||||
|
return NULL;
|
||||||
|
if (!req->url || !req->buf || !req->response)
|
||||||
|
return NULL;
|
||||||
|
if (!req->url->scheme || !req->url->host || !req->url->path)
|
||||||
|
return NULL;
|
||||||
|
if (req->type == HTTP_REQ_POST && !req->data)
|
||||||
|
return NULL;
|
||||||
|
if (req->type == HTTP_REQ_PUT && !req->data)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
@http_response* resp = req->response;
|
||||||
|
|
||||||
|
U8* buf = NULL;
|
||||||
|
U8* headers_buf = "";
|
||||||
|
I64 cnt = 1;
|
||||||
|
I64 len = NULL;
|
||||||
|
|
||||||
|
buf = CAlloc(HTTP_MIN_REQUEST_BUFFER_SIZE, adam_task);
|
||||||
|
if (req->headers) {
|
||||||
|
headers_buf = CAlloc(HTTP_MIN_REQUEST_BUFFER_SIZE, adam_task);
|
||||||
|
JsonKey* key = req->headers->keys;
|
||||||
|
while (key) {
|
||||||
|
StrPrint(headers_buf + StrLen(headers_buf), "%s: %s\r\n", key->name, key->value);
|
||||||
|
key = key->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (req->type) {
|
||||||
|
case HTTP_REQ_GET:
|
||||||
|
StrPrint(buf,
|
||||||
|
"GET %s%s HTTP/1.0\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"%s"
|
||||||
|
"User-Agent: " SEDECIM_USER_AGENT_STRING
|
||||||
|
"\r\n\r\n",
|
||||||
|
req->url->path, req->url->query, req->url->host, headers_buf);
|
||||||
|
break;
|
||||||
|
case HTTP_REQ_HEAD:
|
||||||
|
StrPrint(buf,
|
||||||
|
"HEAD %s%s HTTP/1.0\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"%s"
|
||||||
|
"User-Agent: " SEDECIM_USER_AGENT_STRING
|
||||||
|
"\r\n\r\n",
|
||||||
|
req->url->path, req->url->query, req->url->host, headers_buf);
|
||||||
|
break;
|
||||||
|
case HTTP_REQ_POST:
|
||||||
|
StrPrint(buf,
|
||||||
|
"POST %s%s HTTP/1.0\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"%s"
|
||||||
|
"User-Agent: " SEDECIM_USER_AGENT_STRING
|
||||||
|
"\r\n"
|
||||||
|
"Content-Length: %d\r\n\r\n",
|
||||||
|
req->url->path, req->url->query, req->url->host, headers_buf,
|
||||||
|
StrLen(req->data));
|
||||||
|
StrPrint(buf + StrLen(buf), req->data);
|
||||||
|
break;
|
||||||
|
case HTTP_REQ_PUT:
|
||||||
|
StrPrint(buf,
|
||||||
|
"PUT %s%s HTTP/1.0\r\n"
|
||||||
|
"Host: %s\r\n"
|
||||||
|
"%s"
|
||||||
|
"User-Agent: " SEDECIM_USER_AGENT_STRING
|
||||||
|
"\r\n"
|
||||||
|
"Content-Length: %d\r\n\r\n",
|
||||||
|
req->url->path, req->url->query, req->url->host, headers_buf,
|
||||||
|
StrLen(req->data));
|
||||||
|
StrPrint(buf + StrLen(buf), req->data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TlsSocket* s = NULL;
|
||||||
|
resp->s = NULL;
|
||||||
|
|
||||||
|
if (!StrCmp(req->url->scheme, "http://")) {
|
||||||
|
s = @tcp_socket_create(req->url->host, req->url->port);
|
||||||
|
resp->s = s;
|
||||||
|
while (s->state != TCP_SOCKET_STATE_ESTABLISHED)
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StrCmp(req->url->scheme, "https://")) {
|
||||||
|
s = @tls_socket_create(req->url->host, req->url->port);
|
||||||
|
resp->s = s;
|
||||||
|
while (!@tls_established(s->ctx))
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
resp->state = HTTP_STATE_OPENED;
|
||||||
|
s->send(buf, StrLen(buf));
|
||||||
|
while (cnt || s->state != TCP_SOCKET_STATE_CLOSED) {
|
||||||
|
cnt = s->receive(req->buf + len, 1024);
|
||||||
|
len += cnt;
|
||||||
|
switch (resp->state) {
|
||||||
|
case HTTP_STATE_LOADING:
|
||||||
|
resp->body.length += cnt;
|
||||||
|
break;
|
||||||
|
case HTTP_STATE_HEADERS_RECEIVED:
|
||||||
|
resp->body.length = (req->buf + len) - resp->body.data;
|
||||||
|
resp->state = HTTP_STATE_LOADING;
|
||||||
|
break;
|
||||||
|
case HTTP_STATE_OPENED:
|
||||||
|
if (@http_detect_response_headers(req->buf, len)) {
|
||||||
|
@http_parse_response_headers(resp, req->buf, len);
|
||||||
|
resp->state = HTTP_STATE_HEADERS_RECEIVED;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Sleep(1);
|
||||||
|
}
|
||||||
|
if (!resp->headers_parsed)
|
||||||
|
@http_parse_response_headers(resp, req->buf, len);
|
||||||
|
resp->state = HTTP_STATE_DONE;
|
||||||
|
req->buf[len] = NULL;
|
||||||
|
Free(buf);
|
||||||
|
if (StrLen(headers_buf) > 0) {
|
||||||
|
Free(headers_buf);
|
||||||
|
}
|
||||||
|
s->close();
|
||||||
|
resp->s = NULL;
|
||||||
|
Free(req);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @http_scheme_is_https(@http_url* url)
|
||||||
|
{
|
||||||
|
if (!url || !url->scheme)
|
||||||
|
return FALSE;
|
||||||
|
return !MemCmp(url->scheme, "https", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@http_response* @http_get(@http_url* url, U8* buf, U64 return_req = NULL, JsonObject* headers = NULL)
|
||||||
|
{
|
||||||
|
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
|
||||||
|
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
|
||||||
|
if (return_req)
|
||||||
|
MemCpy(return_req, &req, sizeof(U64));
|
||||||
|
req->url = url;
|
||||||
|
req->buf = buf;
|
||||||
|
req->type = HTTP_REQ_GET;
|
||||||
|
req->headers = headers;
|
||||||
|
req->response = resp;
|
||||||
|
Spawn(&@http_req, req, "HTTPGetRequest");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@http_response* @http_head(@http_url* url, U8* buf, JsonObject* headers = NULL)
|
||||||
|
{
|
||||||
|
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
|
||||||
|
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
|
||||||
|
req->url = url;
|
||||||
|
req->buf = buf;
|
||||||
|
req->type = HTTP_REQ_HEAD;
|
||||||
|
req->headers = headers;
|
||||||
|
req->response = resp;
|
||||||
|
Spawn(&@http_req, req, "HTTPHeadRequest");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@http_response* @http_post(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL)
|
||||||
|
{
|
||||||
|
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
|
||||||
|
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
|
||||||
|
req->url = url;
|
||||||
|
req->buf = buf;
|
||||||
|
req->type = HTTP_REQ_POST;
|
||||||
|
req->headers = headers;
|
||||||
|
req->data = data;
|
||||||
|
req->response = resp;
|
||||||
|
Spawn(&@http_req, req, "HTTPPostRequest");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@http_response* @http_put(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL)
|
||||||
|
{
|
||||||
|
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
|
||||||
|
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
|
||||||
|
req->url = url;
|
||||||
|
req->buf = buf;
|
||||||
|
req->type = HTTP_REQ_PUT;
|
||||||
|
req->headers = headers;
|
||||||
|
req->data = data;
|
||||||
|
req->response = resp;
|
||||||
|
Spawn(&@http_req, req, "HTTPPutRequest");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
class @http
|
||||||
|
{
|
||||||
|
@http_response* (*Get)(@http_url* url, U8* buf, U64* req = NULL, JsonObject* headers = NULL);
|
||||||
|
@http_response* (*Head)(@http_url* url, U8* buf, JsonObject* headers = NULL);
|
||||||
|
@http_response* (*Post)(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL);
|
||||||
|
@http_response* (*Put)(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
@http Http;
|
||||||
|
|
||||||
|
Http.Get = &@http_get;
|
||||||
|
Http.Head = &@http_head;
|
||||||
|
Http.Post = &@http_post;
|
||||||
|
Http.Put = &@http_put;
|
||||||
|
|
||||||
|
Bool @http_is_resource_cached(U8* src, U8* cache_directory = HTTP_CACHE_DIRECTORY)
|
||||||
|
{
|
||||||
|
U8 buf[512];
|
||||||
|
U8* src_md5 = md5_string(src, StrLen(src));
|
||||||
|
StrCpy(buf, cache_directory);
|
||||||
|
StrPrint(buf + StrLen(buf), "/%s", src_md5);
|
||||||
|
Free(src_md5);
|
||||||
|
return FileFind(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @http_cache_resource(U8* src, U8* data, I64 size, U8* cache_directory = HTTP_CACHE_DIRECTORY)
|
||||||
|
{
|
||||||
|
U8 buf[512];
|
||||||
|
U8* src_md5 = md5_string(src, StrLen(src));
|
||||||
|
StrCpy(buf, cache_directory);
|
||||||
|
StrPrint(buf + StrLen(buf), "/%s", src_md5);
|
||||||
|
Free(src_md5);
|
||||||
|
FileWrite(buf, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @http_get_cached_resource_filename(U8* src, U8* cache_directory = HTTP_CACHE_DIRECTORY)
|
||||||
|
{
|
||||||
|
U8* buf = CAlloc(512, adam_task);
|
||||||
|
U8* src_md5 = md5_string(src, StrLen(src));
|
||||||
|
StrCpy(buf, cache_directory);
|
||||||
|
StrPrint(buf + StrLen(buf), "/%s", src_md5);
|
||||||
|
Free(src_md5);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @http_init_tmp_and_cache_directories()
|
||||||
|
{
|
||||||
|
if (!FileFind(HTTP_TMP_DIRECTORY))
|
||||||
|
DirMk(HTTP_TMP_DIRECTORY);
|
||||||
|
if (!FileFind(HTTP_CACHE_DIRECTORY))
|
||||||
|
DirMk(HTTP_CACHE_DIRECTORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@http_response* fetch(U8* url_string, U8* fetch_buffer)
|
||||||
|
{
|
||||||
|
if (!url_string || !fetch_buffer)
|
||||||
|
return NULL;
|
||||||
|
HttpUrl* url = @http_parse_url(url_string);
|
||||||
|
if (!url)
|
||||||
|
return NULL;
|
||||||
|
@http_response* resp = Http.Get(url, fetch_buffer);
|
||||||
|
while (resp->state != HTTP_STATE_DONE)
|
||||||
|
Sleep(1);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 curl(U8* url_string)
|
||||||
|
{
|
||||||
|
if (!url_string)
|
||||||
|
return 0;
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
@http_response* resp = fetch(url_string, fetch_buffer);
|
||||||
|
if (!resp)
|
||||||
|
return 0;
|
||||||
|
if (resp->body.length) {
|
||||||
|
U8* buf = resp->body.data;
|
||||||
|
while (*buf) {
|
||||||
|
if (*buf == '\d')
|
||||||
|
"\d\d";
|
||||||
|
else
|
||||||
|
"%c", *buf;
|
||||||
|
++buf;
|
||||||
|
}
|
||||||
|
"\n";
|
||||||
|
}
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return resp->body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 download(U8* path, U8* url_string)
|
||||||
|
{
|
||||||
|
if (!path || !url_string)
|
||||||
|
return 0;
|
||||||
|
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
|
||||||
|
@http_response* resp = fetch(url_string, fetch_buffer);
|
||||||
|
if (!resp)
|
||||||
|
return 0;
|
||||||
|
if (resp->body.length) {
|
||||||
|
FileWrite(path, resp->body.data, resp->body.length);
|
||||||
|
}
|
||||||
|
Free(fetch_buffer);
|
||||||
|
return resp->body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@http_init_tmp_and_cache_directories;
|
1354
System/Libraries/Json.HC
Normal file
1354
System/Libraries/Json.HC
Normal file
File diff suppressed because it is too large
Load diff
44
System/Libraries/Rsa.HC
Normal file
44
System/Libraries/Rsa.HC
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
Silent(1); // This is needed to suppress "Function should return val" warnings for wrappers to non-HolyC functions
|
||||||
|
|
||||||
|
I64 @rsa_import(U8* der_bytes, I64 der_len, U64 key)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = der_bytes;
|
||||||
|
U64 reg RSI rsi = der_len;
|
||||||
|
U64 reg RDX rdx = key;
|
||||||
|
no_warn rdi, rsi, rdx;
|
||||||
|
asm {
|
||||||
|
MOV RAX, RSA_IMPORT
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @rsa_create_signature(U8* sig, I64* siglen, U8* hash, I64 hashlen, U64 key)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = sig;
|
||||||
|
U64 reg RSI rsi = siglen;
|
||||||
|
U64 reg RDX rdx = hash;
|
||||||
|
U64 reg RCX rcx = hashlen;
|
||||||
|
U64 reg R8 r8 = key;
|
||||||
|
no_warn rdi, rsi, rdx, rcx, r8;
|
||||||
|
asm {
|
||||||
|
MOV RAX, RSA_CREATE_SIGNATURE
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @rsa_verify_signature(U8* sig, I64 siglen, U8* hash, I64 hashlen, I32* stat, U64 key)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = sig;
|
||||||
|
U64 reg RSI rsi = siglen;
|
||||||
|
U64 reg RDX rdx = hash;
|
||||||
|
U64 reg RCX rcx = hashlen;
|
||||||
|
U64 reg R8 r8 = stat;
|
||||||
|
U64 reg R9 r9 = key;
|
||||||
|
no_warn rdi, rsi, rdx, rcx, r8, r9;
|
||||||
|
asm {
|
||||||
|
MOV RAX, RSA_VERIFY_SIGNATURE
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Silent(0);
|
233
System/Libraries/Sha256.HC
Normal file
233
System/Libraries/Sha256.HC
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
#define CHUNK_SIZE 64
|
||||||
|
#define TOTAL_LEN_LEN 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ABOUT bool: this file does not use bool in order to be as pre-C99 compatible
|
||||||
|
* as possible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are
|
||||||
|
* reproduced here. When useful for clarification, portions of the pseudo-code
|
||||||
|
* are reproduced here too.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize array of round constants:
|
||||||
|
* (first 32 bits of the fractional parts of the cube roots of the first 64
|
||||||
|
* primes 2..311):
|
||||||
|
*/
|
||||||
|
U32 k[64] = {
|
||||||
|
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
||||||
|
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||||
|
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
||||||
|
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||||
|
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
||||||
|
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||||
|
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||||
|
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||||
|
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
||||||
|
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||||
|
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||||
|
};
|
||||||
|
|
||||||
|
U32 _h[8] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||||
|
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
|
||||||
|
|
||||||
|
class buffer_state {
|
||||||
|
U8* p;
|
||||||
|
I32 len;
|
||||||
|
I32 total_len;
|
||||||
|
I32 single_one_delivered; /* bool */
|
||||||
|
I32 total_len_delivered; /* bool */
|
||||||
|
};
|
||||||
|
|
||||||
|
U32 right_rot(U32 value, U32 count)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Defined behaviour in standard C for all count where 0 < count < 32,
|
||||||
|
* which is what we need here.
|
||||||
|
*/
|
||||||
|
return value >> count | value << (32 - count);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 init_buf_state(buffer_state* state, U8* input, I32 len)
|
||||||
|
{
|
||||||
|
state->p = input;
|
||||||
|
state->len = len;
|
||||||
|
state->total_len = len;
|
||||||
|
state->single_one_delivered = 0;
|
||||||
|
state->total_len_delivered = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return value: bool */
|
||||||
|
I32 calc_chunk(U8* chunk, buffer_state* state)
|
||||||
|
{
|
||||||
|
I32 space_in_chunk;
|
||||||
|
|
||||||
|
if (state->total_len_delivered) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->len >= CHUNK_SIZE) {
|
||||||
|
MemCpy(chunk, state->p, CHUNK_SIZE);
|
||||||
|
state->p += CHUNK_SIZE;
|
||||||
|
state->len -= CHUNK_SIZE;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemCpy(chunk, state->p, state->len);
|
||||||
|
chunk += state->len;
|
||||||
|
space_in_chunk = CHUNK_SIZE - state->len;
|
||||||
|
state->p += state->len;
|
||||||
|
state->len = 0;
|
||||||
|
|
||||||
|
/* If we are here, space_in_chunk is one at minimum. */
|
||||||
|
if (!state->single_one_delivered) {
|
||||||
|
*chunk++ = 0x80;
|
||||||
|
space_in_chunk -= 1;
|
||||||
|
state->single_one_delivered = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now:
|
||||||
|
* - either there is enough space left for the total length, and we can
|
||||||
|
* conclude,
|
||||||
|
* - or there is too little space left, and we have to pad the rest of this
|
||||||
|
* chunk with zeroes. In the latter case, we will conclude at the next
|
||||||
|
* invokation of this function.
|
||||||
|
*/
|
||||||
|
if (space_in_chunk >= TOTAL_LEN_LEN) {
|
||||||
|
I32 left = space_in_chunk - TOTAL_LEN_LEN;
|
||||||
|
I32 len = state->total_len;
|
||||||
|
I32 i;
|
||||||
|
MemSet(chunk, 0x00, left);
|
||||||
|
chunk += left;
|
||||||
|
|
||||||
|
/* Storing of len * 8 as a big endian 64-bit without overflow. */
|
||||||
|
chunk[7] = (len << 3);
|
||||||
|
len >>= 5;
|
||||||
|
for (i = 6; i >= 0; i--) {
|
||||||
|
chunk[i] = len;
|
||||||
|
len >>= 8;
|
||||||
|
}
|
||||||
|
state->total_len_delivered = 1;
|
||||||
|
} else {
|
||||||
|
MemSet(chunk, 0x00, space_in_chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Limitations:
|
||||||
|
* - Since input is a pointer in RAM, the data to hash should be in RAM, which
|
||||||
|
* could be a problem for large data sizes.
|
||||||
|
* - SHA algorithms theoretically operate on bit strings. However, this
|
||||||
|
* implementation has no support for bit string lengths that are not multiples
|
||||||
|
* of eight, and it really operates on arrays of bytes. In particular, the len
|
||||||
|
* parameter is a number of bytes.
|
||||||
|
*/
|
||||||
|
U0 calc_sha_256(U8* hash, U8* input, I32 len)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Note 1: All integers (expect indexes) are 32-bit U32 integers and addition
|
||||||
|
* is calculated modulo 2^32. Note 2: For each round, there is one round
|
||||||
|
* constant k[i] and one entry in the message schedule array w[i], 0 = i = 63
|
||||||
|
* Note 3: The compression function uses 8 working variables, a through h
|
||||||
|
* Note 4: Big-endian convention is used when expressing the constants in this
|
||||||
|
* pseudocode, and when parsing message block data from bytes to words, for
|
||||||
|
* example, the first word of the input message "abc" after padding is
|
||||||
|
* 0x61626380
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize hash values:
|
||||||
|
* (first 32 bits of the fractional parts of the square roots of the first 8
|
||||||
|
* primes 2..19):
|
||||||
|
*/
|
||||||
|
|
||||||
|
U32 h[8];
|
||||||
|
U32 i, j;
|
||||||
|
MemCpy(h, _h, sizeof(U32) * 8);
|
||||||
|
|
||||||
|
/* 512-bit chunks is what we will operate on. */
|
||||||
|
U8 chunk[64];
|
||||||
|
|
||||||
|
buffer_state state;
|
||||||
|
|
||||||
|
init_buf_state(&state, input, len);
|
||||||
|
|
||||||
|
while (calc_chunk(chunk, &state)) {
|
||||||
|
U32 ah[8];
|
||||||
|
|
||||||
|
U8* p = chunk;
|
||||||
|
|
||||||
|
/* Initialize working variables to current hash value: */
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
ah[i] = h[i];
|
||||||
|
|
||||||
|
/* Compression function main loop: */
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
/*
|
||||||
|
* The w-array is really w[64], but since we only need
|
||||||
|
* 16 of them at a time, we save stack by calculating
|
||||||
|
* 16 at a time.
|
||||||
|
*
|
||||||
|
* This optimization was not there initially and the
|
||||||
|
* rest of the comments about w[64] are kept in their
|
||||||
|
* initial state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* create a 64-entry message schedule array w[0..63] of 32-bit words
|
||||||
|
* (The initial values in w[0..63] don't matter, so many implementations
|
||||||
|
* zero them here) copy chunk into first 16 words w[0..15] of the message
|
||||||
|
* schedule array
|
||||||
|
*/
|
||||||
|
U32 w[16];
|
||||||
|
|
||||||
|
U32 s0, s1;
|
||||||
|
|
||||||
|
for (j = 0; j < 16; j++) {
|
||||||
|
if (i == 0) {
|
||||||
|
w[j] = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
|
||||||
|
p += 4;
|
||||||
|
} else {
|
||||||
|
/* Extend the first 16 words into the remaining 48 words w[16..63] of
|
||||||
|
* the message schedule array: */
|
||||||
|
s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ (w[(j + 1) & 0xf] >> 3);
|
||||||
|
s1 = right_rot(w[(j + 14) & 0xf], 17) ^ right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10);
|
||||||
|
w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1;
|
||||||
|
}
|
||||||
|
s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
|
||||||
|
U32 ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
|
||||||
|
U32 temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j];
|
||||||
|
s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
|
||||||
|
U32 maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
|
||||||
|
U32 temp2 = s0 + maj;
|
||||||
|
|
||||||
|
ah[7] = ah[6];
|
||||||
|
ah[6] = ah[5];
|
||||||
|
ah[5] = ah[4];
|
||||||
|
ah[4] = ah[3] + temp1;
|
||||||
|
ah[3] = ah[2];
|
||||||
|
ah[2] = ah[1];
|
||||||
|
ah[1] = ah[0];
|
||||||
|
ah[0] = temp1 + temp2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add the compressed chunk to the current hash value: */
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
h[i] += ah[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Produce the final hash value (big-endian): */
|
||||||
|
for (i = 0, j = 0; i < 8; i++) {
|
||||||
|
hash[j++] = (h[i] >> 24);
|
||||||
|
hash[j++] = (h[i] >> 16);
|
||||||
|
hash[j++] = (h[i] >> 8);
|
||||||
|
hash[j++] = h[i];
|
||||||
|
}
|
||||||
|
}
|
133
System/Libraries/String.HC
Normal file
133
System/Libraries/String.HC
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
U0 @string_append(U8* dst, U8* fmt, ...)
|
||||||
|
{
|
||||||
|
U8* buf;
|
||||||
|
if (argc) {
|
||||||
|
buf = StrPrintJoin(NULL, fmt, argc, argv);
|
||||||
|
} else {
|
||||||
|
buf = StrNew(fmt, adam_task);
|
||||||
|
}
|
||||||
|
U8* src = buf;
|
||||||
|
StrCpy(dst + StrLen(dst), src);
|
||||||
|
Free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @string_is_number(U8* s)
|
||||||
|
{
|
||||||
|
while (*s) {
|
||||||
|
switch (*s) {
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '0' ... '9':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return FALSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @string_begins_with(U8* fragment, U8* str)
|
||||||
|
{
|
||||||
|
if (!fragment || !str)
|
||||||
|
return FALSE;
|
||||||
|
if (StrLen(fragment) > StrLen(str))
|
||||||
|
return FALSE;
|
||||||
|
return !MemCmp(fragment, str, StrLen(fragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @string_ends_with(U8* fragment, U8* str)
|
||||||
|
{
|
||||||
|
if (!fragment || !str)
|
||||||
|
return FALSE;
|
||||||
|
if (StrLen(fragment) > StrLen(str))
|
||||||
|
return FALSE;
|
||||||
|
return !MemCmp(fragment, str + StrLen(str) - StrLen(fragment), StrLen(fragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @string_replace(U8* s, U8* oldW, U8* newW)
|
||||||
|
{
|
||||||
|
if (!StrFind(oldW, s)) {
|
||||||
|
return StrNew(s, adam_task);
|
||||||
|
}
|
||||||
|
U8* result;
|
||||||
|
I64 i, cnt = 0;
|
||||||
|
I64 newWlen = StrLen(newW);
|
||||||
|
I64 oldWlen = StrLen(oldW);
|
||||||
|
for (i = 0; s[i] != NULL; i++) {
|
||||||
|
if (StrFind(oldW, &s[i]) == &s[i]) {
|
||||||
|
cnt++;
|
||||||
|
|
||||||
|
i += oldWlen - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = MAlloc(i + cnt * (newWlen - oldWlen) + 1, adam_task);
|
||||||
|
i = 0;
|
||||||
|
while (*s) {
|
||||||
|
if (StrFind(oldW, s) == s) {
|
||||||
|
StrCpy(&result[i], newW);
|
||||||
|
i += newWlen;
|
||||||
|
s += oldWlen;
|
||||||
|
} else
|
||||||
|
result[i++] = *s++;
|
||||||
|
}
|
||||||
|
result[i] = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8** @string_split(U8* s, U8 ch = '\n', I64* cnt)
|
||||||
|
{
|
||||||
|
U8 check_buf[4];
|
||||||
|
StrPrint(check_buf, "%c", ch);
|
||||||
|
if (!StrFind(check_buf, s)) {
|
||||||
|
U8** same_arr = CAlloc(sizeof(U8*) * 1, adam_task);
|
||||||
|
same_arr[0] = s;
|
||||||
|
*cnt = 1;
|
||||||
|
return same_arr;
|
||||||
|
}
|
||||||
|
U8* p = s;
|
||||||
|
cnt[0] = 0;
|
||||||
|
while (*p) {
|
||||||
|
if (*p == ch)
|
||||||
|
cnt[0]++;
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (!(cnt[0]))
|
||||||
|
return NULL;
|
||||||
|
cnt[0]++;
|
||||||
|
I64 i = -1;
|
||||||
|
U8** arr = CAlloc(sizeof(U8*) * cnt[0], adam_task);
|
||||||
|
p = s;
|
||||||
|
while (*p) {
|
||||||
|
if (*p == ch || i < 0) {
|
||||||
|
i++;
|
||||||
|
arr[i] = p;
|
||||||
|
if (*p == ch) {
|
||||||
|
arr[i]++;
|
||||||
|
*p = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
class @string
|
||||||
|
{
|
||||||
|
U0(*Append)
|
||||||
|
(U8 * dst, U8 * fmt, ...);
|
||||||
|
Bool (*BeginsWith)(U8* fragment, U8* str);
|
||||||
|
Bool (*EndsWith)(U8* fragment, U8* str);
|
||||||
|
Bool (*IsNumber)(U8* s);
|
||||||
|
U8* (*Replace)(U8* s, U8* oldW, U8* newW);
|
||||||
|
U8** (*Split)(U8* s, U8 ch = '\n', I64 * cnt);
|
||||||
|
};
|
||||||
|
|
||||||
|
@string String;
|
||||||
|
String.Append = &@string_append;
|
||||||
|
String.BeginsWith = &@string_begins_with;
|
||||||
|
String.EndsWith = &@string_ends_with;
|
||||||
|
String.IsNumber = &@string_is_number;
|
||||||
|
String.Replace = &@string_replace;
|
||||||
|
String.Split = &@string_split;
|
115
System/Libraries/Tlse.HC
Normal file
115
System/Libraries/Tlse.HC
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
#define TLS_V12 0x0303
|
||||||
|
|
||||||
|
Silent(1); // This is needed to suppress "Function should return val" warnings for wrappers to non-HolyC functions
|
||||||
|
|
||||||
|
U64 @tls_create_context(U8 is_server, U16 version)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = is_server;
|
||||||
|
U64 reg RSI rsi = version;
|
||||||
|
no_warn rdi, rsi;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_CREATE_CONTEXT
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I32 @tls_sni_set(U64 context, U8* sni)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
U64 reg RSI rsi = sni;
|
||||||
|
no_warn rdi, rsi;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_SNI_SET
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I32 @tls_client_connect(U64 context)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
no_warn rdi;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_CLIENT_CONNECT
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @tls_get_write_buffer(U64 context, U32* outlen)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
U64 reg RSI rsi = outlen;
|
||||||
|
no_warn rdi, rsi;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_GET_WRITE_BUFFER
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @tls_buffer_clear(U64 context)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
no_warn rdi;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_BUFFER_CLEAR
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I32 @tls_connection_status(U64 context)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
no_warn rdi;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_CONNECTION_STATUS
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @tls_consume_stream(U64 context, U8* buf, I32 buf_len, U64 certificate_verify)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
U64 reg RSI rsi = buf;
|
||||||
|
U64 reg RDX rdx = buf_len;
|
||||||
|
U64 reg RCX rcx = certificate_verify;
|
||||||
|
no_warn rdi, rsi, rdx, rcx;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_CONSUME_STREAM
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I32 @tls_read(U64 context, U8* buf, U32 size)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
U64 reg RSI rsi = buf;
|
||||||
|
U64 reg RDX rdx = size;
|
||||||
|
no_warn rdi, rsi, rdx;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_READ
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I32 @tls_write(U64 context, U8* data, U32 len)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
U64 reg RSI rsi = data;
|
||||||
|
U64 reg RDX rdx = len;
|
||||||
|
no_warn rdi, rsi, rdx;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_WRITE
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I32 @tls_established(U64 context)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = context;
|
||||||
|
no_warn rdi;
|
||||||
|
asm {
|
||||||
|
MOV RAX, TLS_ESTABLISHED
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Silent(0);
|
56
System/MakeSystem.HC
Normal file
56
System/MakeSystem.HC
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/* clang-format off */
|
||||||
|
|
||||||
|
DocMax(adam_task);
|
||||||
|
WinMax(adam_task);
|
||||||
|
WinToTop(adam_task);
|
||||||
|
|
||||||
|
#include "Setup/Environment";
|
||||||
|
#include "Drivers/Virtio-blk";
|
||||||
|
|
||||||
|
// FFI support files
|
||||||
|
#include "FFI/Base";
|
||||||
|
#include "FFI/LibC";
|
||||||
|
#include "FFI/New";
|
||||||
|
#include "FFI/ELF64";
|
||||||
|
|
||||||
|
// stb_image library
|
||||||
|
#include "Utilities/Image";
|
||||||
|
load_elf("M:/build/bin/image");
|
||||||
|
|
||||||
|
// Jakt support files
|
||||||
|
#include "Jakt/OS";
|
||||||
|
#include "Jakt/IOPort";
|
||||||
|
#include "Jakt/PCI";
|
||||||
|
#include "Jakt/Time";
|
||||||
|
|
||||||
|
#include "Libraries/Tlse";
|
||||||
|
load_elf("M:/build/bin/tlse");
|
||||||
|
|
||||||
|
// Networking APIs
|
||||||
|
#include "Api/Dns.HC";
|
||||||
|
#include "Api/Icmp.HC";
|
||||||
|
#include "Api/Ipv4.HC";
|
||||||
|
#include "Api/MD5.HC";
|
||||||
|
#include "Api/NetInfo.HC";
|
||||||
|
#include "Api/Tcp.HC";
|
||||||
|
#include "Api/Tls.HC";
|
||||||
|
|
||||||
|
// Libraries
|
||||||
|
#include "Libraries/Base64";
|
||||||
|
#include "Libraries/Json";
|
||||||
|
#include "Libraries/Rsa";
|
||||||
|
#include "Libraries/Sha256";
|
||||||
|
#include "Libraries/String";
|
||||||
|
#include "Libraries/Http";
|
||||||
|
|
||||||
|
load_elf("M:/build/bin/net");
|
||||||
|
|
||||||
|
// Networking Utilities
|
||||||
|
#include "Utilities/Dns";
|
||||||
|
#include "Utilities/NetRep";
|
||||||
|
#include "Utilities/Ping";
|
||||||
|
#include "Utilities/Time";
|
||||||
|
|
||||||
|
Spawn(_start, , "Net Task");
|
||||||
|
|
||||||
|
/* clang-format on */
|
98
System/Setup/Environment.HC
Normal file
98
System/Setup/Environment.HC
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
AutoComplete(0);
|
||||||
|
|
||||||
|
U0 @patch_call_rel32(U32 from, U32 to)
|
||||||
|
{
|
||||||
|
*(from(U8*)) = 0xE8;
|
||||||
|
*((from + 1)(I32*)) = to - from - 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @patch_jmp_rel32(U32 from, U32 to)
|
||||||
|
{
|
||||||
|
*(from(U8*)) = 0xE9;
|
||||||
|
*((from + 1)(I32*)) = to - from - 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
CMemBlk* ShrinkMemBlkByPags(CMemBlk* from, I64 count)
|
||||||
|
{
|
||||||
|
from->pags -= count;
|
||||||
|
U64 to = from;
|
||||||
|
to += count * MEM_PAG_SIZE;
|
||||||
|
MemCpy(to, from, MEM_PAG_SIZE);
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @sse_enable()
|
||||||
|
{
|
||||||
|
/* clang-format off */
|
||||||
|
asm
|
||||||
|
{
|
||||||
|
MOV_EAX_CR0
|
||||||
|
AND AX, 0xFFFB // clear coprocessor emulation CR0.EM
|
||||||
|
OR AX, 0x2 // set coprocessor monitoring CR0.MP
|
||||||
|
MOV_CR0_EAX
|
||||||
|
MOV_EAX_CR4
|
||||||
|
OR AX, 3 << 9 // set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
|
||||||
|
MOV_CR4_EAX
|
||||||
|
}
|
||||||
|
/* clang-format on */
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @sse_enable_on_all_cores()
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
for (i = 1; i < mp_cnt; i++)
|
||||||
|
Spawn(&@sse_enable, , , i);
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @t(Bool _condition, I64 _true, I64 _false)
|
||||||
|
{
|
||||||
|
if (_condition)
|
||||||
|
return _true;
|
||||||
|
return _false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before doing anything else, we:
|
||||||
|
|
||||||
|
// 1. Mark memory in code heap below 0x1000000 as used.
|
||||||
|
sys_code_bp->mem_free_lst->next->pags = 0;
|
||||||
|
|
||||||
|
// 2. Free up 64MB at bottom of code heap for non-HolyC programs
|
||||||
|
sys_code_bp->mem_free_lst = ShrinkMemBlkByPags(sys_code_bp->mem_free_lst, 131072);
|
||||||
|
|
||||||
|
// 3. Enable SSE
|
||||||
|
@sse_enable;
|
||||||
|
|
||||||
|
U0 dd() { DocDump(adam_task->put_doc); }
|
||||||
|
//@patch_jmp_rel32(&Dbg2, &Reboot); // Reboot instead of crashing to the debugger
|
||||||
|
U0 NoBeep(I8, Bool) {};
|
||||||
|
@patch_jmp_rel32(&Beep, &NoBeep); // Don't delay on beep when entering debugger
|
||||||
|
|
||||||
|
Bool BlkDevLock2(CBlkDev* bd)
|
||||||
|
{
|
||||||
|
BlkDevChk(bd);
|
||||||
|
while (bd->lock_fwding)
|
||||||
|
bd = bd->lock_fwding;
|
||||||
|
if (!Bt(&bd->locked_flags, BDlf_LOCKED) || bd->owning_task != Fs) {
|
||||||
|
while (LBts(&bd->locked_flags, BDlf_LOCKED))
|
||||||
|
Sleep(Rand * 10);
|
||||||
|
bd->owning_task = Fs;
|
||||||
|
return TRUE;
|
||||||
|
} else
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool DrvLock2(CDrv* dv)
|
||||||
|
{
|
||||||
|
DrvChk(dv);
|
||||||
|
BlkDevLock2(dv->bd);
|
||||||
|
if (!Bt(&dv->locked_flags, DVlf_LOCKED) || dv->owning_task != Fs) {
|
||||||
|
while (LBts(&dv->locked_flags, DVlf_LOCKED))
|
||||||
|
Sleep(Rand * 10);
|
||||||
|
dv->owning_task = Fs;
|
||||||
|
return TRUE;
|
||||||
|
} else
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@patch_jmp_rel32(&BlkDevLock, &BlkDevLock2); // Patch BlkDevLock so we don't deadlock on multiple tasks reading from virtio disk
|
||||||
|
@patch_jmp_rel32(&DrvLock, &DrvLock2); // Patch DrvLock so we don't deadlock on multiple tasks reading from virtio disk
|
10
System/Utilities/Dns.HC
Normal file
10
System/Utilities/Dns.HC
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
U0 DnsQuery(U8* host)
|
||||||
|
{
|
||||||
|
U32 result = @dns_query(host);
|
||||||
|
if (result == U32_MAX) {
|
||||||
|
"Error looking up host %s\n", host;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
"Query for %s: %d.%d.%d.%d\n", host, result.u8[3], result.u8[2], result.u8[1],
|
||||||
|
result.u8[0];
|
||||||
|
}
|
414
System/Utilities/Image.HC
Normal file
414
System/Utilities/Image.HC
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
Silent(1); // This is needed to suppress "Function should return val" warnings for wrappers to non-HolyC functions
|
||||||
|
|
||||||
|
class @image
|
||||||
|
{
|
||||||
|
CDC* (*FromBuffer)(U8* buffer, I64 len);
|
||||||
|
CDC* (*Load)(U8* filename);
|
||||||
|
CDC* (*Write)(U8* filename, CDC* dc);
|
||||||
|
};
|
||||||
|
|
||||||
|
@image Image;
|
||||||
|
|
||||||
|
class @image_frame
|
||||||
|
{
|
||||||
|
CDC* dc;
|
||||||
|
CSprite* sprite;
|
||||||
|
I64 delay;
|
||||||
|
};
|
||||||
|
|
||||||
|
class @image_collection
|
||||||
|
{
|
||||||
|
@image_frame** frames;
|
||||||
|
I64 count;
|
||||||
|
I64 current;
|
||||||
|
I64 jiffies;
|
||||||
|
I64 index;
|
||||||
|
@image_collection* next;
|
||||||
|
};
|
||||||
|
|
||||||
|
I64 @image_cbgr24_to_4_bit(CBGR24* ptr, Bool dither_probability)
|
||||||
|
{
|
||||||
|
I64 res, k;
|
||||||
|
if (dither_probability) {
|
||||||
|
k = RandU32;
|
||||||
|
if (SqrI64(ptr->r) + SqrI64(ptr->g) + SqrI64(ptr->b) >= 3 * SqrI64(k.u8[0]))
|
||||||
|
res = 8;
|
||||||
|
else
|
||||||
|
res = 0;
|
||||||
|
if (ptr->r >= k.u8[1])
|
||||||
|
res |= RED;
|
||||||
|
if (ptr->g >= k.u8[2])
|
||||||
|
res |= GREEN;
|
||||||
|
if (ptr->b >= k.u8[3])
|
||||||
|
res |= BLUE;
|
||||||
|
} else {
|
||||||
|
if (SqrI64(ptr->r) + SqrI64(ptr->g) + SqrI64(ptr->b) >= SqrI64(0x80)) {
|
||||||
|
res = 8;
|
||||||
|
if (ptr->r >= 0x80)
|
||||||
|
res |= RED;
|
||||||
|
if (ptr->g >= 0x80)
|
||||||
|
res |= GREEN;
|
||||||
|
if (ptr->b >= 0x80)
|
||||||
|
res |= BLUE;
|
||||||
|
} else {
|
||||||
|
res = 0;
|
||||||
|
if (ptr->r >= 0x40)
|
||||||
|
res |= RED;
|
||||||
|
if (ptr->g >= 0x40)
|
||||||
|
res |= GREEN;
|
||||||
|
if (ptr->b >= 0x40)
|
||||||
|
res |= BLUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IMAGE_DITHER_NONE 0
|
||||||
|
#define IMAGE_DITHER_NATIVE 1
|
||||||
|
#define IMAGE_DITHER_FLOYDSTEINBERG 2
|
||||||
|
|
||||||
|
U0 @image_render_4bit_floydstein(U8* buffer, I32 width, I32 height)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = buffer;
|
||||||
|
U64 reg RSI rsi = width;
|
||||||
|
U64 reg RDX rdx = height;
|
||||||
|
no_warn rdi, rsi, rdx;
|
||||||
|
asm {
|
||||||
|
MOV RAX, RENDER_4BIT_FLOYDSTEIN
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CDC* @image_render_16color_native(U8* pixels, I32 x, I32 y, Bool dither)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
I64 j;
|
||||||
|
I64 cnt = 0;
|
||||||
|
CBGR24 cbgr24;
|
||||||
|
CDC* dc = DCNew(x, y);
|
||||||
|
for (i = 0; i < y; i++)
|
||||||
|
for (j = 0; j < x; j++) {
|
||||||
|
cbgr24.r = pixels[cnt];
|
||||||
|
cbgr24.g = pixels[cnt + 1];
|
||||||
|
cbgr24.b = pixels[cnt + 2];
|
||||||
|
if (!pixels[cnt + 3])
|
||||||
|
dc->color = TRANSPARENT;
|
||||||
|
else
|
||||||
|
dc->color = @image_cbgr24_to_4_bit(&cbgr24, dither);
|
||||||
|
GrPlot(dc, j, y - i - 1);
|
||||||
|
cnt += 4;
|
||||||
|
}
|
||||||
|
return dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
CBGR24 @image_palette_std[COLORS_NUM] = {
|
||||||
|
0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
|
||||||
|
0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
|
||||||
|
0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
|
||||||
|
0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF
|
||||||
|
};
|
||||||
|
|
||||||
|
CBGR24 @image_dif_rgb(CBGR24 from, CBGR24 to)
|
||||||
|
{
|
||||||
|
CBGR24 dif;
|
||||||
|
dif.r = to.r - from.r;
|
||||||
|
dif.g = to.g - from.g;
|
||||||
|
dif.b = to.b - from.b;
|
||||||
|
return dif;
|
||||||
|
}
|
||||||
|
|
||||||
|
F64 @image_dist_rgb(CBGR24 from, CBGR24 to)
|
||||||
|
{
|
||||||
|
CBGR24 dif = @image_dif_rgb(from, to);
|
||||||
|
F64 dist = dif.r * dif.r + dif.g * dif.g + dif.b * dif.b;
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @image_get_4bit_color(CBGR24* cbgr24)
|
||||||
|
{
|
||||||
|
F64 dist = -1, tempDist;
|
||||||
|
I64 i;
|
||||||
|
I64 color = TRANSPARENT;
|
||||||
|
for (i = 0; i < COLORS_NUM; i++) {
|
||||||
|
tempDist = @image_dist_rgb(*cbgr24, @image_palette_std[i]);
|
||||||
|
if (tempDist < dist || dist < 0) {
|
||||||
|
dist = tempDist;
|
||||||
|
color = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
CDC* @image_render_16color_floydsteinberg(U8* pixels, I32 width, I32 height)
|
||||||
|
{
|
||||||
|
@image_render_4bit_floydstein(pixels, width, height);
|
||||||
|
I64 i;
|
||||||
|
I64 j;
|
||||||
|
I64 cnt = 0;
|
||||||
|
CBGR24 cbgr24;
|
||||||
|
CDC* dc = DCNew(width, height);
|
||||||
|
for (i = 0; i < height; i++)
|
||||||
|
for (j = 0; j < width; j++) {
|
||||||
|
cbgr24.r = pixels[cnt];
|
||||||
|
cbgr24.g = pixels[cnt + 1];
|
||||||
|
cbgr24.b = pixels[cnt + 2];
|
||||||
|
if (!pixels[cnt + 3])
|
||||||
|
dc->color = TRANSPARENT;
|
||||||
|
else
|
||||||
|
dc->color = @image_get_4bit_color(&cbgr24);
|
||||||
|
GrPlot(dc, j, height - i - 1);
|
||||||
|
cnt += 4;
|
||||||
|
}
|
||||||
|
return dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
CDC* @image_generate_dc_from_pixels(U8* pixels, I32 width, I32 height, Bool dither = IMAGE_DITHER_FLOYDSTEINBERG)
|
||||||
|
{
|
||||||
|
switch (dither) {
|
||||||
|
case IMAGE_DITHER_NONE:
|
||||||
|
case IMAGE_DITHER_NATIVE:
|
||||||
|
return @image_render_16color_native(pixels, width, height, dither);
|
||||||
|
break;
|
||||||
|
case IMAGE_DITHER_FLOYDSTEINBERG:
|
||||||
|
return @image_render_16color_floydsteinberg(pixels, width, height);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @image_load_gif_from_memory(U8* buffer, I64 len, I64** delays, I64* x, I64* y,
|
||||||
|
I64* z)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = buffer;
|
||||||
|
U64 reg RSI rsi = len;
|
||||||
|
U64 reg RDX rdx = delays;
|
||||||
|
U64 reg RCX rcx = x;
|
||||||
|
U64 reg R8 r8 = y;
|
||||||
|
U64 reg R9 r9 = z;
|
||||||
|
no_warn rdi, rsi, rdx, rcx, r8, r9;
|
||||||
|
asm {
|
||||||
|
MOV RAX, IMAGE_LOAD_GIF_FROM_MEMORY
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @stbi_failure_reason()
|
||||||
|
{
|
||||||
|
asm {
|
||||||
|
MOV RAX, STBI_FAILURE_REASON
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I32 @stbi_info_from_memory(U8* buffer, I64 len, I64* x, I64* y, I64* comp)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = buffer;
|
||||||
|
U64 reg RSI rsi = len;
|
||||||
|
U64 reg RDX rdx = x;
|
||||||
|
U64 reg RCX rcx = y;
|
||||||
|
U64 reg R8 r8 = comp;
|
||||||
|
no_warn rdi, rsi, rdx, rcx, r8;
|
||||||
|
asm {
|
||||||
|
MOV RAX, STBI_INFO_FROM_MEMORY
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* @stbi_load_from_memory(U8* buffer, I64 len, I64* x, I64* y,
|
||||||
|
I64* channels_in_file, I64 desired_channels)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = buffer;
|
||||||
|
U64 reg RSI rsi = len;
|
||||||
|
U64 reg RDX rdx = x;
|
||||||
|
U64 reg RCX rcx = y;
|
||||||
|
U64 reg R8 r8 = channels_in_file;
|
||||||
|
U64 reg R9 r9 = desired_channels;
|
||||||
|
no_warn rdi, rsi, rdx, rcx, r8, r9;
|
||||||
|
asm {
|
||||||
|
MOV RAX, STBI_LOAD_FROM_MEMORY
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U32* @stbi_write_png_to_mem(U32* pixels, I32 stride_bytes, I32 x, I32 y, I32 n, I32* out_len)
|
||||||
|
{
|
||||||
|
U64 reg RDI rdi = pixels;
|
||||||
|
U64 reg RSI rsi = stride_bytes;
|
||||||
|
U64 reg RDX rdx = x;
|
||||||
|
U64 reg RCX rcx = y;
|
||||||
|
U64 reg R8 r8 = n;
|
||||||
|
U64 reg R9 r9 = out_len;
|
||||||
|
no_warn rdi, rsi, rdx, rcx, r8, r9;
|
||||||
|
asm {
|
||||||
|
MOV RAX, STBI_WRITE_PNG_TO_MEM
|
||||||
|
CALL RAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CDC* @image_load(U8* filename)
|
||||||
|
{
|
||||||
|
if (!filename || !FileFind(filename)) {
|
||||||
|
PrintErr("Image file not found.\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
I64 len;
|
||||||
|
I32 x;
|
||||||
|
I32 y;
|
||||||
|
I32 comp;
|
||||||
|
U8* buffer = FileRead(filename, &len);
|
||||||
|
I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
|
||||||
|
if (code != 1) {
|
||||||
|
Free(buffer);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
U8* pixels = @stbi_load_from_memory(buffer, len, &x, &y, &comp, 4);
|
||||||
|
Free(buffer);
|
||||||
|
CDC* dc = @image_generate_dc_from_pixels(pixels, x, y);
|
||||||
|
Free(pixels);
|
||||||
|
return dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 @image_rgba_color_table[16] = {
|
||||||
|
0xff000000, 0xffaa0000, 0xff00aa00, 0xffaaaa00,
|
||||||
|
0xff0000aa, 0xffaa00aa, 0xff0055aa, 0xffaaaaaa,
|
||||||
|
0xff555555, 0xffff5555, 0xff55ff55, 0xffffff55,
|
||||||
|
0xff5555ff, 0xffff55ff, 0xff55ffff, 0xffffffff
|
||||||
|
};
|
||||||
|
|
||||||
|
U32 @image_get_rgba_color(I64 color)
|
||||||
|
{
|
||||||
|
if (color > 15)
|
||||||
|
return 0;
|
||||||
|
return @image_rgba_color_table[color];
|
||||||
|
}
|
||||||
|
|
||||||
|
U32* @image_get_rgba_buffer_from_dc_body(CDC* dc)
|
||||||
|
{
|
||||||
|
if (!dc)
|
||||||
|
return NULL;
|
||||||
|
U32* pixels = CAlloc((dc->width * dc->height) * 4, adam_task);
|
||||||
|
I64 x;
|
||||||
|
I64 y;
|
||||||
|
I64 p = 0;
|
||||||
|
for (y = 0; y < dc->height; y++)
|
||||||
|
for (x = 0; x < dc->width; x++)
|
||||||
|
pixels[p++] = @image_get_rgba_color(GrPeek(dc, x, y));
|
||||||
|
return pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @image_write(U8* filename, CDC* dc)
|
||||||
|
{
|
||||||
|
if (!dc) {
|
||||||
|
PrintErr("Device context is NULL.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
I32 out_len;
|
||||||
|
U32* rgba_buffer = @image_get_rgba_buffer_from_dc_body(dc);
|
||||||
|
if (!rgba_buffer) {
|
||||||
|
PrintErr("RGBA buffer is NULL.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
U8* png_buffer = @stbi_write_png_to_mem(rgba_buffer, dc->width * 4, dc->width, dc->height, 4, &out_len);
|
||||||
|
if (!png_buffer) {
|
||||||
|
PrintErr("PNG buffer is NULL.\n");
|
||||||
|
Free(rgba_buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FileWrite(filename, png_buffer, out_len);
|
||||||
|
Free(rgba_buffer);
|
||||||
|
Free(png_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 @image_pixel_flip_rgb_bgr(U32 src)
|
||||||
|
{
|
||||||
|
U32 dst;
|
||||||
|
dst.u8[0] = src.u8[2];
|
||||||
|
dst.u8[1] = src.u8[1];
|
||||||
|
dst.u8[2] = src.u8[0];
|
||||||
|
dst.u8[3] = src.u8[3];
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
CDC* @image_from_buffer(U8* buffer, I64 len)
|
||||||
|
{
|
||||||
|
I32 x = 0;
|
||||||
|
I32 y = 0;
|
||||||
|
U8* pixels = NULL;
|
||||||
|
CDC* dc = NULL;
|
||||||
|
|
||||||
|
I32 comp;
|
||||||
|
I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
|
||||||
|
if (code != 1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
pixels = @stbi_load_from_memory(buffer, len, &x, &y, &comp, 4);
|
||||||
|
if (!pixels)
|
||||||
|
PopUpOk(@stbi_failure_reason);
|
||||||
|
dc = @image_generate_dc_from_pixels(pixels, x, y);
|
||||||
|
Free(pixels);
|
||||||
|
return dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@image_collection* @image_collection_from_buffer(U8* buffer, I64 len)
|
||||||
|
{
|
||||||
|
I64 i;
|
||||||
|
I32* delays;
|
||||||
|
I32 x;
|
||||||
|
I32 y;
|
||||||
|
I32 z;
|
||||||
|
I32 comp;
|
||||||
|
I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
|
||||||
|
if (code != 1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
U64 pixels = @image_load_gif_from_memory(buffer, len, &delays, &x, &y, &z);
|
||||||
|
if (!pixels)
|
||||||
|
PopUpOk(@stbi_failure_reason);
|
||||||
|
if (!z)
|
||||||
|
return NULL; // no frames?
|
||||||
|
@image_collection* collection = CAlloc(sizeof(@image_collection), adam_task);
|
||||||
|
@image_frame* frame;
|
||||||
|
collection->frames = CAlloc(sizeof(@image_frame*) * z, adam_task);
|
||||||
|
collection->count = z;
|
||||||
|
for (i = 0; i < z; i++) {
|
||||||
|
frame = CAlloc(sizeof(@image_frame), adam_task);
|
||||||
|
frame->dc = @image_generate_dc_from_pixels(pixels, x, y);
|
||||||
|
frame->sprite = DC2Sprite(frame->dc);
|
||||||
|
frame->delay = delays[i];
|
||||||
|
collection->frames[i] = frame;
|
||||||
|
pixels += (x * y) * 4;
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image.FromBuffer = &@image_from_buffer;
|
||||||
|
Image.Load = &@image_load;
|
||||||
|
Image.Write = &@image_write;
|
||||||
|
|
||||||
|
Silent(0);
|
||||||
|
|
||||||
|
U0 Screenshot(U8* custom_filename = NULL, Bool output_filename_to_focus_task = FALSE)
|
||||||
|
{
|
||||||
|
CDC* dc = DCScrnCapture;
|
||||||
|
U8 filename[256];
|
||||||
|
CDateStruct ds;
|
||||||
|
if (custom_filename)
|
||||||
|
StrCpy(filename, custom_filename);
|
||||||
|
else {
|
||||||
|
Date2Struct(&ds, Now);
|
||||||
|
StrPrint(filename, "C:/Tmp/ScrnShots/%04d-%02d-%02d-%02d-%02d-%02d.png", ds.year, ds.mon, ds.day_of_mon, ds.hour, ds.min, ds.sec);
|
||||||
|
}
|
||||||
|
Image.Write(filename, dc);
|
||||||
|
DCDel(dc);
|
||||||
|
if (output_filename_to_focus_task)
|
||||||
|
XTalk(sys_focus_task, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
U0 @screenshot_hotkey(I64)
|
||||||
|
{
|
||||||
|
Screenshot("C:/Home/Screenshot.png", TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
CtrlAltCBSet('S', &@screenshot_hotkey, "", , FALSE);
|
22
System/Utilities/NetRep.HC
Normal file
22
System/Utilities/NetRep.HC
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
U0 NetRep()
|
||||||
|
{
|
||||||
|
NetInfoRequest* req = @net_info_request;
|
||||||
|
"MAC address : %02x:%02x:%02x:%02x:%02x:%02x\n", req->mac_address.u8[5], req->mac_address.u8[4],
|
||||||
|
req->mac_address.u8[3], req->mac_address.u8[2],
|
||||||
|
req->mac_address.u8[1], req->mac_address.u8[0];
|
||||||
|
"IPv4 address : %d.%d.%d.%d\n", req->ipv4_address.u8[3], req->ipv4_address.u8[2],
|
||||||
|
req->ipv4_address.u8[1], req->ipv4_address.u8[0];
|
||||||
|
"IPv4 netmask : %d.%d.%d.%d\n", req->ipv4_netmask.u8[3], req->ipv4_netmask.u8[2],
|
||||||
|
req->ipv4_netmask.u8[1], req->ipv4_netmask.u8[0];
|
||||||
|
"IPv4 network : %d.%d.%d.%d\n", req->ipv4_network.u8[3], req->ipv4_network.u8[2],
|
||||||
|
req->ipv4_network.u8[1], req->ipv4_network.u8[0];
|
||||||
|
"IPv4 gateway : %d.%d.%d.%d\n", req->ipv4_gateway.u8[3], req->ipv4_gateway.u8[2],
|
||||||
|
req->ipv4_gateway.u8[1], req->ipv4_gateway.u8[0];
|
||||||
|
"DNS server (port) : %d.%d.%d.%d (%d)\n", req->dns_server_address.u8[3], req->dns_server_address.u8[2],
|
||||||
|
req->dns_server_address.u8[1], req->dns_server_address.u8[0], req->dns_server_port;
|
||||||
|
"RX bytes : %d\n", req->rx_bytes;
|
||||||
|
"RX frames : %d\n", req->rx_frames;
|
||||||
|
"TX bytes : %d\n", req->tx_bytes;
|
||||||
|
"TX frames : %d\n", req->tx_frames;
|
||||||
|
Free(req);
|
||||||
|
}
|
72
System/Utilities/Ping.HC
Normal file
72
System/Utilities/Ping.HC
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
#define PING_ERR_INVALID_HOST 1
|
||||||
|
#define PING_ERR_HOST_NOT_FOUND 2
|
||||||
|
|
||||||
|
#define PING_PAYLOAD_SIZE 56
|
||||||
|
|
||||||
|
I64 @ping_err(I64 code)
|
||||||
|
{
|
||||||
|
switch (code) {
|
||||||
|
case PING_ERR_INVALID_HOST:
|
||||||
|
"Invalid host specified\n";
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
case PING_ERR_HOST_NOT_FOUND:
|
||||||
|
"Host not found\n";
|
||||||
|
return 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
"Unspecified error\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 Ping(U8* host, I64 count = 4)
|
||||||
|
{
|
||||||
|
if (!host)
|
||||||
|
return @ping_err(PING_ERR_INVALID_HOST);
|
||||||
|
if (!StrLen(host))
|
||||||
|
return @ping_err(PING_ERR_INVALID_HOST);
|
||||||
|
|
||||||
|
U32 addr = @dns_query(host);
|
||||||
|
if (addr == U32_MAX)
|
||||||
|
return @ping_err(PING_ERR_HOST_NOT_FOUND);
|
||||||
|
|
||||||
|
U16 iden = (RandU16 * SysTimerRead) & 0xFFFF;
|
||||||
|
I64 start_jiffies;
|
||||||
|
U32 reply = NULL;
|
||||||
|
I64 res = 0;
|
||||||
|
U16 seq = 0;
|
||||||
|
I64 loss = 0;
|
||||||
|
|
||||||
|
IcmpRequest* request = CAlloc(sizeof(IcmpRequest), Fs->code_heap);
|
||||||
|
|
||||||
|
"PING %s (%d.%d.%d.%d): %d data bytes\n",
|
||||||
|
host, addr.u8[3], addr.u8[2], addr.u8[1], addr.u8[0], PING_PAYLOAD_SIZE;
|
||||||
|
|
||||||
|
I64 i;
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
start_jiffies = cnts.jiffies;
|
||||||
|
reply = @icmp_echo_request(addr, iden, seq, request, i);
|
||||||
|
if (!reply) {
|
||||||
|
"Request timeout for icmp_seq %d\n", seq;
|
||||||
|
++loss;
|
||||||
|
res = 1;
|
||||||
|
} else {
|
||||||
|
"%d bytes from %d.%d.%d.%d: icmp_seq=%d ttl=%d time=%d ms\n",
|
||||||
|
reply.u16[1], addr.u8[3], addr.u8[2], addr.u8[1], addr.u8[0], seq, reply.u16[0], cnts.jiffies - start_jiffies;
|
||||||
|
}
|
||||||
|
while (cnts.jiffies < start_jiffies + 1000 && i < (count - 1))
|
||||||
|
Sleep(1);
|
||||||
|
++seq;
|
||||||
|
}
|
||||||
|
|
||||||
|
Free(request);
|
||||||
|
|
||||||
|
"--- %d.%d.%d.%d ping statistics ---\n", addr.u8[3], addr.u8[2], addr.u8[1], addr.u8[0];
|
||||||
|
"%d packets transmitted, %d packets received, %0f",
|
||||||
|
seq, seq - loss, (loss * 1.0 / seq * 1.0) * 100;
|
||||||
|
PutChars(37);
|
||||||
|
" packet loss\n";
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
112
System/Utilities/Time.HC
Normal file
112
System/Utilities/Time.HC
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
U0 @time_cmos_update_byte(I64 time_reg, I64 val)
|
||||||
|
{
|
||||||
|
OutU8(0x70, time_reg);
|
||||||
|
OutU8(0x71, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @time_dec_to_bcd(I64 val)
|
||||||
|
{
|
||||||
|
return (((val / 10) << 4) | (val % 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @time_update(U8* date_str, I64 mS_delta, I64 hour_offset)
|
||||||
|
{
|
||||||
|
no_warn mS_delta;
|
||||||
|
Bool is_bcd;
|
||||||
|
OutU8(0x70, 0x0B);
|
||||||
|
if (InU8(0x71) & 4)
|
||||||
|
is_bcd = FALSE;
|
||||||
|
else
|
||||||
|
is_bcd = TRUE;
|
||||||
|
|
||||||
|
I64 date_argc;
|
||||||
|
U8** date_argv = String.Split(date_str, ' ', &date_argc);
|
||||||
|
|
||||||
|
I64 month = DefineMatch(date_argv[2], "ST_MONTHS") + 1;
|
||||||
|
I64 day = Str2I64(date_argv[1]);
|
||||||
|
I64 year = Str2I64(date_argv[3] + 2);
|
||||||
|
I64 century = 20;
|
||||||
|
|
||||||
|
date_argv[4][2] = NULL;
|
||||||
|
date_argv[4][5] = NULL;
|
||||||
|
|
||||||
|
I64 hour = Str2I64(date_argv[4]);
|
||||||
|
I64 minute = Str2I64(date_argv[4] + 3);
|
||||||
|
I64 second = Str2I64(date_argv[4] + 6);
|
||||||
|
|
||||||
|
// FIXME: Handle month boundaries, and 12 hour time
|
||||||
|
hour += hour_offset;
|
||||||
|
if (hour < 0) {
|
||||||
|
hour += 24;
|
||||||
|
--day;
|
||||||
|
} else if (hour > 23) {
|
||||||
|
hour -= 24;
|
||||||
|
++day;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_bcd) {
|
||||||
|
century = @time_dec_to_bcd(century);
|
||||||
|
year = @time_dec_to_bcd(year);
|
||||||
|
month = @time_dec_to_bcd(month);
|
||||||
|
day = @time_dec_to_bcd(day);
|
||||||
|
hour = @time_dec_to_bcd(hour);
|
||||||
|
minute = @time_dec_to_bcd(minute);
|
||||||
|
second = @time_dec_to_bcd(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
@time_cmos_update_byte(0x32, century);
|
||||||
|
@time_cmos_update_byte(0x09, year);
|
||||||
|
@time_cmos_update_byte(0x08, month);
|
||||||
|
@time_cmos_update_byte(0x07, day);
|
||||||
|
@time_cmos_update_byte(0x04, hour);
|
||||||
|
@time_cmos_update_byte(0x02, minute);
|
||||||
|
@time_cmos_update_byte(0x00, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool @time_in_dst()
|
||||||
|
{
|
||||||
|
CDateStruct ds;
|
||||||
|
Date2Struct(&ds, Now);
|
||||||
|
if (ds.mon > 3 && ds.mon < 11)
|
||||||
|
return TRUE;
|
||||||
|
if (ds.mon < 3 || ds.mon > 11)
|
||||||
|
return FALSE;
|
||||||
|
if (ds.mon == 3 && ds.day_of_mon != 8)
|
||||||
|
return ds.day_of_mon > 8;
|
||||||
|
if (ds.mon == 3)
|
||||||
|
return ds.hour > 1;
|
||||||
|
if (ds.mon == 11 && ds.day_of_mon != 1)
|
||||||
|
return FALSE;
|
||||||
|
if (ds.mon == 11)
|
||||||
|
return ds.hour < 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 @time_tz_offset()
|
||||||
|
{
|
||||||
|
if (@time_in_dst)
|
||||||
|
return -4;
|
||||||
|
return -5;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 @time_query(Bool set = FALSE)
|
||||||
|
{
|
||||||
|
U8 buf[1024];
|
||||||
|
@http_url* url = @http_parse_url("http://time.google.com");
|
||||||
|
@http_response* resp = Http.Head(url, &buf);
|
||||||
|
while (resp->state != HTTP_STATE_DONE)
|
||||||
|
Sleep(1);
|
||||||
|
I64 mS_delta = cnts.jiffies;
|
||||||
|
"Set current date and time to %s ", Json.Get(resp->headers, "Date");
|
||||||
|
if (!set)
|
||||||
|
set = YorN;
|
||||||
|
else
|
||||||
|
"\n";
|
||||||
|
if (set)
|
||||||
|
@time_update(Json.Get(resp->headers, "Date"), mS_delta, @time_tz_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 TimeSync()
|
||||||
|
{
|
||||||
|
Sleep(500);
|
||||||
|
@time_query(1);
|
||||||
|
}
|
188
scripts/build-all
Executable file
188
scripts/build-all
Executable file
|
@ -0,0 +1,188 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
from pathlib import Path
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
raise ValueError('wrong number of arguments')
|
||||||
|
|
||||||
|
project_path = sys.argv[1] + '/'
|
||||||
|
project_name = project_path.rsplit('/')[-2]
|
||||||
|
|
||||||
|
isoc_file = project_path + 'build/isoc/Slon.ISO.C'
|
||||||
|
redsea_path = project_path + 'build/redsea'
|
||||||
|
|
||||||
|
home_path = str(Path.home()) + '/'
|
||||||
|
|
||||||
|
jakt_compiler_path = home_path + 'cloned/jakt/build/bin/jakt'
|
||||||
|
jakt_runtime_path = home_path + 'cloned/jakt/runtime'
|
||||||
|
jakt_lib_path = home_path + 'cloned/jakt/build/lib/x86_64-unknown-linux-unknown/'
|
||||||
|
|
||||||
|
qemu_slipstream_iso_file = project_path + 'build/isoc/bootable.iso'
|
||||||
|
qemu_virtio_disk_path = home_path + 'virtio-disk.qcow2'
|
||||||
|
|
||||||
|
qemu_bin_path = home_path + "/Programs/qemu-9.1.2/build/qemu-system-x86_64"
|
||||||
|
qemu_display = "-display sdl,grab-mod=rctrl"
|
||||||
|
|
||||||
|
templeos_iso_file = home_path + 'iso/TempleOS.ISO'
|
||||||
|
|
||||||
|
qemu_run_cmd = qemu_bin_path + ' ' + qemu_display + ' -enable-kvm -m 1024 -netdev tap,id=mynet0,ifname=tap0,script=no,downscript=no -device virtio-net,netdev=mynet0 -drive file=' + qemu_virtio_disk_path + ',format=qcow2,if=none,index=0,media=disk,id=virtio-disk -device virtio-blk-pci,drive=virtio-disk -cdrom ' + qemu_slipstream_iso_file + ' -debugcon stdio -boot d'
|
||||||
|
|
||||||
|
def clang_format_src_files():
|
||||||
|
print("build-all: clang-format-src-files")
|
||||||
|
exclude_paths = ["stb_", "tlse", ".iso.c"]
|
||||||
|
format_file_extensions = [".c", ".cpp", ".h", ".hc"]
|
||||||
|
for src_file in glob.glob(project_path + "**", recursive=True):
|
||||||
|
exclude_file = False
|
||||||
|
for exclude_path in exclude_paths:
|
||||||
|
if src_file.lower().find(exclude_path) > 0:
|
||||||
|
exclude_file = True
|
||||||
|
if exclude_file:
|
||||||
|
continue
|
||||||
|
for format_file_extension in format_file_extensions:
|
||||||
|
if src_file.lower().endswith(format_file_extension):
|
||||||
|
print(src_file)
|
||||||
|
res = os.system('clang-format -i --style=file:' + project_path + '.clang-format ' + src_file)
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'clang-format-src-files' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def refresh_build_path():
|
||||||
|
print("build-all: refresh-build-path")
|
||||||
|
res = os.system('rm -rf ' + project_path + 'build && mkdir -p ' + project_path + 'build/bin && mkdir -p ' + project_path + 'build/isoc && mkdir -p ' + project_path + 'build/lib && mkdir -p ' + project_path + 'build/redsea')
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'refresh-build-path' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def build_image():
|
||||||
|
print("build-all: build-image")
|
||||||
|
build_specific_options = '-Wl,--section-start=.text=0x1004000 -Wl,--section-start=.plt=0x1002020 -no-pie'
|
||||||
|
res = os.system('cd ' + project_path + '&& cd src/image && gcc -o ../../build/bin/image ' + build_specific_options + ' -O0 -mno-mmx -mno-red-zone image.c')
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'build-image' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def build_libtemple():
|
||||||
|
print("build-all: build-libtemple")
|
||||||
|
res = os.system('cd ' + project_path + 'src/libtemple && g++ -c -o ../../build/libtemple.o libtemple.cpp && gcc -shared -o ../../build/lib/libtemple.so ../../build/libtemple.o && rm ' + project_path + 'build/libtemple.o')
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'build-libtemple' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def build_tlse():
|
||||||
|
print("build-all: build-tlse")
|
||||||
|
build_specific_options = '-Wl,--section-start=.text=0x1204000 -Wl,--section-start=.plt=0x1202020 -no-pie'
|
||||||
|
res = os.system('cd ' + project_path + '&& cd src/tlse && gcc -o ../../build/bin/tlse ' + build_specific_options + ' -O0 -mno-mmx -mno-red-zone -DTLS_AMALGAMATION tlse.c')
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'build-tlse' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def transpile_net_to_sepples():
|
||||||
|
print("build-all: transpile-net-to-sepples")
|
||||||
|
res = os.system('cd ' + project_path + 'src/net && ' + jakt_compiler_path + ' -S -R ' + jakt_runtime_path + ' -B ' + project_path + 'build/net -O net.jakt')
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'transpile-net-to-sepples' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def build_net():
|
||||||
|
print("build-all: build-net")
|
||||||
|
build_specific_options = '-Wno-invalid-offsetof -Wl,--section-start=.text=0x1404000 -Wl,--section-start=.plt=0x1402020 -no-pie'
|
||||||
|
res = os.system('cd ' + project_path + 'build/net && clang++-19 ' + build_specific_options + ' -O3 -I ' + jakt_runtime_path + ' -I ' + project_path + '/src/libtemple -fcolor-diagnostics -std=c++20 -fno-exceptions -Wno-user-defined-literals -Wno-deprecated-declarations -Wno-parentheses-equality -Wno-unqualified-std-cast-call -Wno-unknown-warning-option -Wno-int-to-pointer-cast -mno-red-zone -o ../bin/net *.cpp ../lib/libtemple.so ' + jakt_lib_path + 'libjakt_runtime_x86_64-unknown-linux-unknown.a ' + jakt_lib_path + 'libjakt_main_x86_64-unknown-linux-unknown.a && cd .. && rm -rf net')
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'build-net' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def address_string_for_symbol(file, symbol):
|
||||||
|
p = subprocess.Popen('readelf -s --wide "' + file + '" | grep \'' + symbol + '$\' | awk \'{sub("000000000", "0x", $2); print $2}\'', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
return str(p.communicate()[0][:-1].decode(encoding='utf-8'))
|
||||||
|
|
||||||
|
def image_hc_fixup(macro, symbol, image_bin_path, image_hc_path):
|
||||||
|
os.system('echo -e "#define ' + macro + ' ' + address_string_for_symbol(image_bin_path, symbol) + '\n" | cat - ' + image_hc_path + ' | sponge ' + image_hc_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
def tlse_hc_fixup(macro, symbol, tlse_bin_path, tlse_hc_path):
|
||||||
|
os.system('echo -e "#define ' + macro + ' ' + address_string_for_symbol(tlse_bin_path, symbol) + '\n" | cat - ' + tlse_hc_path + ' | sponge ' + tlse_hc_path)
|
||||||
|
return
|
||||||
|
|
||||||
|
def generate_iso_c_file():
|
||||||
|
print("build-all: generate-iso-c-file")
|
||||||
|
step_error_message = "build-all: step 'generate-iso-c-file' failed, error code "
|
||||||
|
|
||||||
|
res = os.system('isoc-mount --rw ' + isoc_file + ' ' + redsea_path)
|
||||||
|
if res:
|
||||||
|
raise ValueError(step_error_message + str(res))
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
copy_files_cmd_line = 'rsync -av --inplace --progress ' + project_path + ' ' + redsea_path
|
||||||
|
copy_files_cmd_line += ' --exclude .clang-format'
|
||||||
|
copy_files_cmd_line += ' --exclude .git'
|
||||||
|
copy_files_cmd_line += ' --exclude .gitignore'
|
||||||
|
copy_files_cmd_line += ' --exclude .vscode'
|
||||||
|
copy_files_cmd_line += ' --exclude build/isoc'
|
||||||
|
copy_files_cmd_line += ' --exclude build/lib'
|
||||||
|
copy_files_cmd_line += ' --exclude build/redsea'
|
||||||
|
copy_files_cmd_line += ' --exclude scripts'
|
||||||
|
copy_files_cmd_line += ' --exclude src'
|
||||||
|
res = os.system(copy_files_cmd_line)
|
||||||
|
if res:
|
||||||
|
raise ValueError(step_error_message + str(res))
|
||||||
|
|
||||||
|
# Fixup addresses for Image.HC
|
||||||
|
image_bin_path = redsea_path + '/build/bin/image'
|
||||||
|
image_hc_path = redsea_path + '/System/Utilities/Image.HC'
|
||||||
|
|
||||||
|
image_hc_fixup('IMAGE_LOAD_GIF_FROM_MEMORY', 'image_load_gif_from_memory', image_bin_path, image_hc_path)
|
||||||
|
image_hc_fixup('STBI_WRITE_PNG_TO_MEM', 'stbi_write_png_to_mem', image_bin_path, image_hc_path)
|
||||||
|
image_hc_fixup('STBI_LOAD_FROM_MEMORY', 'stbi_load_from_memory', image_bin_path, image_hc_path)
|
||||||
|
image_hc_fixup('STBI_INFO_FROM_MEMORY', 'stbi_info_from_memory', image_bin_path, image_hc_path)
|
||||||
|
image_hc_fixup('STBI_FAILURE_REASON', 'stbi_failure_reason', image_bin_path, image_hc_path)
|
||||||
|
image_hc_fixup('RENDER_4BIT_FLOYDSTEIN', 'render_4bit_floydstein', image_bin_path, image_hc_path)
|
||||||
|
|
||||||
|
# Fixup addresses for Tlse.HC
|
||||||
|
|
||||||
|
rsa_hc_path = redsea_path + '/System/Libraries/Rsa.HC'
|
||||||
|
tlse_bin_path = redsea_path + '/build/bin/tlse'
|
||||||
|
tlse_hc_path = redsea_path + '/System/Libraries/Tlse.HC'
|
||||||
|
|
||||||
|
tlse_hc_fixup('RSA_IMPORT', 'rsa_import', tlse_bin_path, rsa_hc_path)
|
||||||
|
tlse_hc_fixup('RSA_CREATE_SIGNATURE', 'rsa_create_signature', tlse_bin_path, rsa_hc_path)
|
||||||
|
tlse_hc_fixup('RSA_VERIFY_SIGNATURE', 'rsa_verify_signature', tlse_bin_path, rsa_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_CREATE_CONTEXT', 'tls_create_context', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_SNI_SET', 'tls_sni_set', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_CLIENT_CONNECT', 'tls_client_connect', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_CONNECTION_STATUS', 'tls_connection_status', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_GET_WRITE_BUFFER', 'tls_get_write_buffer', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_BUFFER_CLEAR', 'tls_buffer_clear', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_CONSUME_STREAM', 'tls_consume_stream', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_READ', 'tls_read', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_WRITE', 'tls_write', tlse_bin_path, tlse_hc_path)
|
||||||
|
tlse_hc_fixup('TLS_ESTABLISHED', 'tls_established', tlse_bin_path, tlse_hc_path)
|
||||||
|
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
res = os.system('sync && fusermount -u ' + redsea_path)
|
||||||
|
if res:
|
||||||
|
raise ValueError(step_error_message + str(res))
|
||||||
|
time.sleep(0.25)
|
||||||
|
|
||||||
|
def generate_slipstream_iso_file():
|
||||||
|
print("build-all: generate-slipstream-iso-file")
|
||||||
|
res = os.system('templeos-slipstream ' + templeos_iso_file + ' ' + isoc_file + ' ' + qemu_slipstream_iso_file)
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'generate-slipstream-iso-file' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def run():
|
||||||
|
print("build-all: run")
|
||||||
|
res = os.system(qemu_run_cmd)
|
||||||
|
if res:
|
||||||
|
raise ValueError("build-all: step 'run' failed, error code " + str(res))
|
||||||
|
|
||||||
|
def build_all():
|
||||||
|
clang_format_src_files()
|
||||||
|
#refresh_build_path()
|
||||||
|
#build_image()
|
||||||
|
#build_libtemple()
|
||||||
|
#build_tlse()
|
||||||
|
#transpile_net_to_sepples()
|
||||||
|
#build_net()
|
||||||
|
generate_iso_c_file()
|
||||||
|
generate_slipstream_iso_file()
|
||||||
|
run()
|
||||||
|
|
||||||
|
build_all()
|
209
src/image/image.c
Normal file
209
src/image/image.c
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
#define STBI_WRITE_NO_STDIO
|
||||||
|
#define STB_IMAGE_WRITE_STATIC
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#define STBI_NO_LINEAR
|
||||||
|
#define STBI_NO_STDIO
|
||||||
|
#define STBI_NO_SIMD
|
||||||
|
#define STBI_NO_HDR
|
||||||
|
|
||||||
|
#include "stb_image.h"
|
||||||
|
#include "stb_image_write.h"
|
||||||
|
|
||||||
|
int main() { return 0; }
|
||||||
|
|
||||||
|
STBIDEF stbi_uc* image_load_gif_from_memory(stbi_uc const* buffer, int len,
|
||||||
|
int** delays, int* x, int* y,
|
||||||
|
int* z)
|
||||||
|
{
|
||||||
|
int comp;
|
||||||
|
return stbi_load_gif_from_memory(buffer, len, delays, x, y, z, &comp, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dither.c: MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 jonmortiboy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct RGB {
|
||||||
|
int r;
|
||||||
|
int g;
|
||||||
|
int b;
|
||||||
|
} RGB;
|
||||||
|
|
||||||
|
int imgw, imgh;
|
||||||
|
|
||||||
|
// Define the 4bit colour palette
|
||||||
|
int numCols = 16;
|
||||||
|
RGB cols4bit[] = {
|
||||||
|
{ 0, 0, 0 }, { 0, 0, 170 }, { 0, 170, 0 }, { 0, 170, 170 },
|
||||||
|
{ 170, 0, 0 }, { 170, 0, 170 }, { 170, 85, 0 }, { 170, 170, 170 },
|
||||||
|
{ 85, 85, 85 }, { 85, 85, 255 }, { 85, 255, 85 }, { 85, 255, 255 },
|
||||||
|
{ 255, 85, 85 }, { 255, 85, 255 }, { 255, 255, 85 }, { 255, 255, 255 }
|
||||||
|
};
|
||||||
|
RGB* cols = cols4bit;
|
||||||
|
|
||||||
|
RGB getRGB(uint32_t* pixels, int x, int y);
|
||||||
|
void setRGB(uint32_t* pixels, int x, int y, RGB rgb);
|
||||||
|
RGB difRGB(RGB from, RGB to);
|
||||||
|
RGB addRGB(RGB a, RGB b);
|
||||||
|
RGB divRGB(RGB rgb, double d);
|
||||||
|
RGB mulRGB(RGB rgb, double d);
|
||||||
|
RGB nearestRGB(RGB rgb, RGB* rgbs, int numRGBs);
|
||||||
|
double distRGB(RGB from, RGB to);
|
||||||
|
|
||||||
|
void render_4bit_floydstein(uint32_t* pixels, int width, int height);
|
||||||
|
|
||||||
|
RGB getRGB(uint32_t* pixels, int x, int y)
|
||||||
|
{
|
||||||
|
RGB rgb;
|
||||||
|
rgb.r = 0;
|
||||||
|
rgb.g = 0;
|
||||||
|
rgb.b = 0;
|
||||||
|
|
||||||
|
if (x < 0 || x >= imgw || y < 0 || y >= imgh)
|
||||||
|
return rgb;
|
||||||
|
|
||||||
|
rgb.r = (pixels[y * imgw + x] & 0xff);
|
||||||
|
rgb.g = (pixels[y * imgw + x] & 0xff00) >> 8;
|
||||||
|
rgb.b = (pixels[y * imgw + x] & 0xff0000) >> 16;
|
||||||
|
|
||||||
|
return rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRGB(uint32_t* pixels, int x, int y, RGB rgb)
|
||||||
|
{
|
||||||
|
if (x < 0 || x >= imgw || y < 0 || y >= imgh)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32_t alpha = pixels[y * imgw + x] & 0xff000000;
|
||||||
|
pixels[y * imgw + x] = alpha + (rgb.r) + (rgb.g << 8) + (rgb.b << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
RGB difRGB(RGB from, RGB to)
|
||||||
|
{
|
||||||
|
RGB dif;
|
||||||
|
dif.r = to.r - from.r;
|
||||||
|
dif.g = to.g - from.g;
|
||||||
|
dif.b = to.b - from.b;
|
||||||
|
|
||||||
|
return dif;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGB addRGB(RGB a, RGB b)
|
||||||
|
{
|
||||||
|
RGB sum;
|
||||||
|
sum.r = a.r + b.r;
|
||||||
|
sum.g = a.g + b.g;
|
||||||
|
sum.b = a.b + b.b;
|
||||||
|
|
||||||
|
if (sum.r > 255)
|
||||||
|
sum.r = 255;
|
||||||
|
if (sum.r < 0)
|
||||||
|
sum.r = 0;
|
||||||
|
if (sum.g > 255)
|
||||||
|
sum.g = 255;
|
||||||
|
if (sum.g < 0)
|
||||||
|
sum.g = 0;
|
||||||
|
if (sum.b > 255)
|
||||||
|
sum.b = 255;
|
||||||
|
if (sum.b < 0)
|
||||||
|
sum.b = 0;
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGB divRGB(RGB rgb, double d)
|
||||||
|
{
|
||||||
|
RGB div;
|
||||||
|
div.r = (int)((double)rgb.r / d);
|
||||||
|
div.g = (int)((double)rgb.g / d);
|
||||||
|
div.b = (int)((double)rgb.b / d);
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGB mulRGB(RGB rgb, double d)
|
||||||
|
{
|
||||||
|
RGB mul;
|
||||||
|
mul.r = (int)((double)rgb.r * d);
|
||||||
|
mul.g = (int)((double)rgb.g * d);
|
||||||
|
mul.b = (int)((double)rgb.b * d);
|
||||||
|
|
||||||
|
return mul;
|
||||||
|
}
|
||||||
|
|
||||||
|
double distRGB(RGB from, RGB to)
|
||||||
|
{
|
||||||
|
RGB dif = difRGB(from, to);
|
||||||
|
double dist = dif.r * dif.r + dif.g * dif.g + dif.b * dif.b;
|
||||||
|
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGB nearestRGB(RGB rgb, RGB rgbs[], int numRGBs)
|
||||||
|
{
|
||||||
|
double dist = -1, tempDist;
|
||||||
|
RGB nearest;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < numRGBs; i++) {
|
||||||
|
tempDist = distRGB(rgb, rgbs[i]);
|
||||||
|
|
||||||
|
if (tempDist < dist || dist < 0) {
|
||||||
|
dist = tempDist;
|
||||||
|
nearest = rgbs[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void render_4bit_floydstein(uint32_t* pixels, int width, int height)
|
||||||
|
{
|
||||||
|
|
||||||
|
int i, x, y;
|
||||||
|
imgw = width;
|
||||||
|
imgh = height;
|
||||||
|
RGB rgb, nearest, rgberror;
|
||||||
|
for (i = 0; i < imgw * imgh; i++) {
|
||||||
|
rgb = getRGB(pixels, i % imgw, i / imgw);
|
||||||
|
nearest = nearestRGB(rgb, cols, numCols);
|
||||||
|
|
||||||
|
rgberror = difRGB(nearest, rgb);
|
||||||
|
rgberror = divRGB(rgberror, 16);
|
||||||
|
|
||||||
|
x = i % imgw;
|
||||||
|
y = i / imgw;
|
||||||
|
|
||||||
|
setRGB(pixels, x + 1, y,
|
||||||
|
addRGB(getRGB(pixels, x + 1, y), mulRGB(rgberror, 7)));
|
||||||
|
setRGB(pixels, x - 1, y + 1,
|
||||||
|
addRGB(getRGB(pixels, x - 1, y + 1), mulRGB(rgberror, 3)));
|
||||||
|
setRGB(pixels, x, y + 1,
|
||||||
|
addRGB(getRGB(pixels, x, y + 1), mulRGB(rgberror, 5)));
|
||||||
|
setRGB(pixels, x + 1, y + 1,
|
||||||
|
addRGB(getRGB(pixels, x + 1, y + 1), rgberror));
|
||||||
|
|
||||||
|
setRGB(pixels, i % imgw, i / imgw, nearest);
|
||||||
|
}
|
||||||
|
}
|
8632
src/image/stb_image.h
Normal file
8632
src/image/stb_image.h
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue