14 Commits
dev ... 1.0.0

Author SHA1 Message Date
synt-xerror
b9b474af40 New: GET /api/key 2026-02-23 08:31:26 -03:00
synt-xerror
0543a9e9ce idk what am i doing 2026-02-20 21:30:22 -03:00
synt-xerror
1ee3da6722 Removing this (oops) 2026-02-19 02:28:40 -03:00
synt-xerror
f3667d615e fix link 2026-02-19 02:25:36 -03:00
synt-xerror
55788ace47 Example files 2026-02-19 02:21:09 -03:00
synt-xerror
94f34439dc update readme 2026-02-19 02:20:52 -03:00
synt-xerror
795cd984cd removing src folder 2026-02-19 02:20:23 -03:00
synt-xerror
cde638aa71 Temporary files 2026-02-19 02:19:49 -03:00
synt-xerror
832bb88ecd Removing jansson, the library won't parse json anymore 2026-02-19 02:18:43 -03:00
synt-xerror
7cdac92582 Fix: removing old and useless files 2026-02-16 22:05:53 -03:00
synt-xerror
878ee963b5 Fix: segfault error at info function 2026-02-16 21:37:20 -03:00
synt-xerror
c955ee08d4 New readme 2026-02-16 20:34:40 -03:00
synt-xerror
08cc4f9d75 Security improvements 2026-02-16 19:08:31 -03:00
synt-xerror
9ba4be8a92 Library is done for test and improvements 2026-02-16 17:01:21 -03:00
14 changed files with 402 additions and 450 deletions

4
.gitignore vendored Normal file → Executable file
View File

@@ -1 +1,3 @@
auto-commit.txt
example
libneocities.a
neocities.o

44
Makefile Executable file → Normal file
View File

@@ -1,38 +1,10 @@
# ------------------------------
# Neocities CLI Makefile (Portable)
# ------------------------------
make:
gcc -c neocities.c -o neocities.o
ar rcs libneocities.a neocities.o
# Default compiler
CC ?= gcc
# Compiler flags
CFLAGS ?= -Wall -O2 -Iinclude
# Libraries
LIBS ?= -lcurl -ljansson
# Source and target
SRC = \
src/main.c \
src/info.c
BIN = neocities
# Detect Windows (MSYS2 / MinGW)
ifeq ($(OS),Windows_NT)
RM = del /Q
BIN_EXT = .exe
else
RM = rm -f
BIN_EXT =
endif
# Build executable
all: $(BIN)$(BIN_EXT)
$(BIN)$(BIN_EXT): $(SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
# Clean build files
clean:
$(RM) $(BIN)$(BIN_EXT)
rm libneocities.a neocities.o
example:
rm -f example
gcc example.c -L. -lneocities -lcurl -o example

159
README.md
View File

@@ -1,121 +1,94 @@
# Neocities C CLI
# Neocities API C library
A simple command-line tool written in C that interacts with the [Neocities API](https://neocities.org/api).
Currently, only the `--info` command is implemented — it fetches and displays basic information about your Neocities site.
Minimal, secure C wrapper for the Neocities API.
---
## Features
- Fetch site information using Neocities API
- Authenticates via environment variables
- JSON parsing with [jansson](https://digip.org/jansson/)
- Works on Linux, Windows (MSYS2/MinGW), and Android (via Termux)
- Compilable with a Makefile for easy builds
* Site info retrieval
* File listing
* Upload files
* Delete files
* Get API key
* TLS verification enabled by default
---
## Requirements
Youll need:
- A C compiler (`gcc` or `clang`)
- [libcurl](https://curl.se/libcurl/)
- [jansson](https://digip.org/jansson/)
- `make`
* libcurl
### Linux (Debian/Ubuntu-based)
```bash
sudo apt update
sudo apt install build-essential libcurl4-openssl-dev libjansson-dev make
```
### Windows (MSYS2 / MinGW)
```bash
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-curl mingw-w64-x86_64-jansson make
```
### Android (Termux)
```bash
pkg install clang curl-dev jansson make
```
## Building
Run in cloned repository:
```bash
make
```
This will generate the executable neocities (or neocities.exe on Windows).
To clean build files:
```bash
make clean
```
### Environment Variables
Before running, export your Neocities credentials:
#### Linux / macOS / Termux
```bash
export NEOCITIES_USER="your_username"
export NEOCITIES_PASS="your_password"
```
#### Windows (PowerShell)
```batch
setx NEOCITIES_USER "your_username"
setx NEOCITIES_PASS "your_password"
```
## Usage
```bash
./neocities --info
```
Example output:
Linux (example):
```
Neocities C CLI
Sitename: example
Hits: 42
Created at: 2024-01-10
Last updated: 2025-11-01
Domain: domain.com
Tags: art, blog, personal.
sudo apt install libcurl4-openssl-dev
```
## Optional: Install Globally
---
You can move the compiled executable to a directory in your PATH to run it from anywhere:
## API Overview
#### Linux / Termux
### Info (public)
```bash
sudo mv neocities /usr/local/bin/
```c
int neocities_info(const char *sitename, char **out);
```
#### Windows (PowerShell)
Move neocities.exe to a folder in your PATH, e.g., C:\Windows\System32\.
Fetch metadata about a site.
After that, you can run:
---
```batch
neocities --info
### List files
```c
int neocities_list(const char *api_key, const char *path, char** out);
```
from any folder without specifying the path.
List files at a path.
## Notes
---
Only the --info command is implemented for now.
### Upload
If you get curl_easy_perform errors, check your network and SSL setup.
```c
int neocities_upload(const char *api_key, const char **local_files, const char **remote_names, size_t count, char **response);
```
Uploads multiple files.
* `local_files[i]` → local path
* `remote_names[i]` → remote filename
---
### Delete
```c
int neocities_delete(const char *api_key, const char **filenames, size_t count, char **response);
```
Deletes files from the site.
---
### API Key
```c
int neocities_apikey(const char *user, const char *pass, char **out);
```
Get your API key.
---
## Example Usage
See example.c
---
## License
[MIT](LICENSE)
On Termux, use clang for the best compatibility.

35
example.c Normal file
View File

@@ -0,0 +1,35 @@
#include <stdio.h>
#include "neocities.h"
#include <stdlib.h>
int main() {
static const char* username = "YOUR_USERNAME";
static const char* api_key = "YOUR_API_KEY";
char* out = NULL;
// GET /api/key
if (neocities_apikey(username, "YOUR_PASSWORD", &out) == 0) printf("%s", out);
free(out);
// GET /api/info
if (neocities_info(username, &out) == 0) printf("%s", out);
free(out);
// GET /api/list
if (neocities_list(api_key, "/", &out) == 0) printf("%s", out);
free(out);
static const char* files[] = { "test.txt" };
static const int qnt_files = sizeof(files) / sizeof(files[0]);
// GET /api/upload
if (neocities_upload(api_key, files, files, qnt_files, &out) == 0) printf("%s", out);
free(out);
// GET /api/delete
if (neocities_delete(api_key, files, qnt_files, &out) == 0) printf("%s", out);
free(out);
return 0;
}

BIN
neocities

Binary file not shown.

273
neocities.c Normal file
View File

@@ -0,0 +1,273 @@
#include "neocities.h"
#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// -------------------------
// HTTP response buffer
// -------------------------
struct response {
char *data;
size_t len;
};
static size_t write_cb(
void *ptr,
size_t size,
size_t nmemb,
void *userdata
)
{
struct response *r = (struct response*)userdata;
size_t chunk = size * nmemb;
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
// -------------------------
static int perform_request(
const char *url,
const char *api_key,
const char *post_fields,
struct response *resp,
curl_mime *mime
)
{
CURL *curl = curl_easy_init();
if (!curl) return 1;
resp->data = malloc(1);
resp->len = 0;
resp->data[0] = '\0';
struct curl_slist *headers = NULL;
if (api_key && strlen(api_key) > 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_URL, url);
if (headers)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
/* SSL hardening */
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;
}
// -------------------------
// info — GET /api/info
// -------------------------
int neocities_info(
const char *sitename,
char** out
)
{
char url[512];
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;
int ret = perform_request(url, NULL, NULL, &resp, NULL);
if (ret) return ret;
*out = resp.data;
return 0;
}
// -------------------------
// list — GET /api/list
// -------------------------
int neocities_list(
const char *api_key,
const char *path,
char **out
)
{
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;
int ret = perform_request(url, api_key, NULL, &resp, NULL);
if (ret) return ret;
*out = resp.data;
return 0;
}
// -------------------------
// delete — POST /api/delete
// -------------------------
int neocities_delete(
const char *api_key,
const char **filenames,
size_t count,
char **response
)
{
char body[1024] = "";
for (size_t i=0; i<count; i++) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "filenames[]=%s&", filenames[i]);
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
// (multipart/form-data)
// -------------------------
int neocities_upload(
const char *api_key,
const char **local_files,
const char **remote_names,
size_t count,
char **response
)
{
if (!api_key || strlen(api_key) == 0)
return 3;
CURL *curl = curl_easy_init();
if (!curl) return 1;
struct response resp;
resp.data = malloc(1);
resp.len = 0;
resp.data[0] = '\0';
curl_easy_setopt(curl, CURLOPT_URL, "https://neocities.org/api/upload");
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
curl_easy_setopt(curl, CURLOPT_USERPWD, NULL);
struct curl_slist *headers = NULL;
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;
}
// -------------------------
// key — GET /api/key
// -------------------------
int neocities_apikey(const char *user, const char *pass, char **out) {
struct response resp = {0};
char userpass[256];
snprintf(userpass, sizeof(userpass), "%s:%s", user, pass);
CURL *curl = curl_easy_init();
if (!curl) return 1;
resp.data = malloc(1);
resp.len = 0;
resp.data[0] = '\0';
curl_easy_setopt(curl, CURLOPT_URL, "https://neocities.org/api/key");
curl_easy_setopt(curl, CURLOPT_USERPWD, userpass); // aqui é equivalente ao -u do curl
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
CURLcode res = curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
if (res != CURLE_OK || http_code >= 400) {
free(resp.data);
return 2;
}
*out = resp.data; // resposta da API
return 0;
}

15
neocities.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef NEOCITIES_H
#define NEOCITIES_H
#include <stddef.h>
// -------------------------
// API functions
// -------------------------
int neocities_info(const char *sitename, char **out);
int neocities_list(const char *api_key, const char *path, char **out);
int neocities_delete(const char *api_key, const char **filenames, size_t count, char **response);
int neocities_upload(const char *api_key, const char **local_files, const char **remote_names, size_t count, char **response);
int neocities_apikey(const char *user, const char *pass, char **out);
#endif // NEOCITIES_H

View File

@@ -1,156 +0,0 @@
#include "main.h" // Header local do projeto (declarações próprias: struct response, write_callback, etc.)
#include <stdio.h> // printf, fprintf
#include <stdlib.h> // getenv, malloc, free
#include <curl/curl.h> // libcurl: HTTP/HTTPS client
#include <jansson.h> // Jansson: parsing e manipulação de JSON
#include <string.h>
// Função responsável por buscar informações do site do usuário no Neocities
int fetch_neocities_info() {
// Lê variáveis de ambiente com credenciais
// NEOCITIES_USER e NEOCITIES_PASS são usadas para autenticação HTTP Basic
const char *user = getenv("NEOCITIES_USER");
const char *pass = getenv("NEOCITIES_PASS");
// const char *enc = getenv("NEOCITIES_PASS_ENC"); // Não usado aqui (possível uso futuro)
// Verificação básica: se usuário ou senha não existirem, aborta
if (!user || !pass) {
fprintf(stderr, "You're not logged!\n");
return 1;
}
// Buffer para "user:password", formato exigido por CURLOPT_USERPWD
char auth[256];
snprintf(auth, sizeof(auth), "%s:%s", user, pass);
// Inicializa um handle do libcurl
CURL *curl = curl_easy_init();
if (!curl) {
fprintf(stderr, "Erro ao inicializar cURL\n");
return 1;
}
// Monta a URL da API do Neocities para obter informações do site
char infourl[256];
snprintf(
infourl,
sizeof(infourl),
"https://neocities.org/api/info?sitename=%s",
user
);
// Define a URL da requisição
curl_easy_setopt(curl, CURLOPT_URL, infourl);
// Define autenticação HTTP Basic (user:pass)
curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
// Estrutura usada para armazenar a resposta HTTP em memória
// resp.data será expandido pelo write_callback
struct response resp = { .data = malloc(1), .len = 0 };
resp.data[0] = '\0'; // Garante string vazia inicial
// Função chamada pelo libcurl sempre que dados são recebidos
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
// Ponteiro passado para o write_callback (estado da resposta)
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
// Executa a requisição HTTP de forma síncrona
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "Erro na requisição: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
free(resp.data);
return 1;
}
// Libera o handle do curl (a resposta já está em resp.data)
curl_easy_cleanup(curl);
// Estrutura para capturar erros de parsing JSON
json_error_t error;
// Converte a string JSON recebida em um objeto JSON manipulável
json_t *obj = json_loads(resp.data, 0, &error);
free(resp.data); // Não é mais necessária após o parsing
// Caso o JSON seja inválido
if (!obj) {
fprintf(stderr, "Erro ao parsear JSON: %s\n", error.text);
return 1;
}
// Obtém o campo "result" do JSON raiz
json_t *result_obj = json_object_get(obj, "result");
const char *result_str = json_string_value(result_obj);
// Caso a API tenha retornado erro
if (strcmp(result_str, "error") == 0) {
// Tipo de erro retornado pela API
const char *err_type =
json_string_value(json_object_get(obj, "error_type"));
// Mensagem de erro legível
const char *err_msg =
json_string_value(json_object_get(obj, "message"));
// Tratamento específico para erro de autenticação
if (strcmp(err_type, "invalid_auth") == 0) {
printf("Usuário ou senha incorretos!\n");
} else {
printf("Erro! %s\n", err_msg);
}
// Diminui o contador de referência do objeto JSON
json_decref(obj);
return 1;
}
// Caso a requisição tenha sido bem-sucedida
if (strcmp(result_str, "success") == 0) {
// Objeto "info" contém os dados do site
json_t *info_obj = json_object_get(obj, "info");
// Impressão direta dos campos retornados
printf("\nSitename: %s\n",
json_string_value(json_object_get(info_obj, "sitename")));
printf("Hits: %d\n",
(int)json_integer_value(json_object_get(info_obj, "hits")));
printf("Created at: %s\n",
json_string_value(json_object_get(info_obj, "created_at")));
printf("Last updated: %s\n",
json_string_value(json_object_get(info_obj, "last_updated")));
printf("Domain: %s\n",
json_string_value(json_object_get(info_obj, "domain")));
// Array de tags associadas ao site
json_t *tags = json_object_get(info_obj, "tags");
printf("Tags: ");
size_t index;
json_t *tag;
// Itera sobre o array JSON "tags"
json_array_foreach(tags, index, tag) {
printf(
"#%s%s",
json_string_value(tag),
(index < json_array_size(tags) - 1) ? ", " : "."
);
}
printf("\n\n");
}
// Libera o objeto JSON principal
json_decref(obj);
return 0;
}

View File

@@ -1,12 +0,0 @@
#ifndef INFO
#define INFO
// ^ Guardas de inclusão (include guards)
// Evitam que este header seja processado mais de uma vez
// durante a compilação, o que causaria redefinições.
// Declaração da função que busca informações do Neocities.
// O `void` indica explicitamente que a função não recebe parâmetros.
int fetch_neocities_info(void);
#endif
// ^ Fim do include guard

View File

@@ -1,27 +0,0 @@
#!/bin/bash
set -e
CONFIG_DIR="$HOME/.config/neocities"
CONFIG_FILE="$CONFIG_DIR/credentials"
mkdir -p "$CONFIG_DIR"
chmod 700 "$CONFIG_DIR"
read -p "Username: " USER
read -s -p "Password: " PASS
echo
read -p "Would you like to see the credentials on your screen to confirm? [Y/n]: " CHOICE
case $CHOICE in
y|Y) echo -e "Username: $USER\nPassword: $PASS" ;;
*) echo "" ;;
esac
cat > "$CONFIG_FILE" <<EOF
user = $USER
pass = $PASS
EOF
chmod 600 "$CONFIG_FILE"
echo "Credentials saved in $CONFIG_FILE"

View File

@@ -1,93 +0,0 @@
#include <stdio.h> // printf, fprintf
#include <stdlib.h> // malloc, realloc, free
#include <string.h> // strcmp, memcpy
#include <curl/curl.h> // libcurl (necessário para callbacks e tipos)
#include "main.h" // Declarações globais do projeto (upload_func, etc.)
#include "info.h" // Declaração de fetch_neocities_info()
// Estrutura usada para acumular a resposta HTTP em memória.
// É preenchida incrementalmente pelo write_callback.
struct response {
char *data; // Buffer dinâmico com os dados recebidos
size_t len; // Quantidade atual de bytes válidos em data
};
// Callback chamado pelo libcurl sempre que um novo bloco de dados chega.
// Não garante tamanho fixo nem chamada única.
size_t write_callback(void *data, size_t size, size_t nmemb, void *userdata) {
// userdata aponta para a struct response passada via CURLOPT_WRITEDATA
struct response *resp = (struct response *)userdata;
// Tamanho real do bloco recebido
size_t chunk_size = size * nmemb;
// Realoca o buffer para caber os novos dados + '\0'
char *tmp = realloc(resp->data, resp->len + chunk_size + 1);
if (!tmp) {
// Se realloc falhar, aborta a escrita
// Retornar 0 sinaliza erro ao libcurl
fprintf(stderr, "Erro de memória\n");
return 0;
}
// Atualiza o ponteiro com o novo buffer
resp->data = tmp;
// Copia o bloco recebido para o final do buffer atual
memcpy(resp->data + resp->len, data, chunk_size);
// Atualiza o tamanho total acumulado
resp->len += chunk_size;
// Garante que o buffer seja uma string C válida
resp->data[resp->len] = '\0';
// Retornar o número de bytes processados indica sucesso
return chunk_size;
}
// Tipo de ponteiro para função que representa um comando CLI.
// Todas as funções de comando retornam int e não recebem argumentos.
typedef int (*cmd_func_t)(void);
// Estrutura que associa:
// - nome do comando (string digitada no terminal)
// - função que implementa esse comando
typedef struct {
const char *name;
cmd_func_t func;
} command_entry;
int main(int argc, char *argv[]) {
// Tabela de comandos suportados pelo programa.
// Facilita adicionar novos comandos sem alterar a lógica principal.
command_entry commands[] = {
{"--info", fetch_neocities_info},
// {"--upload", upload_func}
};
// Verifica se ao menos um argumento foi passado
if (argc < 2) {
printf("No command provided.\n");
return 0;
}
// Percorre a tabela de comandos procurando correspondência
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
// Compara o argumento passado com o nome do comando
if (strcmp(argv[1], commands[i].name) == 0) {
// Executa a função associada ao comando
// e retorna imediatamente o código dela
return commands[i].func();
}
}
// Se nenhum comando conhecido foi encontrado
printf("Unknown command.\n");
return 0;
}

View File

@@ -1,31 +0,0 @@
#ifndef MAIN_H
#define MAIN_H
// Include guard:
// Impede múltiplas inclusões deste header no mesmo processo de compilação.
#include <stddef.h> // size_t
// Necessário para o tipo size_t, usado em tamanhos e contadores de memória.
// Declarações externas de variáveis globais.
// O `extern` indica que a definição real existe em outro arquivo .c.
extern const char *user;
extern const char *pass;
// Estrutura usada para armazenar dados recebidos dinamicamente,
// geralmente como resposta de uma requisição HTTP.
struct response {
char *data; // Buffer dinâmico contendo os dados acumulados
size_t len; // Quantidade de bytes válidos em data
};
// Protótipo da função de callback usada pelo libcurl.
// Essa função será chamada repetidamente conforme dados chegam.
size_t write_callback(
void *ptr, // Ponteiro para os dados recebidos
size_t size, // Tamanho de cada elemento
size_t nmemb, // Número de elementos
void *userdata // Ponteiro de contexto definido pelo usuário
);
#endif
// Fim do include guard

1
test.txt Normal file
View File

@@ -0,0 +1 @@
hellooo