Release 1.25.0 -- Buddy Idle Time, RTF
[siplcs.git] / src / core / sipe-http-transport.c
blob347f4eb37473635aafbdff6b26be3db3f038045b
1 /**
2 * @file sipe-http-transport.c
4 * pidgin-sipe
6 * Copyright (C) 2013-2019 SIPE Project <http://sipe.sourceforge.net/>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * SIPE HTTP transport layer implementation
26 * - connection handling: opening, closing, timeout
27 * - interface to backend: sending & receiving of raw messages
28 * - request queue pulling
31 #include <string.h>
32 #include <time.h>
34 #include <glib.h>
36 #include "sipmsg.h"
37 #include "sipe-backend.h"
38 #include "sipe-common.h"
39 #include "sipe-core.h"
40 #include "sipe-core-private.h"
41 #include "sipe-http.h"
42 #include "sipe-schedule.h"
43 #include "sipe-utils.h"
45 #define _SIPE_HTTP_PRIVATE_IF_REQUEST
46 #include "sipe-http-request.h"
47 #define _SIPE_HTTP_PRIVATE_IF_TRANSPORT
48 #include "sipe-http-transport.h"
50 #define SIPE_HTTP_CONNECTION ((struct sipe_http_connection *) connection->user_data)
51 #define SIPE_HTTP_CONNECTION_PRIVATE ((struct sipe_http_connection *) conn_public)
52 #define SIPE_HTTP_CONNECTION_PUBLIC ((struct sipe_http_connection_public *) conn)
54 #define SIPE_HTTP_TIMEOUT_ACTION "<+http-timeout>"
55 #define SIPE_HTTP_DEFAULT_TIMEOUT 60 /* in seconds */
57 struct sipe_http_connection {
58 struct sipe_http_connection_public public;
60 struct sipe_transport_connection *connection;
62 gchar *host_port;
63 time_t timeout; /* in seconds from epoch */
64 gboolean use_tls;
67 struct sipe_http {
68 GHashTable *connections;
69 GQueue *timeouts;
70 time_t next_timeout; /* in seconds from epoch, 0 if timer isn't running */
71 gboolean shutting_down;
74 static gint timeout_compare(gconstpointer a,
75 gconstpointer b,
76 SIPE_UNUSED_PARAMETER gpointer user_data)
78 return(((struct sipe_http_connection *) a)->timeout -
79 ((struct sipe_http_connection *) b)->timeout);
82 static void sipe_http_transport_update_timeout_queue(struct sipe_http_connection *conn,
83 gboolean remove);
84 static void sipe_http_transport_free(gpointer data)
86 struct sipe_http_connection *conn = data;
88 SIPE_DEBUG_INFO("sipe_http_transport_free: destroying connection '%s'(%p)",
89 conn->host_port, conn->connection);
91 if (conn->connection)
92 sipe_backend_transport_disconnect(conn->connection);
93 conn->connection = NULL;
95 sipe_http_transport_update_timeout_queue(conn, TRUE);
97 sipe_http_request_shutdown(SIPE_HTTP_CONNECTION_PUBLIC,
98 conn->public.sipe_private->http->shutting_down);
100 g_free(conn->public.host);
102 g_free(conn->host_port);
103 g_free(conn);
106 static void sipe_http_transport_drop(struct sipe_http *http,
107 struct sipe_http_connection *conn,
108 const gchar *message)
110 SIPE_LOG_INFO("sipe_http_transport_drop: '%s'(%p): %s",
111 conn->host_port,
112 conn->connection,
113 message ? message : "REASON UNKNOWN");
115 #if GLIB_CHECK_VERSION(2,30,0)
116 /* this triggers sipe_http_transport_free() */
117 g_hash_table_remove(http->connections, conn->host_port);
118 #else
119 /* GLIB < 2.30 calls destroy notifiers *before* removing the entry */
120 /* which can cause a race condition with sipe_http_transport_new() */
121 g_hash_table_steal(http->connections, conn->host_port);
122 sipe_http_transport_free(conn);
123 #endif
124 /* conn is no longer valid */
127 static void start_timer(struct sipe_core_private *sipe_private,
128 time_t current_time);
129 static void sipe_http_transport_timeout(struct sipe_core_private *sipe_private,
130 gpointer data)
132 struct sipe_http *http = sipe_private->http;
133 struct sipe_http_connection *conn = data;
134 time_t current_time = time(NULL);
136 /* timer has expired */
137 http->next_timeout = 0;
139 while (1) {
140 sipe_http_transport_drop(http, conn, "timeout");
141 /* conn is no longer valid */
143 /* is there another active connection? */
144 conn = g_queue_peek_head(http->timeouts);
145 if (!conn)
146 break;
148 /* restart timer for next connection */
149 if (conn->timeout > current_time) {
150 start_timer(sipe_private, current_time);
151 break;
154 /* next connection timed-out too, loop around */
158 static void start_timer(struct sipe_core_private *sipe_private,
159 time_t current_time)
161 struct sipe_http *http = sipe_private->http;
162 struct sipe_http_connection *conn = g_queue_peek_head(http->timeouts);
164 http->next_timeout = conn->timeout;
165 sipe_schedule_seconds(sipe_private,
166 SIPE_HTTP_TIMEOUT_ACTION,
167 conn,
168 http->next_timeout - current_time,
169 sipe_http_transport_timeout,
170 NULL);
173 static void sipe_http_transport_update_timeout_queue(struct sipe_http_connection *conn,
174 gboolean remove)
176 struct sipe_core_private *sipe_private = conn->public.sipe_private;
177 struct sipe_http *http = sipe_private->http;
178 GQueue *timeouts = http->timeouts;
179 time_t current_time = time(NULL);
181 /* is this connection at head of queue? */
182 gboolean update = (conn == g_queue_peek_head(timeouts));
184 /* update timeout queue */
185 if (remove) {
186 g_queue_remove(timeouts, conn);
187 } else {
188 conn->timeout = current_time + SIPE_HTTP_DEFAULT_TIMEOUT;
189 g_queue_sort(timeouts,
190 timeout_compare,
191 NULL);
194 /* update timer if necessary */
195 if (update) {
196 sipe_schedule_cancel(sipe_private, SIPE_HTTP_TIMEOUT_ACTION);
197 if (g_queue_is_empty(timeouts)) {
198 http->next_timeout = 0;
199 } else {
200 start_timer(sipe_private, current_time);
205 gboolean sipe_http_shutting_down(struct sipe_core_private *sipe_private)
207 struct sipe_http *http = sipe_private->http;
208 /* We need to return FALSE in case HTTP stack isn't initialized yet */
209 if (!http)
210 return(FALSE);
211 return(http->shutting_down);
214 void sipe_http_free(struct sipe_core_private *sipe_private)
216 struct sipe_http *http = sipe_private->http;
217 if (!http)
218 return;
220 /* HTTP stack is shutting down: reject all new requests */
221 http->shutting_down = TRUE;
223 sipe_schedule_cancel(sipe_private, SIPE_HTTP_TIMEOUT_ACTION);
224 g_hash_table_destroy(http->connections);
225 g_queue_free(http->timeouts);
226 g_free(http);
227 sipe_private->http = NULL;
230 static void sipe_http_init(struct sipe_core_private *sipe_private)
232 struct sipe_http *http;
233 if (sipe_private->http)
234 return;
236 sipe_private->http = http = g_new0(struct sipe_http, 1);
237 http->connections = g_hash_table_new_full(g_str_hash, g_str_equal,
238 NULL,
239 sipe_http_transport_free);
240 http->timeouts = g_queue_new();
243 static void sipe_http_transport_connected(struct sipe_transport_connection *connection)
245 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
246 struct sipe_core_private *sipe_private = conn->public.sipe_private;
247 struct sipe_http *http = sipe_private->http;
248 time_t current_time = time(NULL);
250 SIPE_LOG_INFO("sipe_http_transport_connected: '%s'(%p)",
251 conn->host_port, connection);
252 conn->public.connected = TRUE;
254 /* add active connection to timeout queue */
255 conn->timeout = current_time + SIPE_HTTP_DEFAULT_TIMEOUT;
256 g_queue_insert_sorted(http->timeouts,
257 conn,
258 timeout_compare,
259 NULL);
261 /* start timeout timer if necessary */
262 if (http->next_timeout == 0)
263 start_timer(sipe_private, current_time);
265 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC);
268 static void sipe_http_transport_input(struct sipe_transport_connection *connection)
270 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
271 char *current = connection->buffer;
273 /* according to the RFC remove CRLF at the beginning */
274 while (*current == '\r' || *current == '\n') {
275 current++;
277 if (current != connection->buffer)
278 sipe_utils_shrink_buffer(connection, current);
280 if (conn->connection &&
281 (current = strstr(connection->buffer, "\r\n\r\n")) != NULL) {
282 struct sipmsg *msg;
283 gboolean drop = FALSE;
284 gboolean next;
286 current += 2;
287 current[0] = '\0';
288 msg = sipmsg_parse_header(connection->buffer);
289 if (!msg) {
290 /* restore header for next try */
291 current[0] = '\r';
292 return;
295 /* HTTP/1.1 Transfer-Encoding: chunked */
296 if (msg->bodylen == SIPMSG_BODYLEN_CHUNKED) {
297 gchar *start = current + 2;
298 GSList *chunks = NULL;
299 gboolean incomplete = TRUE;
301 msg->bodylen = 0;
302 while (strlen(start) > 0) {
303 gchar *tmp;
304 guint length = g_ascii_strtoll(start, &tmp, 16);
305 guint remainder;
306 struct _chunk {
307 guint length;
308 const gchar *start;
309 } *chunk;
311 /* Illegal number */
312 if ((length == 0) && (start == tmp))
313 break;
314 msg->bodylen += length;
316 /* Chunk header not finished yet */
317 tmp = strstr(tmp, "\r\n");
318 if (tmp == NULL)
319 break;
321 /* Chunk not finished yet */
322 tmp += 2;
323 remainder = connection->buffer_used - (tmp - connection->buffer);
324 if (remainder < length + 2)
325 break;
327 /* Next chunk */
328 start = tmp + length + 2;
330 /* Body completed */
331 if (length == 0) {
332 gchar *dummy = g_malloc(msg->bodylen + 1);
333 gchar *p = dummy;
334 GSList *entry = chunks;
336 while (entry) {
337 chunk = entry->data;
338 memcpy(p, chunk->start, chunk->length);
339 p += chunk->length;
340 entry = entry->next;
342 p[0] = '\0';
344 msg->body = dummy;
345 sipe_utils_message_debug(connection,
346 "HTTP",
347 connection->buffer,
348 msg->body,
349 FALSE);
351 current = start;
352 sipe_utils_shrink_buffer(connection,
353 current);
355 incomplete = FALSE;
356 break;
359 /* Append completed chunk */
360 chunk = g_new0(struct _chunk, 1);
361 chunk->length = length;
362 chunk->start = tmp;
363 chunks = g_slist_append(chunks, chunk);
366 if (chunks)
367 sipe_utils_slist_free_full(chunks, g_free);
369 if (incomplete) {
370 /* restore header for next try */
371 sipmsg_free(msg);
372 current[0] = '\r';
373 return;
376 } else {
377 guint remainder = connection->buffer_used - (current + 2 - connection->buffer);
379 if (remainder >= (guint) msg->bodylen) {
380 char *dummy = g_malloc(msg->bodylen + 1);
381 current += 2;
382 memcpy(dummy, current, msg->bodylen);
383 dummy[msg->bodylen] = '\0';
384 msg->body = dummy;
385 current += msg->bodylen;
386 sipe_utils_message_debug(connection,
387 "HTTP",
388 connection->buffer,
389 msg->body,
390 FALSE);
391 sipe_utils_shrink_buffer(connection, current);
392 } else {
393 SIPE_DEBUG_INFO("sipe_http_transport_input: body too short (%d < %d, strlen %" G_GSIZE_FORMAT ") - ignoring message",
394 remainder, msg->bodylen, strlen(connection->buffer));
396 /* restore header for next try */
397 sipmsg_free(msg);
398 current[0] = '\r';
399 return;
403 if (msg->response == SIPMSG_RESPONSE_FATAL_ERROR) {
404 /* fatal header parse error */
405 msg->response = SIPE_HTTP_STATUS_SERVER_ERROR;
406 drop = TRUE;
407 } else if (sipe_strcase_equal(sipmsg_find_header(msg, "Connection"), "close")) {
408 SIPE_DEBUG_INFO("sipe_http_transport_input: server requested close '%s'",
409 conn->host_port);
410 drop = TRUE;
413 sipe_http_request_response(SIPE_HTTP_CONNECTION_PUBLIC, msg);
414 next = sipe_http_request_pending(SIPE_HTTP_CONNECTION_PUBLIC);
416 if (drop) {
417 /* drop backend connection */
418 sipe_backend_transport_disconnect(conn->connection);
419 conn->connection = NULL;
420 conn->public.connected = FALSE;
422 /* if we have pending requests we need to trigger re-connect */
423 if (next)
424 sipe_http_transport_new(conn->public.sipe_private,
425 conn->public.host,
426 conn->public.port,
427 conn->use_tls);
429 } else if (next) {
430 /* trigger sending of next pending request */
431 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC);
434 sipmsg_free(msg);
438 static void sipe_http_transport_error(struct sipe_transport_connection *connection,
439 const gchar *msg)
441 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
442 sipe_http_transport_drop(conn->public.sipe_private->http,
443 conn,
444 msg);
445 /* conn is no longer valid */
448 struct sipe_http_connection_public *sipe_http_transport_new(struct sipe_core_private *sipe_private,
449 const gchar *host_in,
450 const guint32 port,
451 gboolean use_tls)
453 struct sipe_http *http;
454 struct sipe_http_connection *conn = NULL;
455 /* host name matching should be case insensitive */
456 gchar *host = g_ascii_strdown(host_in, -1);
457 gchar *host_port = g_strdup_printf("%s:%" G_GUINT32_FORMAT, host, port);
459 sipe_http_init(sipe_private);
461 http = sipe_private->http;
462 if (http->shutting_down) {
463 SIPE_DEBUG_ERROR("sipe_http_transport_new: new connection requested during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
464 "Host/Port: %s", host_port);
465 } else {
466 conn = g_hash_table_lookup(http->connections, host_port);
468 if (conn) {
469 /* re-establishing connection */
470 if (!conn->connection) {
471 SIPE_DEBUG_INFO("sipe_http_transport_new: re-establishing %s", host_port);
473 /* will be re-inserted after connect */
474 sipe_http_transport_update_timeout_queue(conn, TRUE);
477 } else {
478 /* new connection */
479 SIPE_DEBUG_INFO("sipe_http_transport_new: new %s", host_port);
481 conn = g_new0(struct sipe_http_connection, 1);
483 conn->public.sipe_private = sipe_private;
484 conn->public.host = g_strdup(host);
485 conn->public.port = port;
487 conn->host_port = host_port;
488 conn->use_tls = use_tls;
490 g_hash_table_insert(http->connections,
491 host_port,
492 conn);
493 host_port = NULL; /* conn_private takes ownership of the key */
496 if (!conn->connection) {
497 sipe_connect_setup setup = {
498 use_tls ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP,
499 host,
500 port,
501 conn,
502 sipe_http_transport_connected,
503 sipe_http_transport_input,
504 sipe_http_transport_error
507 conn->public.connected = FALSE;
508 conn->connection = sipe_backend_transport_connect(SIPE_CORE_PUBLIC,
509 &setup);
513 g_free(host_port);
514 g_free(host);
515 return(SIPE_HTTP_CONNECTION_PUBLIC);
518 void sipe_http_transport_send(struct sipe_http_connection_public *conn_public,
519 const gchar *header,
520 const gchar *body)
522 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION_PRIVATE;
523 GString *message = g_string_new(header);
525 g_string_append_printf(message, "\r\n%s", body ? body : "");
527 sipe_utils_message_debug(conn->connection, "HTTP", message->str, NULL, TRUE);
528 sipe_backend_transport_message(conn->connection, message->str);
529 g_string_free(message, TRUE);
531 sipe_http_transport_update_timeout_queue(conn, FALSE);
535 Local Variables:
536 mode: c
537 c-file-style: "bsd"
538 indent-tabs-mode: t
539 tab-width: 8
540 End: