2 * @file sipe-http-transport.c
6 * Copyright (C) 2013-2018 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
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
;
63 time_t timeout
; /* in seconds from epoch */
68 GHashTable
*connections
;
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
,
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
,
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
);
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
);
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: dropping connection '%s'(%p): %s",
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
);
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
);
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
,
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;
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
);
148 /* restart timer for next connection */
149 if (conn
->timeout
> current_time
) {
150 start_timer(sipe_private
, current_time
);
154 /* next connection timed-out too, loop around */
158 static void start_timer(struct sipe_core_private
*sipe_private
,
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
,
168 http
->next_timeout
- current_time
,
169 sipe_http_transport_timeout
,
173 static void sipe_http_transport_update_timeout_queue(struct sipe_http_connection
*conn
,
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 */
186 g_queue_remove(timeouts
, conn
);
188 conn
->timeout
= current_time
+ SIPE_HTTP_DEFAULT_TIMEOUT
;
189 g_queue_sort(timeouts
,
194 /* update timer if necessary */
196 sipe_schedule_cancel(sipe_private
, SIPE_HTTP_TIMEOUT_ACTION
);
197 if (g_queue_is_empty(timeouts
)) {
198 http
->next_timeout
= 0;
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 */
211 return(http
->shutting_down
);
214 void sipe_http_free(struct sipe_core_private
*sipe_private
)
216 struct sipe_http
*http
= sipe_private
->http
;
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
);
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
)
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
,
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
,
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') {
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
) {
283 gboolean drop
= FALSE
;
288 msg
= sipmsg_parse_header(connection
->buffer
);
290 /* restore header for next try */
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
;
302 while (strlen(start
) > 0) {
304 guint length
= g_ascii_strtoll(start
, &tmp
, 16);
312 if ((length
== 0) && (start
== tmp
))
314 msg
->bodylen
+= length
;
316 /* Chunk header not finished yet */
317 tmp
= strstr(tmp
, "\r\n");
321 /* Chunk not finished yet */
323 remainder
= connection
->buffer_used
- (tmp
- connection
->buffer
);
324 if (remainder
< length
+ 2)
328 start
= tmp
+ length
+ 2;
332 gchar
*dummy
= g_malloc(msg
->bodylen
+ 1);
334 GSList
*entry
= chunks
;
338 memcpy(p
, chunk
->start
, chunk
->length
);
345 sipe_utils_message_debug(connection
,
352 sipe_utils_shrink_buffer(connection
,
359 /* Append completed chunk */
360 chunk
= g_new0(struct _chunk
, 1);
361 chunk
->length
= length
;
363 chunks
= g_slist_append(chunks
, chunk
);
367 sipe_utils_slist_free_full(chunks
, g_free
);
370 /* restore header for next try */
377 guint remainder
= connection
->buffer_used
- (current
+ 2 - connection
->buffer
);
379 if (remainder
>= (guint
) msg
->bodylen
) {
380 char *dummy
= g_malloc(msg
->bodylen
+ 1);
382 memcpy(dummy
, current
, msg
->bodylen
);
383 dummy
[msg
->bodylen
] = '\0';
385 current
+= msg
->bodylen
;
386 sipe_utils_message_debug(connection
,
391 sipe_utils_shrink_buffer(connection
, current
);
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 */
403 if (msg
->response
== SIPMSG_RESPONSE_FATAL_ERROR
) {
404 /* fatal header parse error */
405 msg
->response
= SIPE_HTTP_STATUS_SERVER_ERROR
;
407 } else if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
408 SIPE_DEBUG_INFO("sipe_http_transport_input: server requested close '%s'",
413 sipe_http_request_response(SIPE_HTTP_CONNECTION_PUBLIC
, msg
);
414 next
= sipe_http_request_pending(SIPE_HTTP_CONNECTION_PUBLIC
);
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 */
424 sipe_http_transport_new(conn
->public.sipe_private
,
430 /* trigger sending of next pending request */
431 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC
);
438 static void sipe_http_transport_error(struct sipe_transport_connection
*connection
,
441 struct sipe_http_connection
*conn
= SIPE_HTTP_CONNECTION
;
442 sipe_http_transport_drop(conn
->public.sipe_private
->http
,
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
,
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
);
466 conn
= g_hash_table_lookup(http
->connections
, host_port
);
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
);
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
,
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
,
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
,
515 return(SIPE_HTTP_CONNECTION_PUBLIC
);
518 void sipe_http_transport_send(struct sipe_http_connection_public
*conn_public
,
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
);