added asking for and uploading tapes to score server (only once)
[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   strcpy(request->body, body_utf8);
41   checked_free(body_utf8);
42 }
43
44 void ConvertHttpResponseBodyToClientEncoding(struct HttpResponse *response)
45 {
46   char *body_latin1 = getLatin1FromUTF8(response->body);
47
48   strcpy(response->body, body_latin1);
49   checked_free(body_latin1);
50
51   response->body_size = strlen(response->body);
52 }
53
54 static void SetHttpResponseToDefaults(struct HttpResponse *response)
55 {
56   response->head[0] = '\0';
57   response->body[0] = '\0';
58   response->body_size = 0;
59
60   response->status_code = 0;
61   response->status_text[0] = '\0';
62 }
63
64 static boolean SetHTTPResponseCode(struct HttpResponse *response, char *buffer)
65 {
66   char *prefix = "HTTP/1.1 ";
67   char *prefix_start = strstr(buffer, prefix);
68
69   if (prefix_start == NULL)
70     return FALSE;
71
72   char *status_code_start = prefix_start + strlen(prefix);
73   char *status_code_end = strstr(status_code_start, " ");
74
75   if (status_code_end == NULL)
76     return FALSE;
77
78   int status_code_size = status_code_end - status_code_start;
79
80   if (status_code_size != 3)    // status code must have three digits
81     return FALSE;
82
83   char status_code[status_code_size + 1];
84
85   strncpy(status_code, status_code_start, status_code_size);
86   status_code[status_code_size] = '\0';
87
88   response->status_code = atoi(status_code);
89
90   char *status_text_start = status_code_end + 1;
91   char *status_text_end = strstr(status_text_start, "\r\n");
92
93   if (status_text_end == NULL)
94     return FALSE;
95
96   int status_text_size = status_text_end - status_text_start;
97
98   if (status_text_size > MAX_HTTP_ERROR_SIZE)
99     return FALSE;
100
101   strncpy(response->status_text, status_text_start, status_text_size);
102   response->status_text[status_text_size] = '\0';
103
104   return TRUE;
105 }
106
107 static boolean SetHTTPResponseHead(struct HttpResponse *response, char *buffer)
108 {
109   char *separator = "\r\n\r\n";
110   char *separator_start = strstr(buffer, separator);
111
112   if (separator_start == NULL)
113     return FALSE;
114
115   int head_size = separator_start - buffer;
116
117   if (head_size > MAX_HTTP_HEAD_SIZE)
118     return FALSE;
119
120   strncpy(response->head, buffer, head_size);
121   response->head[head_size] = '\0';
122
123   return TRUE;
124 }
125
126 static boolean SetHTTPResponseBody(struct HttpResponse *response, char *buffer,
127                                    int buffer_size)
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 separator_size = strlen(separator);
136   int full_head_size = separator_start + separator_size - buffer;
137   int body_size = buffer_size - full_head_size;
138
139   if (body_size > MAX_HTTP_BODY_SIZE)
140     return FALSE;
141
142   memcpy(response->body, buffer + full_head_size, body_size);
143   response->body[body_size] = '\0';
144   response->body_size = body_size;
145
146   return TRUE;
147 }
148
149 static boolean DoHttpRequestExt(struct HttpRequest *request,
150                                 struct HttpResponse *response,
151                                 char *send_buffer,
152                                 char *recv_buffer,
153                                 int max_http_buffer_size)
154 {
155   SDLNet_SocketSet socket_set;
156   TCPsocket socket;
157   IPaddress ip;
158   int server_host;
159
160   SetHttpResponseToDefaults(response);
161
162   socket_set = SDLNet_AllocSocketSet(1);
163
164   if (socket_set == NULL)
165   {
166     SetHttpError("cannot allocate socket set");
167
168     return FALSE;
169   }
170
171   SDLNet_ResolveHost(&ip, request->hostname, request->port);
172
173   if (ip.host == INADDR_NONE)
174   {
175     SetHttpError("cannot resolve hostname '%s'", request->hostname);
176
177     return FALSE;
178   }
179
180   server_host = SDLNet_Read32(&ip.host);
181
182   Debug("network:http", "trying to connect to server at %d.%d.%d.%d ...",
183         (server_host >> 24) & 0xff,
184         (server_host >> 16) & 0xff,
185         (server_host >>  8) & 0xff,
186         (server_host >>  0) & 0xff);
187
188   socket = SDLNet_TCP_Open(&ip);
189
190   if (socket == NULL)
191   {
192     SetHttpError("cannot connect to host '%s': %s", request->hostname,
193                  SDLNet_GetError());
194
195     return FALSE;
196   }
197
198   if (SDLNet_TCP_AddSocket(socket_set, socket) == -1)
199   {
200     SetHttpError("cannot add socket to socket set");
201
202     return FALSE;
203   }
204
205   Debug("network:http", "successfully connected to server");
206
207   snprintf(request->head, MAX_HTTP_HEAD_SIZE,
208            "%s %s HTTP/1.1\r\n"
209            "Host: %s\r\n"
210            "X-Requested-With: XMLHttpRequest\r\n"
211            "Content-Type: application/json\r\n"
212            "Connection: close\r\n"
213            "Content-Length: %d\r\n",
214            request->method,
215            request->uri,
216            request->hostname,
217            (int)strlen(request->body));
218
219   snprintf(send_buffer, max_http_buffer_size,
220            "%s\r\n%s", request->head, request->body);
221
222   Debug("network:http", "client request:\n--- snip ---\n%s\n--- snip ---",
223         send_buffer);
224
225   int send_bytes = SDLNet_TCP_Send(socket, send_buffer, strlen(send_buffer));
226
227   if (send_bytes != strlen(send_buffer))
228   {
229     SetHttpError("sending request to server failed");
230
231     return FALSE;
232   }
233
234   int recv_bytes = SDLNet_TCP_Recv(socket, recv_buffer, max_http_buffer_size);
235
236   if (recv_bytes <= 0)
237   {
238     SetHttpError("receiving response from server failed");
239
240     return FALSE;
241   }
242
243   recv_buffer[recv_bytes] = '\0';
244
245   Debug("network:http", "server response:\n--- snip ---\n%s\n--- snip ---",
246         recv_buffer);
247
248   if (!SetHTTPResponseCode(response, recv_buffer))
249   {
250     SetHttpError("malformed HTTP response");
251
252     return FALSE;
253   }
254
255   if (!SetHTTPResponseHead(response, recv_buffer))
256   {
257     SetHttpError("invalid HTTP response header");
258
259     return FALSE;
260   }
261
262   if (!SetHTTPResponseBody(response, recv_buffer, recv_bytes))
263   {
264     SetHttpError("invalid HTTP response body");
265
266     return FALSE;
267   }
268
269   SDLNet_TCP_DelSocket(socket_set, socket);
270   SDLNet_TCP_Close(socket);
271
272   Debug("network:http", "server response: %d %s",
273         response->status_code,
274         response->status_text);
275
276   return TRUE;
277 }
278
279 boolean DoHttpRequest(struct HttpRequest *request,
280                       struct HttpResponse *response)
281 {
282   int max_http_buffer_size = MAX_HTTP_HEAD_SIZE + MAX_HTTP_BODY_SIZE;
283   char *send_buffer = checked_malloc(max_http_buffer_size + 1);
284   char *recv_buffer = checked_malloc(max_http_buffer_size + 1);
285
286   boolean success = DoHttpRequestExt(request, response,
287                                      send_buffer, recv_buffer,
288                                      max_http_buffer_size);
289
290   checked_free(send_buffer);
291   checked_free(recv_buffer);
292
293   runtime.api_server = success;
294
295   return success;
296 }