1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2021 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
20 static char http_error[MAX_HTTP_ERROR_SIZE];
22 static void SetHttpError(char *format, ...)
27 vsnprintf(http_error, MAX_HTTP_ERROR_SIZE, format, ap);
31 char *GetHttpError(void)
36 void ConvertHttpRequestBodyToServerEncoding(struct HttpRequest *request)
38 char *body_utf8 = getUTF8FromLatin1(request->body);
40 strncpy(request->body, body_utf8, MAX_HTTP_BODY_SIZE);
41 request->body[MAX_HTTP_BODY_SIZE] = '\0';
43 checked_free(body_utf8);
46 void ConvertHttpResponseBodyToClientEncoding(struct HttpResponse *response)
48 char *body_latin1 = getLatin1FromUTF8(response->body);
50 strncpy(response->body, body_latin1, MAX_HTTP_BODY_SIZE);
51 response->body[MAX_HTTP_BODY_SIZE] = '\0';
53 response->body_size = strlen(response->body);
55 checked_free(body_latin1);
58 static void SetHttpResponseToDefaults(struct HttpResponse *response)
60 response->head[0] = '\0';
61 response->body[0] = '\0';
62 response->body_size = 0;
64 response->status_code = 0;
65 response->status_text[0] = '\0';
68 struct HttpResponse *GetHttpResponseFromBuffer(void *buffer, int body_size)
70 if (body_size > MAX_HTTP_BODY_SIZE)
73 struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
75 SetHttpResponseToDefaults(response);
77 memcpy(response->body, buffer, body_size);
78 response->body[body_size] = '\0';
79 response->body_size = body_size;
84 static boolean SetHTTPResponseCode(struct HttpResponse *response, char *buffer)
86 char *prefix = "HTTP/1.1 ";
87 char *prefix_start = strstr(buffer, prefix);
89 if (prefix_start == NULL)
92 char *status_code_start = prefix_start + strlen(prefix);
93 char *status_code_end = strstr(status_code_start, " ");
95 if (status_code_end == NULL)
98 int status_code_size = status_code_end - status_code_start;
100 if (status_code_size != 3) // status code must have three digits
103 char status_code[status_code_size + 1];
105 strncpy(status_code, status_code_start, status_code_size);
106 status_code[status_code_size] = '\0';
108 response->status_code = atoi(status_code);
110 char *status_text_start = status_code_end + 1;
111 char *status_text_end = strstr(status_text_start, "\r\n");
113 if (status_text_end == NULL)
116 int status_text_size = status_text_end - status_text_start;
118 if (status_text_size > MAX_HTTP_ERROR_SIZE)
121 strncpy(response->status_text, status_text_start, status_text_size);
122 response->status_text[status_text_size] = '\0';
127 static boolean SetHTTPResponseHead(struct HttpResponse *response, char *buffer)
129 char *separator = "\r\n\r\n";
130 char *separator_start = strstr(buffer, separator);
132 if (separator_start == NULL)
135 int head_size = separator_start - buffer;
137 if (head_size > MAX_HTTP_HEAD_SIZE)
140 strncpy(response->head, buffer, head_size);
141 response->head[head_size] = '\0';
146 static boolean SetHTTPResponseBody(struct HttpResponse *response, char *buffer,
149 char *separator = "\r\n\r\n";
150 char *separator_start = strstr(buffer, separator);
152 if (separator_start == NULL)
155 int separator_size = strlen(separator);
156 int full_head_size = separator_start + separator_size - buffer;
157 int body_size = buffer_size - full_head_size;
159 if (body_size > MAX_HTTP_BODY_SIZE)
162 memcpy(response->body, buffer + full_head_size, body_size);
163 response->body[body_size] = '\0';
164 response->body_size = body_size;
169 static int GetHttpResponse(TCPsocket socket, char *buffer, int max_buffer_size)
171 char *buffer_ptr = buffer;
172 int buffer_left = max_buffer_size;
174 int response_size = 0;
178 // read as many bytes to the buffer as possible
179 int bytes = SDLNet_TCP_Recv(socket, buffer_ptr, buffer_left);
183 SetHttpError("receiving response from server failed");
189 buffer_size += bytes;
190 buffer_left -= bytes;
192 // check if response size was already determined
193 if (response_size > 0)
195 // check if response data was completely received
196 if (buffer_size >= response_size)
199 // continue reading response body from server
203 char *separator = "\r\n\r\n";
204 char *separator_start = strstr(buffer, separator);
205 int separator_size = strlen(separator);
207 // check if response header was completely received
208 if (separator_start == NULL)
210 // continue reading response header from server
214 char *content_length = "Content-Length: ";
215 char *content_length_start = strstr(buffer, content_length);
216 int head_size = separator_start - buffer;
218 // check if response header contains content length header
219 if (content_length_start == NULL ||
220 content_length_start >= buffer + head_size)
222 SetHttpError("receiving 'Content-Length' header from server failed");
227 char *content_length_value = content_length_start + strlen(content_length);
228 char *content_length_end = strstr(content_length_value, "\r\n");
230 // check if content length header has line termination
231 if (content_length_end == NULL)
233 SetHttpError("receiving 'Content-Length' value from server failed");
238 int value_len = content_length_end - content_length_value;
239 int max_value_len = 10;
241 // check if content length header has valid size
242 if (value_len > max_value_len)
244 SetHttpError("received invalid 'Content-Length' value from server");
249 char value_str[value_len + 1];
251 strncpy(value_str, content_length_value, value_len);
252 value_str[value_len] = '\0';
254 int body_size = atoi(value_str);
256 response_size = head_size + separator_size + body_size;
258 // check if response data was completely received
259 if (buffer_size >= response_size)
266 static boolean DoHttpRequestExt(struct HttpRequest *request,
267 struct HttpResponse *response,
270 int max_http_buffer_size,
271 SDLNet_SocketSet *socket_set,
277 SetHttpResponseToDefaults(response);
279 *socket_set = SDLNet_AllocSocketSet(1);
281 if (*socket_set == NULL)
283 SetHttpError("cannot allocate socket set");
288 SDLNet_ResolveHost(&ip, request->hostname, request->port);
290 if (ip.host == INADDR_NONE)
292 SetHttpError("cannot resolve hostname '%s'", request->hostname);
297 server_host = SDLNet_Read32(&ip.host);
299 Debug("network:http", "trying to connect to server at %d.%d.%d.%d ...",
300 (server_host >> 24) & 0xff,
301 (server_host >> 16) & 0xff,
302 (server_host >> 8) & 0xff,
303 (server_host >> 0) & 0xff);
305 *socket = SDLNet_TCP_Open(&ip);
309 SetHttpError("cannot connect to host '%s': %s", request->hostname,
315 if (SDLNet_TCP_AddSocket(*socket_set, *socket) == -1)
317 SetHttpError("cannot add socket to socket set");
322 Debug("network:http", "successfully connected to server");
324 snprintf(request->head, MAX_HTTP_HEAD_SIZE,
327 "X-Requested-With: XMLHttpRequest\r\n"
328 "Content-Type: application/json\r\n"
329 "Connection: close\r\n"
330 "Content-Length: %d\r\n",
334 (int)strlen(request->body));
336 snprintf(send_buffer, max_http_buffer_size,
337 "%s\r\n%s", request->head, request->body);
339 Debug("network:http", "client request:\n--- snip ---\n%s\n--- snip ---",
342 int send_bytes = SDLNet_TCP_Send(*socket, send_buffer, strlen(send_buffer));
344 if (send_bytes != strlen(send_buffer))
346 SetHttpError("sending request to server failed");
351 int recv_bytes = GetHttpResponse(*socket, recv_buffer, max_http_buffer_size);
355 // HTTP error already set in GetHttpResponse()
360 recv_buffer[recv_bytes] = '\0';
362 Debug("network:http", "server response:\n--- snip ---\n%s\n--- snip ---",
365 if (!SetHTTPResponseCode(response, recv_buffer))
367 SetHttpError("malformed HTTP response");
372 if (!SetHTTPResponseHead(response, recv_buffer))
374 SetHttpError("invalid HTTP response header");
379 if (!SetHTTPResponseBody(response, recv_buffer, recv_bytes))
381 SetHttpError("invalid HTTP response body");
386 Debug("network:http", "server response: %d %s",
387 response->status_code,
388 response->status_text);
393 boolean DoHttpRequest(struct HttpRequest *request,
394 struct HttpResponse *response)
396 int max_http_buffer_size = MAX_HTTP_HEAD_SIZE + 2 + MAX_HTTP_BODY_SIZE + 1;
397 char *send_buffer = checked_malloc(max_http_buffer_size);
398 char *recv_buffer = checked_malloc(max_http_buffer_size);
399 SDLNet_SocketSet socket_set = NULL;
400 TCPsocket socket = NULL;
402 boolean success = DoHttpRequestExt(request, response,
403 send_buffer, recv_buffer,
404 max_http_buffer_size,
405 &socket_set, &socket);
406 if (socket_set != NULL)
410 SDLNet_TCP_DelSocket(socket_set, socket);
411 SDLNet_TCP_Close(socket);
414 SDLNet_FreeSocketSet(socket_set);
417 checked_free(send_buffer);
418 checked_free(recv_buffer);
420 runtime.use_api_server = success;