added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / src / libgame / http.c
index 74fafb379713e61b5d4d7a9edf768da73090a896..c83c91f71b1515695da8540a0f7c384b9c4f74ea 100644 (file)
@@ -37,7 +37,9 @@ void ConvertHttpRequestBodyToServerEncoding(struct HttpRequest *request)
 {
   char *body_utf8 = getUTF8FromLatin1(request->body);
 
-  strcpy(request->body, body_utf8);
+  strncpy(request->body, body_utf8, MAX_HTTP_BODY_SIZE);
+  request->body[MAX_HTTP_BODY_SIZE] = '\0';
+
   checked_free(body_utf8);
 }
 
@@ -45,10 +47,12 @@ void ConvertHttpResponseBodyToClientEncoding(struct HttpResponse *response)
 {
   char *body_latin1 = getLatin1FromUTF8(response->body);
 
-  strcpy(response->body, body_latin1);
-  checked_free(body_latin1);
+  strncpy(response->body, body_latin1, MAX_HTTP_BODY_SIZE);
+  response->body[MAX_HTTP_BODY_SIZE] = '\0';
 
   response->body_size = strlen(response->body);
+
+  checked_free(body_latin1);
 }
 
 static void SetHttpResponseToDefaults(struct HttpResponse *response)
@@ -61,6 +65,22 @@ static void SetHttpResponseToDefaults(struct HttpResponse *response)
   response->status_text[0] = '\0';
 }
 
+struct HttpResponse *GetHttpResponseFromBuffer(void *buffer, int body_size)
+{
+  if (body_size > MAX_HTTP_BODY_SIZE)
+    return NULL;
+
+  struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
+
+  SetHttpResponseToDefaults(response);
+
+  memcpy(response->body, buffer, body_size);
+  response->body[body_size] = '\0';
+  response->body_size = body_size;
+
+  return response;
+}
+
 static boolean SetHTTPResponseCode(struct HttpResponse *response, char *buffer)
 {
   char *prefix = "HTTP/1.1 ";
@@ -146,19 +166,119 @@ static boolean SetHTTPResponseBody(struct HttpResponse *response, char *buffer,
   return TRUE;
 }
 
-boolean DoHttpRequest(struct HttpRequest *request,
-                     struct HttpResponse *response)
+static int GetHttpResponse(TCPsocket socket, char *buffer, int max_buffer_size)
+{
+  char *buffer_ptr = buffer;
+  int buffer_left = max_buffer_size;
+  int buffer_size = 0;
+  int response_size = 0;
+
+  while (1)
+  {
+    // read as many bytes to the buffer as possible
+    int bytes = SDLNet_TCP_Recv(socket, buffer_ptr, buffer_left);
+
+    if (bytes <= 0)
+    {
+      SetHttpError("receiving response from server failed");
+
+      return -1;
+    }
+
+    buffer_ptr += bytes;
+    buffer_size += bytes;
+    buffer_left -= bytes;
+
+    // check if response size was already determined
+    if (response_size > 0)
+    {
+      // check if response data was completely received
+      if (buffer_size >= response_size)
+       break;
+
+      // continue reading response body from server
+      continue;
+    }
+
+    char *separator = "\r\n\r\n";
+    char *separator_start = strstr(buffer, separator);
+    int separator_size = strlen(separator);
+
+    // check if response header was completely received
+    if (separator_start == NULL)
+    {
+      // continue reading response header from server
+      continue;
+    }
+
+    char *content_length = "Content-Length: ";
+    char *content_length_start = strstr(buffer, content_length);
+    int head_size = separator_start - buffer;
+
+    // check if response header contains content length header
+    if (content_length_start == NULL ||
+       content_length_start >= buffer + head_size)
+    {
+      SetHttpError("receiving 'Content-Length' header from server failed");
+
+      return -1;
+    }
+
+    char *content_length_value = content_length_start + strlen(content_length);
+    char *content_length_end = strstr(content_length_value, "\r\n");
+
+    // check if content length header has line termination
+    if (content_length_end == NULL)
+    {
+      SetHttpError("receiving 'Content-Length' value from server failed");
+
+      return -1;
+    }
+
+    int value_len = content_length_end - content_length_value;
+    int max_value_len = 10;
+
+    // check if content length header has valid size
+    if (value_len > max_value_len)
+    {
+      SetHttpError("received invalid 'Content-Length' value from server");
+
+      return -1;
+    }
+
+    char value_str[value_len + 1];
+
+    strncpy(value_str, content_length_value, value_len);
+    value_str[value_len] = '\0';
+
+    int body_size = atoi(value_str);
+
+    response_size = head_size + separator_size + body_size;
+
+    // check if response data was completely received
+    if (buffer_size >= response_size)
+      break;
+  }
+
+  return buffer_size;
+}
+
+static boolean DoHttpRequestExt(struct HttpRequest *request,
+                               struct HttpResponse *response,
+                               char *send_buffer,
+                               char *recv_buffer,
+                               int max_http_buffer_size,
+                               SDLNet_SocketSet *socket_set,
+                               TCPsocket *socket)
 {
-  SDLNet_SocketSet socket_set;
-  TCPsocket socket;
   IPaddress ip;
   int server_host;
 
   SetHttpResponseToDefaults(response);
 
-  socket_set = SDLNet_AllocSocketSet(1);
+  *socket_set = SDLNet_AllocSocketSet(1);
 
-  if (socket_set == NULL)
+  if (*socket_set == NULL)
   {
     SetHttpError("cannot allocate socket set");
 
@@ -182,9 +302,9 @@ boolean DoHttpRequest(struct HttpRequest *request,
         (server_host >>  8) & 0xff,
         (server_host >>  0) & 0xff);
 
-  socket = SDLNet_TCP_Open(&ip);
+  *socket = SDLNet_TCP_Open(&ip);
 
-  if (socket == NULL)
+  if (*socket == NULL)
   {
     SetHttpError("cannot connect to host '%s': %s", request->hostname,
                 SDLNet_GetError());
@@ -192,7 +312,7 @@ boolean DoHttpRequest(struct HttpRequest *request,
     return FALSE;
   }
 
-  if (SDLNet_TCP_AddSocket(socket_set, socket) == -1)
+  if (SDLNet_TCP_AddSocket(*socket_set, *socket) == -1)
   {
     SetHttpError("cannot add socket to socket set");
 
@@ -213,17 +333,13 @@ boolean DoHttpRequest(struct HttpRequest *request,
           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));
+  int send_bytes = SDLNet_TCP_Send(*socket, send_buffer, strlen(send_buffer));
 
   if (send_bytes != strlen(send_buffer))
   {
@@ -232,11 +348,11 @@ boolean DoHttpRequest(struct HttpRequest *request,
     return FALSE;
   }
 
-  int recv_bytes = SDLNet_TCP_Recv(socket, recv_buffer, max_http_buffer_size);
+  int recv_bytes = GetHttpResponse(*socket, recv_buffer, max_http_buffer_size);
 
   if (recv_bytes <= 0)
   {
-    SetHttpError("receiving response from server failed");
+    // HTTP error already set in GetHttpResponse()
 
     return FALSE;
   }
@@ -267,12 +383,41 @@ boolean DoHttpRequest(struct HttpRequest *request,
     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;
 }
+
+boolean DoHttpRequest(struct HttpRequest *request,
+                     struct HttpResponse *response)
+{
+  int max_http_buffer_size = MAX_HTTP_HEAD_SIZE + 2 + MAX_HTTP_BODY_SIZE + 1;
+  char *send_buffer = checked_malloc(max_http_buffer_size);
+  char *recv_buffer = checked_malloc(max_http_buffer_size);
+  SDLNet_SocketSet socket_set = NULL;
+  TCPsocket socket = NULL;
+
+  boolean success = DoHttpRequestExt(request, response,
+                                    send_buffer, recv_buffer,
+                                    max_http_buffer_size,
+                                    &socket_set, &socket);
+  if (socket_set != NULL)
+  {
+    if (socket != NULL)
+    {
+      SDLNet_TCP_DelSocket(socket_set, socket);
+      SDLNet_TCP_Close(socket);
+    }
+
+    SDLNet_FreeSocketSet(socket_set);
+  }
+
+  checked_free(send_buffer);
+  checked_free(recv_buffer);
+
+  runtime.use_api_server = success;
+
+  return success;
+}