added basic HTTP support
authorHolger Schemel <info@artsoft.org>
Sat, 24 Apr 2021 22:00:26 +0000 (00:00 +0200)
committerHolger Schemel <info@artsoft.org>
Sat, 1 May 2021 13:38:30 +0000 (15:38 +0200)
src/libgame/Makefile
src/libgame/http.c [new file with mode: 0644]
src/libgame/http.h [new file with mode: 0644]
src/libgame/libgame.h

index 0e2d57d6b0cebd053bcf48d1a13c02d7e5bc4b1c..246655f5ddbf029dc45926d453db86cad3db39d6 100644 (file)
@@ -22,6 +22,7 @@ SRCS =        system.c        \
        image.c         \
        random.c        \
        hash.c          \
+       http.c          \
        base64.c        \
        setup.c         \
        misc.c          \
@@ -40,6 +41,7 @@ OBJS =        system.o        \
        image.o         \
        random.o        \
        hash.o          \
+       http.o          \
        base64.o        \
        setup.o         \
        misc.o          \
diff --git a/src/libgame/http.c b/src/libgame/http.c
new file mode 100644 (file)
index 0000000..2a41b5b
--- /dev/null
@@ -0,0 +1,260 @@
+// ============================================================================
+// Artsoft Retro-Game Library
+// ----------------------------------------------------------------------------
+// (c) 1995-2021 by Artsoft Entertainment
+//                         Holger Schemel
+//                 info@artsoft.org
+//                 https://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// http.c
+// ============================================================================
+
+#include <sys/stat.h>
+
+#include "platform.h"
+
+#include "http.h"
+#include "misc.h"
+
+
+static char http_error[MAX_HTTP_ERROR_SIZE];
+
+static void SetHttpError(char *format, ...)
+{
+  va_list ap;
+
+  va_start(ap, format);
+  vsnprintf(http_error, MAX_HTTP_ERROR_SIZE, format, ap);
+  va_end(ap);
+}
+
+char *GetHttpError(void)
+{
+  return http_error;
+}
+
+static void SetHttpResponseToDefaults(struct HttpResponse *response)
+{
+  response->head[0] = '\0';
+  response->body[0] = '\0';
+  response->body_size = 0;
+
+  response->status_code = 0;
+  response->status_text[0] = '\0';
+}
+
+static boolean SetHTTPResponseCode(struct HttpResponse *response, char *buffer)
+{
+  char *prefix = "HTTP/1.1 ";
+  char *prefix_start = strstr(buffer, prefix);
+
+  if (prefix_start == NULL)
+    return FALSE;
+
+  char *status_code_start = prefix_start + strlen(prefix);
+  char *status_code_end = strstr(status_code_start, " ");
+
+  if (status_code_end == NULL)
+    return FALSE;
+
+  int status_code_size = status_code_end - status_code_start;
+
+  if (status_code_size != 3)   // status code must have three digits
+    return FALSE;
+
+  char status_code[status_code_size + 1];
+
+  strncpy(status_code, status_code_start, status_code_size);
+  status_code[status_code_size] = '\0';
+
+  response->status_code = atoi(status_code);
+
+  char *status_text_start = status_code_end + 1;
+  char *status_text_end = strstr(status_text_start, "\r\n");
+
+  if (status_text_end == NULL)
+    return FALSE;
+
+  int status_text_size = status_text_end - status_text_start;
+
+  if (status_text_size > MAX_HTTP_ERROR_SIZE)
+    return FALSE;
+
+  strncpy(response->status_text, status_text_start, status_text_size);
+  response->status_text[status_text_size] = '\0';
+
+  return TRUE;
+}
+
+static boolean SetHTTPResponseHead(struct HttpResponse *response, char *buffer)
+{
+  char *separator = "\r\n\r\n";
+  char *separator_start = strstr(buffer, separator);
+
+  if (separator_start == NULL)
+    return FALSE;
+
+  int head_size = separator_start - buffer;
+
+  if (head_size > MAX_HTTP_HEAD_SIZE)
+    return FALSE;
+
+  strncpy(response->head, buffer, head_size);
+  response->head[head_size] = '\0';
+
+  return TRUE;
+}
+
+static boolean SetHTTPResponseBody(struct HttpResponse *response, char *buffer,
+                                  int buffer_size)
+{
+  char *separator = "\r\n\r\n";
+  char *separator_start = strstr(buffer, separator);
+
+  if (separator_start == NULL)
+    return FALSE;
+
+  int separator_size = strlen(separator);
+  int full_head_size = separator_start + separator_size - buffer;
+  int body_size = buffer_size - full_head_size;
+
+  if (body_size > MAX_HTTP_BODY_SIZE)
+    return FALSE;
+
+  memcpy(response->body, buffer + full_head_size, body_size);
+  response->body[body_size] = '\0';
+  response->body_size = body_size;
+
+  return TRUE;
+}
+
+boolean DoHttpRequest(struct HttpRequest *request,
+                     struct HttpResponse *response)
+{
+  SDLNet_SocketSet socket_set;
+  TCPsocket socket;
+  IPaddress ip;
+  int server_host;
+
+  SetHttpResponseToDefaults(response);
+
+  socket_set = SDLNet_AllocSocketSet(1);
+
+  if (socket_set == NULL)
+  {
+    SetHttpError("cannot allocate socket set");
+
+    return FALSE;
+  }
+
+  SDLNet_ResolveHost(&ip, request->hostname, request->port);
+
+  if (ip.host == INADDR_NONE)
+  {
+    SetHttpError("cannot resolve hostname '%s'", request->hostname);
+
+    return FALSE;
+  }
+
+  server_host = SDLNet_Read32(&ip.host);
+
+  Debug("network:http", "trying to connect to server at %d.%d.%d.%d ...",
+        (server_host >> 24) & 0xff,
+        (server_host >> 16) & 0xff,
+        (server_host >>  8) & 0xff,
+        (server_host >>  0) & 0xff);
+
+  socket = SDLNet_TCP_Open(&ip);
+
+  if (socket == NULL)
+  {
+    SetHttpError("cannot connect to host '%s': %s", request->hostname,
+                SDLNet_GetError());
+
+    return FALSE;
+  }
+
+  if (SDLNet_TCP_AddSocket(socket_set, socket) == -1)
+  {
+    SetHttpError("cannot add socket to socket set");
+
+    return FALSE;
+  }
+
+  Debug("network:http", "successfully connected to server");
+
+  snprintf(request->head, MAX_HTTP_HEAD_SIZE,
+          "%s %s HTTP/1.1\r\n"
+          "Host: %s\r\n"
+          "X-Requested-With: XMLHttpRequest\r\n"
+          "Content-Type: application/json\r\n"
+          "Connection: close\r\n"
+          "Content-Length: %d\r\n",
+          request->method,
+          request->uri,
+          request->hostname,
+          (int)strlen(request->body));
+
+  int max_http_buffer_size = MAX_HTTP_HEAD_SIZE + MAX_HTTP_BODY_SIZE;
+  char send_buffer[max_http_buffer_size + 1];
+  char recv_buffer[max_http_buffer_size + 1];
+
+  snprintf(send_buffer, max_http_buffer_size,
+          "%s\r\n%s", request->head, request->body);
+
+  Debug("network:http", "client request:\n--- snip ---\n%s\n--- snip ---",
+       send_buffer);
+
+  int send_bytes = SDLNet_TCP_Send(socket, send_buffer, strlen(send_buffer));
+
+  if (send_bytes != strlen(send_buffer))
+  {
+    SetHttpError("sending request to server failed");
+
+    return FALSE;
+  }
+
+  int recv_bytes = SDLNet_TCP_Recv(socket, recv_buffer, max_http_buffer_size);
+
+  if (recv_bytes <= 0)
+  {
+    SetHttpError("receiving response from server failed");
+
+    return FALSE;
+  }
+
+  recv_buffer[recv_bytes] = '\0';
+
+  Debug("network:http", "server response:\n--- snip ---\n%s\n--- snip ---",
+       recv_buffer);
+
+  if (!SetHTTPResponseCode(response, recv_buffer))
+  {
+    SetHttpError("malformed HTTP response");
+
+    return FALSE;
+  }
+
+  if (!SetHTTPResponseHead(response, recv_buffer))
+  {
+    SetHttpError("invalid HTTP response header");
+
+    return FALSE;
+  }
+
+  if (!SetHTTPResponseBody(response, recv_buffer, recv_bytes))
+  {
+    SetHttpError("invalid HTTP response body");
+
+    return FALSE;
+  }
+
+  SDLNet_TCP_DelSocket(socket_set, socket);
+  SDLNet_TCP_Close(socket);
+
+  Debug("network:http", "server response: %d %s",
+       response->status_code,
+       response->status_text);
+
+  return TRUE;
+}
diff --git a/src/libgame/http.h b/src/libgame/http.h
new file mode 100644 (file)
index 0000000..b002ec6
--- /dev/null
@@ -0,0 +1,49 @@
+// ============================================================================
+// Artsoft Retro-Game Library
+// ----------------------------------------------------------------------------
+// (c) 1995-2021 by Artsoft Entertainment
+//                         Holger Schemel
+//                 info@artsoft.org
+//                 https://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// http.h
+// ============================================================================
+
+#ifndef HTTP_H
+#define HTTP_H
+
+#include "system.h"
+
+#define MAX_HTTP_HEAD_SIZE             4096
+#define MAX_HTTP_BODY_SIZE             1048576
+#define MAX_HTTP_ERROR_SIZE            1024
+
+#define HTTP_SUCCESS(c)                        ((c) >= 200 && (c) < 300)
+
+
+struct HttpRequest
+{
+  char head[MAX_HTTP_HEAD_SIZE + 1];
+  char body[MAX_HTTP_BODY_SIZE + 1];
+
+  char *hostname;
+  int port;
+  char *method;
+  char *uri;
+};
+
+struct HttpResponse
+{
+  char head[MAX_HTTP_HEAD_SIZE + 1];
+  char body[MAX_HTTP_BODY_SIZE + 1];
+  int body_size;
+
+  int status_code;
+  char status_text[MAX_HTTP_ERROR_SIZE + 1];
+};
+
+
+char *GetHttpError(void);
+boolean DoHttpRequest(struct HttpRequest *, struct HttpResponse *);
+
+#endif
index 6a3c435aeb4bf52c7ac83d41e795cd42ea638501..2573a635b249052532d0101c8414a65378c2d803 100644 (file)
@@ -26,6 +26,7 @@
 #include "image.h"
 #include "setup.h"
 #include "misc.h"
+#include "http.h"
 #include "base64.h"
 #include "zip/miniunz.h"