renamed variable
[rocksndiamonds.git] / src / libgame / http.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2021 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // http.c
10 // ============================================================================
11
12 #include <sys/stat.h>
13
14 #include "platform.h"
15
16 #include "http.h"
17 #include "misc.h"
18
19
20 static char http_error[MAX_HTTP_ERROR_SIZE];
21
22 static void SetHttpError(char *format, ...)
23 {
24   va_list ap;
25
26   va_start(ap, format);
27   vsnprintf(http_error, MAX_HTTP_ERROR_SIZE, format, ap);
28   va_end(ap);
29 }
30
31 char *GetHttpError(void)
32 {
33   return http_error;
34 }
35
36 void ConvertHttpRequestBodyToServerEncoding(struct HttpRequest *request)
37 {
38   char *body_utf8 = getUTF8FromLatin1(request->body);
39
40   strncpy(request->body, body_utf8, MAX_HTTP_BODY_SIZE);
41   request->body[MAX_HTTP_BODY_SIZE] = '\0';
42
43   checked_free(body_utf8);
44 }
45
46 void ConvertHttpResponseBodyToClientEncoding(struct HttpResponse *response)
47 {
48   char *body_latin1 = getLatin1FromUTF8(response->body);
49
50   strncpy(response->body, body_latin1, MAX_HTTP_BODY_SIZE);
51   response->body[MAX_HTTP_BODY_SIZE] = '\0';
52
53   response->body_size = strlen(response->body);
54
55   checked_free(body_latin1);
56 }
57
58 static void SetHttpResponseToDefaults(struct HttpResponse *response)
59 {
60   response->head[0] = '\0';
61   response->body[0] = '\0';
62   response->body_size = 0;
63
64   response->status_code = 0;
65   response->status_text[0] = '\0';
66 }
67
68 struct HttpResponse *GetHttpResponseFromBuffer(void *buffer, int body_size)
69 {
70   if (body_size > MAX_HTTP_BODY_SIZE)
71     return NULL;
72
73   struct HttpResponse *response = checked_calloc(sizeof(struct HttpResponse));
74
75   SetHttpResponseToDefaults(response);
76
77   memcpy(response->body, buffer, body_size);
78   response->body[body_size] = '\0';
79   response->body_size = body_size;
80
81   return response;
82 }
83
84 static boolean SetHTTPResponseCode(struct HttpResponse *response, char *buffer)
85 {
86   char *prefix = "HTTP/1.1 ";
87   char *prefix_start = strstr(buffer, prefix);
88
89   if (prefix_start == NULL)
90     return FALSE;
91
92   char *status_code_start = prefix_start + strlen(prefix);
93   char *status_code_end = strstr(status_code_start, " ");
94
95   if (status_code_end == NULL)
96     return FALSE;
97
98   int status_code_size = status_code_end - status_code_start;
99
100   if (status_code_size != 3)    // status code must have three digits
101     return FALSE;
102
103   char status_code[status_code_size + 1];
104
105   strncpy(status_code, status_code_start, status_code_size);
106   status_code[status_code_size] = '\0';
107
108   response->status_code = atoi(status_code);
109
110   char *status_text_start = status_code_end + 1;
111   char *status_text_end = strstr(status_text_start, "\r\n");
112
113   if (status_text_end == NULL)
114     return FALSE;
115
116   int status_text_size = status_text_end - status_text_start;
117
118   if (status_text_size > MAX_HTTP_ERROR_SIZE)
119     return FALSE;
120
121   strncpy(response->status_text, status_text_start, status_text_size);
122   response->status_text[status_text_size] = '\0';
123
124   return TRUE;
125 }
126
127 static boolean SetHTTPResponseHead(struct HttpResponse *response, char *buffer)
128 {
129   char *separator = "\r\n\r\n";
130   char *separator_start = strstr(buffer, separator);
131
132   if (separator_start == NULL)
133     return FALSE;
134
135   int head_size = separator_start - buffer;
136
137   if (head_size > MAX_HTTP_HEAD_SIZE)
138     return FALSE;
139
140   strncpy(response->head, buffer, head_size);
141   response->head[head_size] = '\0';
142
143   return TRUE;
144 }
145
146 static boolean SetHTTPResponseBody(struct HttpResponse *response, char *buffer,
147                                    int buffer_size)
148 {
149   char *separator = "\r\n\r\n";
150   char *separator_start = strstr(buffer, separator);
151
152   if (separator_start == NULL)
153     return FALSE;
154
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;
158
159   if (body_size > MAX_HTTP_BODY_SIZE)
160     return FALSE;
161
162   memcpy(response->body, buffer + full_head_size, body_size);
163   response->body[body_size] = '\0';
164   response->body_size = body_size;
165
166   return TRUE;
167 }
168
169 static int GetHttpResponse(TCPsocket socket, char *buffer, int max_buffer_size)
170 {
171   char *buffer_ptr = buffer;
172   int buffer_left = max_buffer_size;
173   int buffer_size = 0;
174   int response_size = 0;
175
176   while (1)
177   {
178     // read as many bytes to the buffer as possible
179     int bytes = SDLNet_TCP_Recv(socket, buffer_ptr, buffer_left);
180
181     if (bytes <= 0)
182     {
183       SetHttpError("receiving response from server failed");
184
185       return -1;
186     }
187
188     buffer_ptr += bytes;
189     buffer_size += bytes;
190     buffer_left -= bytes;
191
192     // check if response size was already determined
193     if (response_size > 0)
194     {
195       // check if response data was completely received
196       if (buffer_size >= response_size)
197         break;
198
199       // continue reading response body from server
200       continue;
201     }
202
203     char *separator = "\r\n\r\n";
204     char *separator_start = strstr(buffer, separator);
205     int separator_size = strlen(separator);
206
207     // check if response header was completely received
208     if (separator_start == NULL)
209     {
210       // continue reading response header from server
211       continue;
212     }
213
214     char *content_length = "Content-Length: ";
215     char *content_length_start = strstr(buffer, content_length);
216     int head_size = separator_start - buffer;
217
218     // check if response header contains content length header
219     if (content_length_start == NULL ||
220         content_length_start >= buffer + head_size)
221     {
222       SetHttpError("receiving 'Content-Length' header from server failed");
223
224       return -1;
225     }
226
227     char *content_length_value = content_length_start + strlen(content_length);
228     char *content_length_end = strstr(content_length_value, "\r\n");
229
230     // check if content length header has line termination
231     if (content_length_end == NULL)
232     {
233       SetHttpError("receiving 'Content-Length' value from server failed");
234
235       return -1;
236     }
237
238     int value_len = content_length_end - content_length_value;
239     int max_value_len = 10;
240
241     // check if content length header has valid size
242     if (value_len > max_value_len)
243     {
244       SetHttpError("received invalid 'Content-Length' value from server");
245
246       return -1;
247     }
248
249     char value_str[value_len + 1];
250
251     strncpy(value_str, content_length_value, value_len);
252     value_str[value_len] = '\0';
253
254     int body_size = atoi(value_str);
255
256     response_size = head_size + separator_size + body_size;
257
258     // check if response data was completely received
259     if (buffer_size >= response_size)
260       break;
261   }
262
263   return buffer_size;
264 }
265
266 static boolean DoHttpRequestExt(struct HttpRequest *request,
267                                 struct HttpResponse *response,
268                                 char *send_buffer,
269                                 char *recv_buffer,
270                                 int max_http_buffer_size,
271                                 SDLNet_SocketSet *socket_set,
272                                 TCPsocket *socket)
273 {
274   IPaddress ip;
275   int server_host;
276
277   SetHttpResponseToDefaults(response);
278
279   *socket_set = SDLNet_AllocSocketSet(1);
280
281   if (*socket_set == NULL)
282   {
283     SetHttpError("cannot allocate socket set");
284
285     return FALSE;
286   }
287
288   SDLNet_ResolveHost(&ip, request->hostname, request->port);
289
290   if (ip.host == INADDR_NONE)
291   {
292     SetHttpError("cannot resolve hostname '%s'", request->hostname);
293
294     return FALSE;
295   }
296
297   server_host = SDLNet_Read32(&ip.host);
298
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);
304
305   *socket = SDLNet_TCP_Open(&ip);
306
307   if (*socket == NULL)
308   {
309     SetHttpError("cannot connect to host '%s': %s", request->hostname,
310                  SDLNet_GetError());
311
312     return FALSE;
313   }
314
315   if (SDLNet_TCP_AddSocket(*socket_set, *socket) == -1)
316   {
317     SetHttpError("cannot add socket to socket set");
318
319     return FALSE;
320   }
321
322   Debug("network:http", "successfully connected to server");
323
324   snprintf(request->head, MAX_HTTP_HEAD_SIZE,
325            "%s %s HTTP/1.1\r\n"
326            "Host: %s\r\n"
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",
331            request->method,
332            request->uri,
333            request->hostname,
334            (int)strlen(request->body));
335
336   snprintf(send_buffer, max_http_buffer_size,
337            "%s\r\n%s", request->head, request->body);
338
339   Debug("network:http", "client request:\n--- snip ---\n%s\n--- snip ---",
340         send_buffer);
341
342   int send_bytes = SDLNet_TCP_Send(*socket, send_buffer, strlen(send_buffer));
343
344   if (send_bytes != strlen(send_buffer))
345   {
346     SetHttpError("sending request to server failed");
347
348     return FALSE;
349   }
350
351   int recv_bytes = GetHttpResponse(*socket, recv_buffer, max_http_buffer_size);
352
353   if (recv_bytes <= 0)
354   {
355     // HTTP error already set in GetHttpResponse()
356
357     return FALSE;
358   }
359
360   recv_buffer[recv_bytes] = '\0';
361
362   Debug("network:http", "server response:\n--- snip ---\n%s\n--- snip ---",
363         recv_buffer);
364
365   if (!SetHTTPResponseCode(response, recv_buffer))
366   {
367     SetHttpError("malformed HTTP response");
368
369     return FALSE;
370   }
371
372   if (!SetHTTPResponseHead(response, recv_buffer))
373   {
374     SetHttpError("invalid HTTP response header");
375
376     return FALSE;
377   }
378
379   if (!SetHTTPResponseBody(response, recv_buffer, recv_bytes))
380   {
381     SetHttpError("invalid HTTP response body");
382
383     return FALSE;
384   }
385
386   Debug("network:http", "server response: %d %s",
387         response->status_code,
388         response->status_text);
389
390   return TRUE;
391 }
392
393 boolean DoHttpRequest(struct HttpRequest *request,
394                       struct HttpResponse *response)
395 {
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;
401
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)
407   {
408     if (socket != NULL)
409     {
410       SDLNet_TCP_DelSocket(socket_set, socket);
411       SDLNet_TCP_Close(socket);
412     }
413
414     SDLNet_FreeSocketSet(socket_set);
415   }
416
417   checked_free(send_buffer);
418   checked_free(recv_buffer);
419
420   runtime.use_api_server = success;
421
422   return success;
423 }