2026-01-03 10:20:13 +11:00
|
|
|
#include <groundext.h>
|
|
|
|
|
#include <groundvm.h>
|
2026-03-18 05:53:04 +11:00
|
|
|
#include <curl/curl.h>
|
2026-01-03 10:20:13 +11:00
|
|
|
#include <stdio.h>
|
2026-03-18 05:53:04 +11:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
|
|
|
|
|
CURL* curlHandle = NULL;
|
|
|
|
|
|
|
|
|
|
GroundStruct template;
|
2026-01-03 10:20:13 +11:00
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
const char* VALID_REQUEST_TYPES[] = {
|
|
|
|
|
"GET",
|
|
|
|
|
"POST"
|
|
|
|
|
};
|
2026-01-03 10:20:13 +11:00
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
char* data;
|
|
|
|
|
size_t size;
|
|
|
|
|
} ResponseBuffer;
|
|
|
|
|
|
|
|
|
|
size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) {
|
|
|
|
|
size_t total_size = size * nmemb;
|
|
|
|
|
ResponseBuffer* buffer = (ResponseBuffer*)userdata;
|
|
|
|
|
|
|
|
|
|
char* new_data = realloc(buffer->data, buffer->size + total_size + 1);
|
|
|
|
|
if (new_data == NULL) {
|
|
|
|
|
printf("Failed to allocate memory\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buffer->data = new_data;
|
|
|
|
|
memcpy(buffer->data + buffer->size, ptr, total_size);
|
|
|
|
|
buffer->size += total_size;
|
|
|
|
|
buffer->data[buffer->size] = '\0';
|
|
|
|
|
|
|
|
|
|
return total_size;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
GroundValue ground_request(GroundScope* scope, List args) {
|
2026-01-03 10:20:13 +11:00
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
GroundObject obj = *args.values[0].data.customVal;
|
2026-01-03 10:20:13 +11:00
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
// check arg types
|
|
|
|
|
GroundObjectField* address = groundFindField(obj, "address");
|
|
|
|
|
if (address == NULL || address->value.type != STRING) {
|
|
|
|
|
ERROR("Object does not have address field as string", "ValueError");
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
2026-03-18 05:53:04 +11:00
|
|
|
GroundObjectField* type = groundFindField(obj, "type");
|
|
|
|
|
if (type == NULL || type->value.type != STRING) {
|
|
|
|
|
ERROR("Object does not have type field as string", "ValueError");
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
2026-03-18 05:53:04 +11:00
|
|
|
GroundObjectField* userAgent = groundFindField(obj, "userAgent");
|
|
|
|
|
if (userAgent == NULL || userAgent->value.type != STRING) {
|
|
|
|
|
ERROR("Object does not have userAgent field as string", "ValueError");
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
2026-03-18 05:53:04 +11:00
|
|
|
GroundObjectField* httpHeaders = groundFindField(obj, "httpHeaders");
|
|
|
|
|
if (httpHeaders == NULL || httpHeaders->value.type != LIST) {
|
|
|
|
|
ERROR("Object does not have httpHeaders field as list<string>", "ValueError");
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
2026-03-18 05:53:04 +11:00
|
|
|
GroundObjectField* verbose = groundFindField(obj, "verbose");
|
|
|
|
|
if (verbose == NULL || verbose->value.type != BOOL) {
|
|
|
|
|
ERROR("Object does not have verbose field as bool", "ValueError");
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
|
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
// check request type string
|
|
|
|
|
bool requestTypeOk = false;
|
|
|
|
|
for (int i = 0; i < sizeof(VALID_REQUEST_TYPES)/sizeof(VALID_REQUEST_TYPES[0]); i++) {
|
|
|
|
|
if (strcmp(type->value.data.stringVal, VALID_REQUEST_TYPES[i]) == 0) {
|
|
|
|
|
requestTypeOk = true;
|
|
|
|
|
break;
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-18 05:53:04 +11:00
|
|
|
if (!requestTypeOk)
|
|
|
|
|
ERROR("Invalid request type! Choices: GET, POST", "ValueError");
|
2026-01-03 10:20:13 +11:00
|
|
|
|
|
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
ResponseBuffer buffer = {0};
|
2026-01-03 10:20:13 +11:00
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
// set curl params
|
|
|
|
|
curl_easy_setopt(curlHandle, CURLOPT_URL, address->value.data.stringVal);
|
|
|
|
|
curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, write_callback);
|
|
|
|
|
curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, (void*)&buffer);
|
|
|
|
|
curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, userAgent->value.data.stringVal);
|
|
|
|
|
curl_easy_setopt(curlHandle, CURLOPT_VERBOSE, verbose->value.data.boolVal);
|
2026-01-03 10:20:13 +11:00
|
|
|
|
2026-03-18 05:53:04 +11:00
|
|
|
// append headers to http request
|
|
|
|
|
struct curl_slist* httpHeaderList = NULL;
|
|
|
|
|
GroundValue* httpHeaderStrings = httpHeaders->value.data.listVal.values;
|
|
|
|
|
for (int i = 0; i < httpHeaders->value.data.listVal.size; i++) {
|
|
|
|
|
httpHeaderList = curl_slist_append(httpHeaderList, httpHeaderStrings[i].data.stringVal);
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
2026-03-18 05:53:04 +11:00
|
|
|
curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, httpHeaderList);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CURLcode curlStatus = curl_easy_perform(curlHandle);
|
|
|
|
|
curl_slist_free_all(httpHeaderList); // dont want em' memory leaks :P - spooopy
|
|
|
|
|
|
|
|
|
|
// check for any curl errors
|
|
|
|
|
if (curlStatus != CURLE_OK) {
|
|
|
|
|
char curlErrorMsg[255] = {0};
|
|
|
|
|
|
|
|
|
|
snprintf(curlErrorMsg, 255, "%s\n", curl_easy_strerror(curlStatus));
|
|
|
|
|
ERROR(curlErrorMsg, "CurlError");
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
2026-03-18 05:53:04 +11:00
|
|
|
|
|
|
|
|
// get the http response code
|
|
|
|
|
long httpCode = 0;
|
|
|
|
|
curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpCode);
|
|
|
|
|
|
|
|
|
|
// create return value and return it
|
|
|
|
|
GroundValue value = groundCreateValue(CUSTOM, &template);
|
|
|
|
|
|
|
|
|
|
GroundObjectField* status = groundFindField(*value.data.customVal, "statusCode");
|
|
|
|
|
status->value.data.intVal = httpCode;
|
|
|
|
|
|
|
|
|
|
GroundObjectField* data = groundFindField(*value.data.customVal, "data");
|
|
|
|
|
data->value.data.stringVal = buffer.data;
|
|
|
|
|
|
|
|
|
|
value.type = CUSTOM;
|
|
|
|
|
|
|
|
|
|
return value;
|
2026-01-03 10:20:13 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ground_init(GroundScope* scope) {
|
2026-03-18 05:53:04 +11:00
|
|
|
// init libcurl
|
|
|
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
|
curlHandle = curl_easy_init();
|
|
|
|
|
|
|
|
|
|
groundAddValueToScope(scope, "requestInitSuccess", groundCreateValue(BOOL, curlHandle != NULL));
|
|
|
|
|
if (curlHandle == NULL) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// create struct
|
|
|
|
|
template = groundCreateStruct();
|
|
|
|
|
groundAddFieldToStruct(&template, "statusCode", groundCreateValue(INT, 200));
|
|
|
|
|
groundAddFieldToStruct(&template, "data", groundCreateValue(STRING, ""));
|
|
|
|
|
groundAddFieldToStruct(&template, "ok", groundCreateValue(BOOL, 1));
|
|
|
|
|
|
|
|
|
|
// create struct that users can pass to this library
|
|
|
|
|
GroundStruct requestStruct = groundCreateStruct();
|
|
|
|
|
groundAddFieldToStruct(&requestStruct, "address", groundCreateValue(STRING, ""));
|
|
|
|
|
groundAddFieldToStruct(&requestStruct, "type", groundCreateValue(STRING, "GET"));
|
|
|
|
|
groundAddFieldToStruct(&requestStruct, "userAgent", groundCreateValue(STRING, "Ground/1.0"));
|
|
|
|
|
groundAddFieldToStruct(&requestStruct, "httpHeaders", groundCreateValue(LIST, 0));
|
|
|
|
|
groundAddFieldToStruct(&requestStruct, "verbose", groundCreateValue(BOOL, 0));
|
|
|
|
|
|
|
|
|
|
groundAddValueToScope(scope, "request", groundCreateValue(STRUCTVAL, requestStruct));
|
|
|
|
|
|
|
|
|
|
// create functions
|
|
|
|
|
groundAddNativeFunction(scope, "request_Request", ground_request, CUSTOM, 1, CUSTOM, "requestInfo");
|
|
|
|
|
}
|