view postdog/main.c @ 113:7a4e942814bc

[MrJuneJune] Fixed static assets
author June Park <parkjune1995@gmail.com>
date Sun, 04 Jan 2026 14:42:54 -0800
parents d6d578b49a19
children e2a73e64e8e6
line wrap: on
line source

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <dirent.h>
#include "dowa/dowa.h"

#include <curl/curl.h>
#include "third_party/raylib/include/raylib.h"
#define RAYGUI_IMPLEMENTATION
#include "third_party/raylib/include/raygui.h"

#define SCREEN_WIDTH 1280
#define SCREEN_HEIGHT 780
#define TEXT_SIZE 10

#define JSON_INPUT_BUFFER_LEN 8192
#define HEADER_INPUT_BUFFER_LEN 4096
#define PARAM_INPUT_BUFFER_LEN 4096
#define MAX_HISTORY_ITEMS 100

#define HEADER_BUFFER_LENGTH 1024 * 4
#define DEFAULT_TEXT_BUFFER_LENGTH 1024 * 4
#define URL_TEXT_BUFFER 1024 * 10
#define BODY_BUFFER_LENGTH 1024 * 1025 * 5
#define RESULT_BUFFER_LENGTH 1024 * 1025 * 5
#define AREANA_BUFFER_LENGTH 1024 * 1025 * 15

typedef Dowa_KV(char*, char*) INPUT_HASHMAP;

typedef struct {
  char *data;
  size_t size;
} ResponseBuffer;

typedef struct {
  char filename[256];
  char displayName[128];
  char method[16];
  time_t timestamp;
} HistoryItem;

char *PostDog_Enum_To_String(int active_enum)
{
  switch(active_enum)
  {
    case 0: return "GET";
    case 1: return "POST";
    case 2: return "PUT";
    case 3: return "DELETE";
  }
}

static size_t Postdog_Curl_Callback(void *contents, size_t size, size_t nmemb, void *userp)
{
  size_t real_size = size * nmemb;
  ResponseBuffer *buf = (ResponseBuffer *)userp;

  char *ptr = realloc(buf->data, buf->size + real_size + 1);
  if (ptr == NULL)
  {
    printf("Not enough memory for response\n");
    return 0;
  }

  buf->data = ptr;
  memcpy(&(buf->data[buf->size]), contents, real_size);
  buf->size += real_size;
  buf->data[buf->size] = 0;

  return real_size;
}

int PostDog_Make_HttpRequest(
    const char *url,
    const char *method,
    const char *headers,
    const char *body, 
    char *response, size_t responseSize)
{
  CURL *curl;
  CURLcode res;
  ResponseBuffer buffer = { .data = malloc(1), .size = 0 };

  response[0] = '\n';

  if (buffer.data == NULL)
  {
    snprintf(response, responseSize, "Error: Failed to allocate memory");
    return -1;
  }
  buffer.data[0] = '\0';

  curl_global_init(CURL_GLOBAL_DEFAULT);
  curl = curl_easy_init();

  if (curl)
  {
    struct curl_slist *headerList = NULL;

    // Set URL
    curl_easy_setopt(curl, CURLOPT_URL, url);

    // Set HTTP method
    if (strcmp(method, "POST") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_POST, 1L);
      if (body && strlen(body) > 0) {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
      }
    } 
    else if (strcmp(method, "PUT") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
      if (body && strlen(body) > 0) {
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
      }
    }
    else if (strcmp(method, "DELETE") == 0)
    {
      curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
    }
    // Default is GET

    // Parse and add headers
    if (headers && strlen(headers) > 0)
    {
      char *headersCopy = strdup(headers);
      char *line = strtok(headersCopy, "\n");
      while (line != NULL) {
        // Trim whitespace
        while (*line == ' ' || *line == '\t') line++;
        if (strlen(line) > 0) {
          headerList = curl_slist_append(headerList, line);
        }
        line = strtok(NULL, "\n");
      }
      curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerList);
      free(headersCopy);
    }

    // Set write callback
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Postdog_Curl_Callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&buffer);

    // Follow redirects
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

    // Set timeout
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);

    // Perform request
    res = curl_easy_perform(curl);

    if (res != CURLE_OK)
      snprintf(response, responseSize, "Error: %s\n", curl_easy_strerror(res));
    else
    {
      long response_code;
      curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

      snprintf(response, responseSize, "HTTP Status: %ld\n\n%s",
               response_code, buffer.data ? buffer.data : "");
    }

    // Cleanup
    if (headerList) curl_slist_free_all(headerList);
    curl_easy_cleanup(curl);
  } else {
    snprintf(response, responseSize, "Error: Failed to initialize curl");
  }

  free(buffer.data);
  curl_global_cleanup();

  return 0;
}

typedef struct {
  Rectangle rectangle;
  char *label;
  bool active;
} TabItem;

typedef enum {
  TAB_HEADER = 0,
  TAB_BODY,
  TAB_GET_PARAMS,
  TAB_BAR,
} PostDog_Tab_Enum;

void PostDog_Update_URL(char **p_url_input_text, char* get_params)
{
  char *url_input_text = *p_url_input_text;

  // Reset
  char *question_mark = strchr(url_input_text, '?');
  if (question_mark)
    *question_mark = '\0';

  int get_params_length = (int)strlen(get_params) ;
  if (get_params_length == 0)
    return;

  char *separator = "?";

  Dowa_Arena *arena = Dowa_Arena_Create(1024*1024);
  char **lines = Dowa_String_Split(get_params, "\n", get_params_length, 1, arena);

  size_t url_len = strlen(url_input_text);
  size_t url_capacity = URL_TEXT_BUFFER;

  for (int i = 0; i < Dowa_Array_Length(lines); i++)
  {
    char *line = lines[i];
    char **key_value = Dowa_String_Split(line, " ", (int)strlen(line), 1, arena);

    if (Dowa_Array_Length(key_value) < 2)
      continue;  // Skip this line, not break entire loop

    // Check buffer capacity before each append
    size_t needed = url_len + strlen(separator) + strlen(key_value[0]) + 1 + 10; // +10 for "=" and safety
    if (needed >= url_capacity) break;

    strcat(url_input_text, separator);
    strcat(url_input_text, key_value[0]);
    strcat(url_input_text, "=");
    url_len = strlen(url_input_text);

    for (int i = 1; i < Dowa_Array_Length(key_value); i++)
    {
      if (!key_value[i] || key_value[i][0] == '\0')
        break;

      size_t value_len = strlen(key_value[i]);
      needed = url_len + value_len + 4; // +4 for "%20" if needed
      if (needed >= url_capacity) break;

      printf("\n\n------\n\n");
      printf("key_value[%i]: %s, p = %p\n", i, key_value[i], key_value[i]);
      printf("\n\n------\n\n");

      if (i > 1) strcat(url_input_text, "%20");
      strcat(url_input_text, key_value[i]);
      url_len = strlen(url_input_text);
    }
    separator = "&";
  }

  Dowa_Arena_Free(arena);
}

int main()
{
  // -- initizlied --//
  InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "PostDog");
  SetWindowState(FLAG_WINDOW_RESIZABLE);
  SetTargetFPS(60);

  Dowa_Arena *arena = Dowa_Arena_Create(AREANA_BUFFER_LENGTH);

  // -- Starting pos ---//
  float side_bar_x = 10;
  float side_bar_y = 10;

  Rectangle history_sidebar = { .x = side_bar_x, .y = side_bar_y, .width = 0, .height = 0 };

  Rectangle url_area = { 0 };
  Rectangle textBounds = { 0 };
  Rectangle url_input_bounds = { 0 };
  bool url_input_bool = false;
  Rectangle url_text_bounds = { 0 };
  Rectangle url_enter_button = { 0 };
  Rectangle method_dropdown = { 0 };

  char *url_input_text = (char *)Dowa_Arena_Allocate(arena, URL_TEXT_BUFFER);
  snprintf(url_input_text, URL_TEXT_BUFFER, "https://httpbin.org/get");

  INPUT_HASHMAP *url_body_map = NULL;
  Dowa_HashMap_Push_Arena(url_body_map, "Header", (char *)Dowa_Arena_Allocate(arena, HEADER_BUFFER_LENGTH), arena);
  Dowa_HashMap_Push_Arena(url_body_map, "Body", (char *)Dowa_Arena_Allocate(arena, BODY_BUFFER_LENGTH), arena);
  Dowa_HashMap_Push_Arena(url_body_map, "Get Param", (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);
  Dowa_HashMap_Push_Arena(url_body_map, "Bar",  (char *)Dowa_Arena_Allocate(arena, DEFAULT_TEXT_BUFFER_LENGTH), arena);

  snprintf(url_body_map[0].value, HEADER_BUFFER_LENGTH, "Content-Type: application/json");
  snprintf(url_body_map[1].value, HEADER_BUFFER_LENGTH, "");

  char *url_result_text = (char *)Dowa_Arena_Allocate(arena, RESULT_BUFFER_LENGTH);

  int active_method_dropdown = 0;
  bool method_edit = false;

  int sendRequest;

  // -- input --//
  Rectangle input_area = { 0 };
  Rectangle input_tab = { 0 };
  Rectangle input_tab_item = { 0 };
  Rectangle input_body = { 0 };
  bool input_body_bool = false;

  // -- result --//
  Rectangle result_area = { 0 };
  Rectangle result_body = { 0 };

  // General styling.
  int padding = 10; // TODO make it % based?
  int active_input_tab = 0;

  while (!WindowShouldClose())
  {
    int screen_width = GetScreenWidth();
    int screen_height = GetScreenHeight();

    history_sidebar.width = screen_width * 0.15;
    history_sidebar.height = screen_width - 10;

    // -- URL Area --//
    url_area.x = (side_bar_x + history_sidebar.width);
    url_area.y = (side_bar_y);
    url_area.width = (screen_width - history_sidebar.width) * 0.9;
    url_area.height = (screen_height) * 0.1;

    url_text_bounds.x = url_area.x + padding;
    url_text_bounds.y = (url_area.height) / 2;
    url_text_bounds.width = 7 * (TEXT_SIZE / 2);
    url_text_bounds.height = TEXT_SIZE * 2;

    url_input_bounds.x = url_text_bounds.x + url_text_bounds.width + padding;
    url_input_bounds.y = (url_area.height) / 2;
    url_input_bounds.width = (url_area.width - padding) * 0.7;
    url_input_bounds.height = TEXT_SIZE * 2;

    url_enter_button.x = url_input_bounds.x + url_input_bounds.width + padding;
    url_enter_button.y = (url_area.height) / 2;
    url_enter_button.width = (url_area.width - padding) * 0.1;
    url_enter_button.height = TEXT_SIZE * 2;

    method_dropdown.x = url_enter_button.x + url_enter_button.width + padding;
    method_dropdown.y = (url_area.height) / 2;
    method_dropdown.width = (url_area.width - padding) * 0.1;
    method_dropdown.height = TEXT_SIZE * 2;

    // -- Input Area --//
    input_area.x = (side_bar_x + history_sidebar.width);
    input_area.y = (side_bar_y + url_area.height);
    input_area.width = url_area.width * 0.5;
    input_area.height = screen_height - url_area.height;

    input_tab.x = (side_bar_x + history_sidebar.width) + padding;
    input_tab.y = (side_bar_y + url_area.height) + padding;
    input_tab.width = url_area.width * 0.49 - padding;
    input_tab.height = input_area.height * 0.1;
    input_tab_item = input_tab;
    input_tab_item.width = input_tab.width / 4;

    input_body.x = input_tab.x;
    input_body.y = input_tab.y + input_tab.height;
    input_body.width = url_area.width * 0.49 - padding;
    input_body.height = input_area.height - input_tab.height - padding;

    // -- Result Area --//
    result_area.x = (input_area.x + input_area.width);
    result_area.y = (side_bar_y + url_area.height);
    result_area.width = url_area.width * 0.49;
    result_area.height = screen_height - url_area.height;

    result_body.x = result_area.x + padding;
    result_body.y = result_area.y + input_tab.height + padding;
    result_body.width = url_area.width * 0.49 - padding;
    result_body.height = result_area.height - input_tab.height - padding;

    BeginDrawing();
      ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));

      // Sidebar Rect
      DrawRectangleRec(history_sidebar, Fade(GRAY, 0.1f));

      // URL area Rect
      GuiDrawText("URL: ", url_text_bounds, TEXT_ALIGN_CENTER, RED); 
      DrawRectangleRec(url_area, Fade(RED, 0.1f));
      if (GuiTextBox(url_input_bounds, url_input_text, DEFAULT_TEXT_BUFFER_LENGTH, url_input_bool))
        url_input_bool = !url_input_bool;
      sendRequest = GuiButton(url_enter_button, "ENTER");
      if (sendRequest)
        PostDog_Make_HttpRequest(
            url_input_text,
            PostDog_Enum_To_String(active_method_dropdown),
            url_body_map[0].value,
            url_body_map[1].value,
            url_result_text,
            RESULT_BUFFER_LENGTH
        );
      if (GuiDropdownBox(method_dropdown, "GET;POST;PUT;DELETE", &active_method_dropdown, method_edit))
        method_edit = !method_edit;

      // Input Tabs Rect
      DrawRectangleRec(input_area, Fade(BLUE, 0.1f));
      DrawRectangleRec(input_tab,  Fade(DARKBLUE, 0.1f));
      GuiSetStyle(TOGGLE, GROUP_PADDING, 0);
      if (JUNE_GuiTextBox(input_body, url_body_map[active_input_tab].value, DEFAULT_TEXT_BUFFER_LENGTH, input_body_bool))
        input_body_bool = !input_body_bool;
      GuiToggleGroup(input_tab_item, "Header;Body;Get Param;Bar", &active_input_tab);
      PostDog_Update_URL(&url_input_text, url_body_map[TAB_GET_PARAMS].value);

      // Result Rect
      DrawRectangleRec(result_area, Fade(GREEN, 0.1f));
      DrawRectangleRec(result_body, Fade(DARKGREEN, 0.1f));
      GuiTextBoxMulti(result_body, url_result_text, RESULT_BUFFER_LENGTH, false);
    EndDrawing();
  }
  CloseWindow();
  return 0;
}