http: update timeout queue when destroying connection
[siplcs.git] / src / core / sipe-http-transport.c
blob04792a1761cac02aa2635e5c18400f1b1f4f7874
1 /**
2 * @file sipe-http-transport.c
4 * pidgin-sipe
6 * Copyright (C) 2013 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 */
66 struct sipe_http {
67 GHashTable *connections;
68 GQueue *timeouts;
69 time_t next_timeout; /* in seconds from epoch, 0 if timer isn't running */
70 gboolean shutting_down;
73 static gint timeout_compare(gconstpointer a,
74 gconstpointer b,
75 SIPE_UNUSED_PARAMETER gpointer user_data)
77 return(((struct sipe_http_connection *) a)->timeout -
78 ((struct sipe_http_connection *) b)->timeout);
81 static void sipe_http_transport_update_timeout_queue(struct sipe_http_connection *conn,
82 gboolean remove);
83 static void sipe_http_transport_free(gpointer data)
85 struct sipe_http_connection *conn = data;
87 SIPE_DEBUG_INFO("sipe_http_transport_free: destroying connection '%s'",
88 conn->host_port);
90 if (conn->connection)
91 sipe_backend_transport_disconnect(conn->connection);
92 conn->connection = NULL;
94 sipe_http_transport_update_timeout_queue(conn, TRUE);
96 sipe_http_request_shutdown(SIPE_HTTP_CONNECTION_PUBLIC);
98 g_free(conn->public.host);
100 g_free(conn->host_port);
101 g_free(conn);
104 static void sipe_http_transport_drop(struct sipe_http *http,
105 struct sipe_http_connection *conn,
106 const gchar *message)
108 SIPE_DEBUG_INFO("sipe_http_transport_drop: dropping connection '%s': %s",
109 conn->host_port,
110 message ? message : "REASON UNKNOWN");
112 /* this triggers sipe_http_transport_new */
113 g_hash_table_remove(http->connections,
114 conn->host_port);
117 static void start_timer(struct sipe_core_private *sipe_private,
118 time_t current_time);
119 static void sipe_http_transport_timeout(struct sipe_core_private *sipe_private,
120 gpointer data)
122 struct sipe_http *http = sipe_private->http;
123 struct sipe_http_connection *conn = data;
124 time_t current_time = time(NULL);
126 /* timer has expired */
127 http->next_timeout = 0;
129 while (1) {
130 sipe_http_transport_drop(http, conn, "timeout");
131 /* conn is no longer valid */
133 /* is there another active connection? */
134 conn = g_queue_peek_head(http->timeouts);
135 if (!conn)
136 break;
138 /* restart timer for next connection */
139 if (conn->timeout > current_time) {
140 start_timer(sipe_private, current_time);
141 break;
144 /* next connection timed-out too, loop around */
148 static void start_timer(struct sipe_core_private *sipe_private,
149 time_t current_time)
151 struct sipe_http *http = sipe_private->http;
152 struct sipe_http_connection *conn = g_queue_peek_head(http->timeouts);
154 http->next_timeout = conn->timeout;
155 sipe_schedule_seconds(sipe_private,
156 SIPE_HTTP_TIMEOUT_ACTION,
157 conn,
158 http->next_timeout - current_time,
159 sipe_http_transport_timeout,
160 NULL);
163 static void sipe_http_transport_update_timeout_queue(struct sipe_http_connection *conn,
164 gboolean remove)
166 struct sipe_core_private *sipe_private = conn->public.sipe_private;
167 struct sipe_http *http = sipe_private->http;
168 GQueue *timeouts = http->timeouts;
169 time_t current_time = time(NULL);
171 /* is this connection at head of queue? */
172 gboolean update = (conn == g_queue_peek_head(timeouts));
174 /* update timeout queue */
175 if (remove) {
176 g_queue_remove(timeouts, conn);
177 } else {
178 conn->timeout = current_time + SIPE_HTTP_DEFAULT_TIMEOUT;
179 g_queue_sort(timeouts,
180 timeout_compare,
181 NULL);
184 /* update timer if necessary */
185 if (update) {
186 sipe_schedule_cancel(sipe_private, SIPE_HTTP_TIMEOUT_ACTION);
187 if (g_queue_is_empty(timeouts)) {
188 http->next_timeout = 0;
189 } else {
190 start_timer(sipe_private, current_time);
195 gboolean sipe_http_shutting_down(struct sipe_core_private *sipe_private)
197 struct sipe_http *http = sipe_private->http;
198 /* We need to return FALSE in case HTTP stack isn't initialized yet */
199 if (!http)
200 return(FALSE);
201 return(http->shutting_down);
204 void sipe_http_free(struct sipe_core_private *sipe_private)
206 struct sipe_http *http = sipe_private->http;
207 if (!http)
208 return;
210 /* HTTP stack is shutting down: reject all new requests */
211 http->shutting_down = TRUE;
213 sipe_schedule_cancel(sipe_private, SIPE_HTTP_TIMEOUT_ACTION);
214 g_hash_table_destroy(http->connections);
215 g_queue_free(http->timeouts);
216 g_free(http);
217 sipe_private->http = NULL;
220 static void sipe_http_init(struct sipe_core_private *sipe_private)
222 struct sipe_http *http;
223 if (sipe_private->http)
224 return;
226 sipe_private->http = http = g_new0(struct sipe_http, 1);
227 http->connections = g_hash_table_new_full(g_str_hash, g_str_equal,
228 NULL,
229 sipe_http_transport_free);
230 http->timeouts = g_queue_new();
233 static void sipe_http_transport_connected(struct sipe_transport_connection *connection)
235 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
236 struct sipe_core_private *sipe_private = conn->public.sipe_private;
237 struct sipe_http *http = sipe_private->http;
238 time_t current_time = time(NULL);
240 SIPE_DEBUG_INFO("sipe_http_transport_connected: %s", conn->host_port);
241 conn->public.connected = TRUE;
243 /* add active connection to timeout queue */
244 conn->timeout = current_time + SIPE_HTTP_DEFAULT_TIMEOUT;
245 g_queue_insert_sorted(http->timeouts,
246 conn,
247 timeout_compare,
248 NULL);
250 /* start timeout timer if necessary */
251 if (http->next_timeout == 0)
252 start_timer(sipe_private, current_time);
254 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC);
257 static void sipe_http_transport_input(struct sipe_transport_connection *connection)
259 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
260 char *current = connection->buffer;
262 /* according to the RFC remove CRLF at the beginning */
263 while (*current == '\r' || *current == '\n') {
264 current++;
266 if (current != connection->buffer)
267 sipe_utils_shrink_buffer(connection, current);
269 if ((current = strstr(connection->buffer, "\r\n\r\n")) != NULL) {
270 struct sipmsg *msg;
271 gboolean next;
273 current += 2;
274 current[0] = '\0';
275 msg = sipmsg_parse_header(connection->buffer);
276 if (!msg) {
277 /* restore header for next try */
278 current[0] = '\r';
279 return;
282 /* HTTP/1.1 Transfer-Encoding: chunked */
283 if (msg->bodylen == SIPMSG_BODYLEN_CHUNKED) {
284 gchar *start = current + 2;
285 GSList *chunks = NULL;
286 gboolean incomplete = TRUE;
288 msg->bodylen = 0;
289 while (strlen(start) > 0) {
290 gchar *tmp;
291 guint length = g_ascii_strtoll(start, &tmp, 16);
292 guint remainder;
293 struct _chunk {
294 guint length;
295 const gchar *start;
296 } *chunk;
298 /* Illegal number */
299 if ((length == 0) && (start == tmp))
300 break;
301 msg->bodylen += length;
303 /* Chunk header not finished yet */
304 tmp = strstr(tmp, "\r\n");
305 if (tmp == NULL)
306 break;
308 /* Chunk not finished yet */
309 tmp += 2;
310 remainder = connection->buffer_used - (tmp - connection->buffer);
311 if (remainder < length + 2)
312 break;
314 /* Next chunk */
315 start = tmp + length + 2;
317 /* Body completed */
318 if (length == 0) {
319 gchar *dummy = g_malloc(msg->bodylen + 1);
320 gchar *p = dummy;
321 GSList *entry = chunks;
323 while (entry) {
324 chunk = entry->data;
325 memcpy(p, chunk->start, chunk->length);
326 p += chunk->length;
327 entry = entry->next;
329 p[0] = '\0';
331 msg->body = dummy;
332 sipe_utils_message_debug("HTTP",
333 connection->buffer,
334 msg->body,
335 FALSE);
337 current = start;
338 sipe_utils_shrink_buffer(connection,
339 current);
341 incomplete = FALSE;
342 break;
345 /* Append completed chunk */
346 chunk = g_new0(struct _chunk, 1);
347 chunk->length = length;
348 chunk->start = tmp;
349 chunks = g_slist_append(chunks, chunk);
352 if (chunks)
353 sipe_utils_slist_free_full(chunks, g_free);
355 if (incomplete) {
356 /* restore header for next try */
357 sipmsg_free(msg);
358 current[0] = '\r';
359 return;
362 } else {
363 guint remainder = connection->buffer_used - (current + 2 - connection->buffer);
365 if (remainder >= (guint) msg->bodylen) {
366 char *dummy = g_malloc(msg->bodylen + 1);
367 current += 2;
368 memcpy(dummy, current, msg->bodylen);
369 dummy[msg->bodylen] = '\0';
370 msg->body = dummy;
371 current += msg->bodylen;
372 sipe_utils_message_debug("HTTP",
373 connection->buffer,
374 msg->body,
375 FALSE);
376 sipe_utils_shrink_buffer(connection, current);
377 } else {
378 SIPE_DEBUG_INFO("sipe_http_transport_input: body too short (%d < %d, strlen %" G_GSIZE_FORMAT ") - ignoring message",
379 remainder, msg->bodylen, strlen(connection->buffer));
381 /* restore header for next try */
382 sipmsg_free(msg);
383 current[0] = '\r';
384 return;
388 sipe_http_request_response(SIPE_HTTP_CONNECTION_PUBLIC, msg);
389 next = sipe_http_request_pending(SIPE_HTTP_CONNECTION_PUBLIC);
391 if (sipe_strcase_equal(sipmsg_find_header(msg, "Connection"), "close")) {
392 /* drop backend connection */
393 SIPE_DEBUG_INFO("sipe_http_transport_input: server requested close '%s'",
394 conn->host_port);
395 sipe_backend_transport_disconnect(conn->connection);
396 conn->connection = NULL;
397 conn->public.connected = FALSE;
399 /* if we have pending requests we need to trigger re-connect */
400 if (next)
401 sipe_http_transport_new(conn->public.sipe_private,
402 conn->public.host,
403 conn->public.port);
405 } else if (next) {
406 /* trigger sending of next pending request */
407 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC);
410 sipmsg_free(msg);
414 static void sipe_http_transport_error(struct sipe_transport_connection *connection,
415 const gchar *msg)
417 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
418 sipe_http_transport_drop(conn->public.sipe_private->http,
419 conn,
420 msg);
421 /* conn is no longer valid */
424 struct sipe_http_connection_public *sipe_http_transport_new(struct sipe_core_private *sipe_private,
425 const gchar *host_in,
426 const guint32 port)
428 struct sipe_http *http;
429 struct sipe_http_connection *conn = NULL;
430 /* host name matching should be case insensitive */
431 gchar *host = g_ascii_strdown(host_in, -1);
432 gchar *host_port = g_strdup_printf("%s:%" G_GUINT32_FORMAT, host, port);
434 sipe_http_init(sipe_private);
436 http = sipe_private->http;
437 if (http->shutting_down) {
438 SIPE_DEBUG_ERROR("sipe_http_transport_new: new connection requested during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
439 "Host/Port: %s", host_port);
440 } else {
441 conn = g_hash_table_lookup(http->connections, host_port);
443 if (conn) {
444 /* re-establishing connection */
445 if (!conn->connection) {
446 SIPE_DEBUG_INFO("sipe_http_transport_new: re-establishing %s", host_port);
448 /* will be re-inserted after connect */
449 sipe_http_transport_update_timeout_queue(conn, TRUE);
452 } else {
453 /* new connection */
454 SIPE_DEBUG_INFO("sipe_http_transport_new: new %s", host_port);
456 conn = g_new0(struct sipe_http_connection, 1);
458 conn->public.sipe_private = sipe_private;
459 conn->public.host = g_strdup(host);
460 conn->public.port = port;
462 conn->host_port = host_port;
464 g_hash_table_insert(http->connections,
465 host_port,
466 conn);
467 host_port = NULL; /* conn_private takes ownership of the key */
470 if (!conn->connection) {
471 sipe_connect_setup setup = {
472 SIPE_TRANSPORT_TLS, /* TBD: we only support TLS for now */
473 host,
474 port,
475 conn,
476 sipe_http_transport_connected,
477 sipe_http_transport_input,
478 sipe_http_transport_error
481 conn->public.connected = FALSE;
482 conn->connection = sipe_backend_transport_connect(SIPE_CORE_PUBLIC,
483 &setup);
487 g_free(host_port);
488 g_free(host);
489 return(SIPE_HTTP_CONNECTION_PUBLIC);
492 void sipe_http_transport_send(struct sipe_http_connection_public *conn_public,
493 const gchar *header,
494 const gchar *body)
496 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION_PRIVATE;
497 GString *message = g_string_new(header);
499 g_string_append_printf(message, "\r\n%s", body ? body : "");
501 sipe_utils_message_debug("HTTP", message->str, NULL, TRUE);
502 sipe_backend_transport_message(conn->connection, message->str);
503 g_string_free(message, TRUE);
505 sipe_http_transport_update_timeout_queue(conn, FALSE);
509 Local Variables:
510 mode: c
511 c-file-style: "bsd"
512 indent-tabs-mode: t
513 tab-width: 8
514 End: