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