Security improvements

This commit is contained in:
synt-xerror
2026-02-16 19:08:31 -03:00
parent 9ba4be8a92
commit 08cc4f9d75

View File

@@ -5,222 +5,298 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
// ------------------------- // -------------------------
// HTTP responde buffer // HTTP responde buffer
// ------------------------- // -------------------------
struct response { struct response {
char *data; char *data;
size_t len; size_t len;
}; };
static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *userdata) { static size_t write_cb(
struct response *r = (struct response*)userdata; void *ptr,
size_t chunk = size * nmemb; size_t size,
char *tmp = realloc(r->data, r->len + chunk + 1); size_t nmemb,
if (!tmp) return 0; void *userdata
r->data = tmp; )
memcpy(r->data + r->len, ptr, chunk); {
r->len += chunk; struct response *r = (struct response*)userdata;
r->data[r->len] = '\0'; size_t chunk = size * nmemb;
return chunk; char *tmp = realloc(r->data, r->len + chunk + 1);
if (!tmp) return 0;
r->data = tmp;
memcpy(r->data + r->len, ptr, chunk);
r->len += chunk;
r->data[r->len] = '\0';
return chunk;
} }
// ------------------------- // -------------------------
// HTTP helpers // HTTP helpers
// ------------------------- // -------------------------
static int perform_request(const char *url, const char *user, const char *pass, static int perform_request(
const char *post_fields, struct response *resp) const char *url,
const char *api_key,
const char *post_fields,
struct response *resp,
curl_mime *mime
)
{ {
CURL *curl = curl_easy_init(); CURL *curl = curl_easy_init();
if (!curl) return 1; if (!curl) return 1;
resp->data = malloc(1);
resp->len = 0;
curl_easy_setopt(curl, CURLOPT_URL, url); resp->data = malloc(1);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); resp->len = 0;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, resp); resp->data[0] = '\0';
if (user && pass) { struct curl_slist *headers = NULL;
char auth[256];
snprintf(auth, sizeof(auth), "%s:%s", user, pass);
curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
}
if (post_fields) { if (api_key && strlen(api_key) > 0) {
curl_easy_setopt(curl, CURLOPT_POST, 1L); char auth_header[512];
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields); snprintf(auth_header, sizeof(auth_header),
} "Authorization: Bearer %s", api_key);
headers = curl_slist_append(headers, auth_header);
}
CURLcode res = curl_easy_perform(curl); curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_cleanup(curl);
if (res != CURLE_OK) { if (headers)
free(resp->data); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
return 2;
} /* SSL hardening */
return 0; curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L); // política
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, resp);
if (post_fields) {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields);
}
if (mime)
curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (res != CURLE_OK || http_code >= 400) {
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
free(resp->data);
return 2;
}
return 0;
} }
// ------------------------- // -------------------------
// JSON parsers // JSON parsers
// ------------------------- // -------------------------
void neocities_free_info(neocities_info_t *info) { void neocities_free_info(
if (!info) return; neocities_info_t *info
free(info->sitename); )
free(info->created_at); {
free(info->last_updated); if (!info) return;
free(info->domain); free(info->sitename);
for (size_t i=0; i<info->tag_count; i++) free(info->tags[i]); free(info->created_at);
free(info->tags); free(info->last_updated);
free(info->domain);
for (size_t i=0; i<info->tag_count; i++) free(info->tags[i]);
free(info->tags);
} }
void neocities_free_filelist(neocities_filelist_t *list) { void neocities_free_filelist(
if (!list) return; neocities_filelist_t *list
for (size_t i=0; i<list->count; i++) free(list->paths[i]); )
free(list->paths); {
if (!list) return;
for (size_t i=0; i<list->count; i++) free(list->paths[i]);
free(list->paths);
} }
// ------------------------- // -------------------------
// info — GET /api/info // info — GET /api/info
// ------------------------- // -------------------------
int neocities_info(const char *sitename, neocities_info_t *out) { int neocities_info(
char url[512]; const char *sitename, neocities_info_t *out
if (sitename) )
snprintf(url, sizeof(url), "https://neocities.org/api/info?sitename=%s", sitename); {
else char url[512];
snprintf(url, sizeof(url), "https://neocities.org/api/info"); if (sitename)
snprintf(url, sizeof(url), "https://neocities.org/api/info?sitename=%s", sitename);
else
snprintf(url, sizeof(url), "https://neocities.org/api/info");
struct response resp; struct response resp;
int ret = perform_request(url, NULL, NULL, NULL, &resp); int ret = perform_request(url, NULL, NULL, &resp, NULL);
if (ret) return ret; if (ret) return ret;
json_error_t error; json_error_t error;
json_t *root = json_loads(resp.data, 0, &error); json_t *root = json_loads(resp.data, 0, &error);
free(resp.data); free(resp.data);
if (!root) return 3; if (!root) return 3;
const char *result = json_string_value(json_object_get(root,"result")); const char *result = json_string_value(json_object_get(root,"result"));
if (!result || strcmp(result,"success")!=0) { json_decref(root); return 4; } if (!result || strcmp(result,"success")!=0) { json_decref(root); return 4; }
json_t *info = json_object_get(root,"info"); json_t *info = json_object_get(root,"info");
out->sitename = strdup(json_string_value(json_object_get(info,"sitename"))); out->sitename = strdup(json_string_value(json_object_get(info,"sitename")));
out->hits = (int)json_integer_value(json_object_get(info,"hits")); out->hits = (int)json_integer_value(json_object_get(info,"hits"));
out->created_at = strdup(json_string_value(json_object_get(info,"created_at"))); out->created_at = strdup(json_string_value(json_object_get(info,"created_at")));
out->last_updated= strdup(json_string_value(json_object_get(info,"last_updated"))); out->last_updated= strdup(json_string_value(json_object_get(info,"last_updated")));
out->domain = strdup(json_string_value(json_object_get(info,"domain"))); out->domain = strdup(json_string_value(json_object_get(info,"domain")));
json_t *tags = json_object_get(info,"tags"); json_t *tags = json_object_get(info,"tags");
size_t tc = json_array_size(tags); size_t tc = json_array_size(tags);
out->tags = malloc(sizeof(char*) * tc); out->tags = malloc(sizeof(char*) * tc);
out->tag_count = tc; out->tag_count = tc;
for (size_t i=0; i<tc; i++) for (size_t i=0; i<tc; i++)
out->tags[i] = strdup(json_string_value(json_array_get(tags,i))); out->tags[i] = strdup(json_string_value(json_array_get(tags,i)));
json_decref(root); json_decref(root);
return 0; return 0;
} }
// ------------------------- // -------------------------
// list — GET /api/list // list — GET /api/list
// ------------------------- // -------------------------
int neocities_list(const char *user, const char *pass, const char *path, neocities_filelist_t *out) { int neocities_list(
char url[512]; const char *api_key,
if (path) snprintf(url, sizeof(url), "https://neocities.org/api/list?path=%s", path); const char *path, neocities_filelist_t *out
else snprintf(url, sizeof(url), "https://neocities.org/api/list"); )
{
char url[512];
if (path) snprintf(url, sizeof(url), "https://neocities.org/api/list?path=%s", path);
else snprintf(url, sizeof(url), "https://neocities.org/api/list");
struct response resp; struct response resp;
int ret = perform_request(url, user, pass, NULL, &resp); int ret = perform_request(url, api_key, NULL, &resp, NULL);
if (ret) return ret; if (ret) return ret;
json_error_t error; json_error_t error;
json_t *root = json_loads(resp.data, 0, &error); json_t *root = json_loads(resp.data, 0, &error);
free(resp.data); free(resp.data);
if (!root) return 3; if (!root) return 3;
json_t *arr = json_object_get(root, "files"); json_t *arr = json_object_get(root, "files");
if (!arr) { json_decref(root); return 4; } if (!arr) { json_decref(root); return 4; }
size_t count = json_array_size(arr); size_t count = json_array_size(arr);
out->paths = malloc(sizeof(char*) * count); out->paths = malloc(sizeof(char*) * count);
out->count = count; out->count = count;
for (size_t i=0; i<count; i++) { for (size_t i=0; i<count; i++) {
json_t *f = json_array_get(arr, i); json_t *f = json_array_get(arr, i);
out->paths[i] = strdup(json_string_value(json_object_get(f,"path"))); out->paths[i] = strdup(json_string_value(json_object_get(f,"path")));
} }
json_decref(root); json_decref(root);
return 0; return 0;
} }
// ------------------------- // -------------------------
// delete — POST /api/delete // delete — POST /api/delete
// ------------------------- // -------------------------
int neocities_delete(const char *user, const char *pass, const char **filenames, size_t count, char **response) { int neocities_delete(
char body[1024] = ""; const char *api_key,
for (size_t i=0; i<count; i++) { const char **filenames,
char tmp[256]; size_t count,
snprintf(tmp, sizeof(tmp), "filenames[]=%s&", filenames[i]); char **response
strncat(body, tmp, sizeof(body)-strlen(body)-1); )
} {
struct response resp; char body[1024] = "";
int ret = perform_request("https://neocities.org/api/delete", user, pass, body, &resp); for (size_t i=0; i<count; i++) {
if (ret) return ret; char tmp[256];
*response = resp.data; snprintf(tmp, sizeof(tmp), "filenames[]=%s&", filenames[i]);
return 0; strncat(body, tmp, sizeof(body)-strlen(body)-1);
}
struct response resp;
int ret = perform_request(
"https://neocities.org/api/delete",
api_key,
body,
&resp,
NULL
);
if (ret) return ret;
*response = resp.data;
return 0;
} }
// ------------------------- // -------------------------
// upload — POST /api/upload // upload — POST /api/upload
// (multipart/form-data) // (multipart/form-data)
// ------------------------- // -------------------------
int neocities_upload(const char *user, const char *pass, const char **local_files, const char **remote_names, size_t count, char **response) { int neocities_upload(
CURL *curl = curl_easy_init(); const char *api_key,
if (!curl) return 1; const char **local_files,
const char **remote_names,
size_t count,
char **response
)
{
if (!api_key || strlen(api_key) == 0)
return 3;
struct response resp; CURL *curl = curl_easy_init();
resp.data = malloc(1); if (!curl) return 1;
resp.len = 0;
curl_easy_setopt(curl, CURLOPT_URL, "https://neocities.org/api/upload"); struct response resp;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); resp.data = malloc(1);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); resp.len = 0;
resp.data[0] = '\0';
curl_easy_setopt(curl, CURLOPT_USERPWD, NULL); curl_easy_setopt(curl, CURLOPT_URL, "https://neocities.org/api/upload");
char auth[256]; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
snprintf(auth, sizeof(auth), "%s:%s", user, pass); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
curl_mime *form = curl_mime_init(curl); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
for (size_t i=0; i<count; i++) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_mimepart *part = curl_mime_addpart(form); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
curl_mime_name(part, remote_names[i]); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_mime_filedata(part, local_files[i]); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
}
curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);
CURLcode res = curl_easy_perform(curl);
curl_mime_free(form);
curl_easy_cleanup(curl);
if (res != CURLE_OK) { free(resp.data); return 2; } curl_easy_setopt(curl, CURLOPT_USERPWD, NULL);
*response = resp.data; struct curl_slist *headers = NULL;
return 0; char auth_header[512];
snprintf(auth_header, sizeof(auth_header),
"Authorization: Bearer %s", api_key);
headers = curl_slist_append(headers, auth_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_mime *form = curl_mime_init(curl);
for (size_t i=0; i<count; i++) {
curl_mimepart *part = curl_mime_addpart(form);
curl_mime_name(part, remote_names[i]);
curl_mime_filedata(part, local_files[i]);
}
curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);
CURLcode res = curl_easy_perform(curl);
curl_mime_free(form);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
if (res != CURLE_OK) { free(resp.data); return 2; }
*response = resp.data;
return 0;
} }
// ------------------------- // -------------------------
// get_api_key — GET /api/key // get_api_key — GET /api/key
// ------------------------- // -------------------------
int neocities_get_api_key(const char *user, const char *pass, char **api_key) { // no. for security reasons of course.
struct response resp;
int ret = perform_request("https://neocities.org/api/key", user, pass, NULL, &resp);
if (ret) return ret;
json_error_t error;
json_t *root = json_loads(resp.data, 0, &error);
free(resp.data);
if (!root) return 3;
const char *key = json_string_value(json_object_get(root,"api_key"));
*api_key = strdup(key);
json_decref(root);
return 0;
}