Security improvements
This commit is contained in:
384
src/neocities.c
384
src/neocities.c
@@ -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;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user