2 * @file sipe-http-transport.c
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
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_public *) connection->user_data)
52 #define SIPE_HTTP_TIMEOUT_ACTION "<+http-timeout>"
53 #define SIPE_HTTP_DEFAULT_TIMEOUT 60 /* in seconds */
55 struct sipe_http_connection_private
{
56 struct sipe_transport_connection
*connection
;
58 time_t timeout
; /* in seconds from epoch */
62 GHashTable
*connections
;
64 time_t next_timeout
; /* in seconds from epoch, 0 if timer isn't running */
67 static gint
timeout_compare(gconstpointer a
,
69 SIPE_UNUSED_PARAMETER gpointer user_data
)
71 return(((struct sipe_http_connection_private
*) a
)->timeout
-
72 ((struct sipe_http_connection_private
*) b
)->timeout
);
75 static void sipe_http_transport_free(gpointer data
)
77 struct sipe_http_connection_public
*conn_public
= data
;
78 struct sipe_http_connection_private
*conn_private
= conn_public
->conn_private
;
79 struct sipe_http
*http
= conn_public
->sipe_private
->http
;
81 SIPE_DEBUG_INFO("sipe_http_transport_free: destroying connection '%s'",
82 conn_private
->host_port
);
84 if (conn_private
->connection
)
85 sipe_backend_transport_disconnect(conn_private
->connection
);
86 g_queue_remove(http
->timeouts
, conn_private
);
87 g_free(conn_private
->host_port
);
89 conn_public
->conn_private
= NULL
;
91 sipe_http_request_shutdown(conn_public
);
94 static void sipe_http_transport_drop(struct sipe_http
*http
,
95 struct sipe_http_connection_private
*conn_private
,
98 SIPE_DEBUG_INFO("sipe_http_transport_drop: dropping connection '%s': %s",
99 conn_private
->host_port
,
100 message
? message
: "REASON UNKNOWN");
102 /* this triggers sipe_http_transport_new */
103 g_hash_table_remove(http
->connections
,
104 conn_private
->host_port
);
107 static void start_timer(struct sipe_core_private
*sipe_private
,
108 time_t current_time
);
109 static void sipe_http_transport_timeout(struct sipe_core_private
*sipe_private
,
112 struct sipe_http
*http
= sipe_private
->http
;
113 struct sipe_http_connection_private
*conn_private
= data
;
114 time_t current_time
= time(NULL
);
116 /* timer has expired */
117 http
->next_timeout
= 0;
120 sipe_http_transport_drop(http
, conn_private
, "timeout");
121 /* conn_private is no longer valid */
123 /* is there another active connection? */
124 conn_private
= g_queue_peek_head(http
->timeouts
);
128 /* restart timer for next connection */
129 if (conn_private
->timeout
> current_time
) {
130 start_timer(sipe_private
, current_time
);
134 /* next connection timed-out too, loop around */
138 static void start_timer(struct sipe_core_private
*sipe_private
,
141 struct sipe_http
*http
= sipe_private
->http
;
142 struct sipe_http_connection_private
*conn_private
= g_queue_peek_head(http
->timeouts
);
144 http
->next_timeout
= conn_private
->timeout
;
145 sipe_schedule_seconds(sipe_private
,
146 SIPE_HTTP_TIMEOUT_ACTION
,
148 http
->next_timeout
- current_time
,
149 sipe_http_transport_timeout
,
153 void sipe_http_free(struct sipe_core_private
*sipe_private
)
155 struct sipe_http
*http
= sipe_private
->http
;
159 sipe_schedule_cancel(sipe_private
, SIPE_HTTP_TIMEOUT_ACTION
);
160 g_hash_table_destroy(http
->connections
);
161 g_queue_free(http
->timeouts
);
163 sipe_private
->http
= NULL
;
166 static void sipe_http_init(struct sipe_core_private
*sipe_private
)
168 struct sipe_http
*http
;
169 if (sipe_private
->http
)
172 sipe_private
->http
= http
= g_new0(struct sipe_http
, 1);
173 http
->connections
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
175 sipe_http_transport_free
);
176 http
->timeouts
= g_queue_new();
179 static void sipe_http_transport_connected(struct sipe_transport_connection
*connection
)
181 struct sipe_http_connection_public
*conn_public
= SIPE_HTTP_CONNECTION
;
183 SIPE_DEBUG_INFO("sipe_http_transport_connected: %s",
184 conn_public
->conn_private
->host_port
);
185 conn_public
->connected
= TRUE
;
186 sipe_http_request_next(conn_public
);
189 static void sipe_http_transport_input(struct sipe_transport_connection
*connection
)
191 struct sipe_http_connection_public
*conn_public
= SIPE_HTTP_CONNECTION
;
192 char *current
= connection
->buffer
;
194 /* according to the RFC remove CRLF at the beginning */
195 while (*current
== '\r' || *current
== '\n') {
198 if (current
!= connection
->buffer
)
199 sipe_utils_shrink_buffer(connection
, current
);
201 if ((current
= strstr(connection
->buffer
, "\r\n\r\n")) != NULL
) {
207 msg
= sipmsg_parse_header(connection
->buffer
);
209 /* restore header for next try */
214 /* HTTP/1.1 Transfer-Encoding: chunked */
215 if (msg
->bodylen
== SIPMSG_BODYLEN_CHUNKED
) {
216 gchar
*start
= current
+ 2;
217 GSList
*chunks
= NULL
;
218 gboolean incomplete
= TRUE
;
221 while (strlen(start
) > 0) {
223 guint length
= g_ascii_strtoll(start
, &tmp
, 16);
231 if ((length
== 0) && (start
== tmp
))
233 msg
->bodylen
+= length
;
235 /* Chunk header not finished yet */
236 tmp
= strstr(tmp
, "\r\n");
240 /* Chunk not finished yet */
242 remainder
= connection
->buffer_used
- (tmp
- connection
->buffer
);
243 if (remainder
< length
+ 2)
247 start
= tmp
+ length
+ 2;
251 gchar
*dummy
= g_malloc(msg
->bodylen
+ 1);
253 GSList
*entry
= chunks
;
257 memcpy(p
, chunk
->start
, chunk
->length
);
264 sipe_utils_message_debug("HTTP",
270 sipe_utils_shrink_buffer(connection
,
277 /* Append completed chunk */
278 chunk
= g_new0(struct _chunk
, 1);
279 chunk
->length
= length
;
281 chunks
= g_slist_append(chunks
, chunk
);
285 GSList
*entry
= chunks
;
290 g_slist_free(chunks
);
294 /* restore header for next try */
301 guint remainder
= connection
->buffer_used
- (current
+ 2 - connection
->buffer
);
303 if (remainder
>= (guint
) msg
->bodylen
) {
304 char *dummy
= g_malloc(msg
->bodylen
+ 1);
306 memcpy(dummy
, current
, msg
->bodylen
);
307 dummy
[msg
->bodylen
] = '\0';
309 current
+= msg
->bodylen
;
310 sipe_utils_message_debug("HTTP",
314 sipe_utils_shrink_buffer(connection
, current
);
316 SIPE_DEBUG_INFO("sipe_http_transport_input: body too short (%d < %d, strlen %" G_GSIZE_FORMAT
") - ignoring message",
317 remainder
, msg
->bodylen
, strlen(connection
->buffer
));
319 /* restore header for next try */
326 sipe_http_request_response(conn_public
, msg
);
327 next
= sipe_http_request_pending(conn_public
);
329 if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
330 /* drop backend connection */
331 struct sipe_http_connection_private
*conn_private
= conn_public
->conn_private
;
332 SIPE_DEBUG_INFO("sipe_http_transport_input: server requested close '%s'",
333 conn_private
->host_port
);
334 sipe_backend_transport_disconnect(conn_private
->connection
);
335 conn_private
->connection
= NULL
;
336 conn_public
->connected
= FALSE
;
338 /* if we have pending requests we need to trigger re-connect */
340 sipe_http_transport_new(conn_public
->sipe_private
,
345 /* trigger sending of next pending request */
346 sipe_http_request_next(conn_public
);
353 static void sipe_http_transport_error(struct sipe_transport_connection
*connection
,
356 struct sipe_http_connection_public
*conn_public
= SIPE_HTTP_CONNECTION
;
357 sipe_http_transport_drop(conn_public
->sipe_private
->http
,
358 conn_public
->conn_private
,
360 /* conn_public is no longer valid */
363 struct sipe_http_connection_public
*sipe_http_transport_new(struct sipe_core_private
*sipe_private
,
364 const gchar
*host_in
,
367 struct sipe_http
*http
;
368 struct sipe_http_connection_public
*conn_public
;
369 struct sipe_http_connection_private
*conn_private
;
370 /* host name matching should be case insensitive */
371 gchar
*host
= g_ascii_strdown(host_in
, -1);
372 gchar
*host_port
= g_strdup_printf("%s:%" G_GUINT32_FORMAT
, host
, port
);
374 sipe_http_init(sipe_private
);
376 http
= sipe_private
->http
;
377 conn_public
= g_hash_table_lookup(http
->connections
, host_port
);
380 /* re-establishing connection */
381 conn_private
= conn_public
->conn_private
;
382 if (!conn_private
->connection
) {
383 SIPE_DEBUG_INFO("sipe_http_transport_new: re-establishing %s", host_port
);
384 g_queue_remove(http
->timeouts
, conn_private
);
389 SIPE_DEBUG_INFO("sipe_http_transport_new: new %s", host_port
);
391 conn_public
= sipe_http_connection_new(sipe_private
,
395 conn_public
->conn_private
= conn_private
= g_new0(struct sipe_http_connection_private
, 1);
397 conn_private
->host_port
= host_port
;
399 g_hash_table_insert(http
->connections
,
402 host_port
= NULL
; /* conn_private takes ownership of the key */
405 if (!conn_private
->connection
) {
406 time_t current_time
= time(NULL
);
407 sipe_connect_setup setup
= {
408 SIPE_TRANSPORT_TLS
, /* TBD: we only support TLS for now */
412 sipe_http_transport_connected
,
413 sipe_http_transport_input
,
414 sipe_http_transport_error
417 conn_public
->connected
= FALSE
;
418 conn_private
->connection
= sipe_backend_transport_connect(SIPE_CORE_PUBLIC
,
420 conn_private
->timeout
= current_time
+ SIPE_HTTP_DEFAULT_TIMEOUT
;
422 g_queue_insert_sorted(http
->timeouts
,
427 /* start timeout timer if necessary */
428 if (http
->next_timeout
== 0)
429 start_timer(sipe_private
, current_time
);
437 void sipe_http_transport_send(struct sipe_http_connection_public
*conn_public
,
441 struct sipe_http_connection_private
*conn_private
= conn_public
->conn_private
;
442 GString
*message
= g_string_new(header
);
444 g_string_append_printf(message
, "\r\n%s", body
? body
: "");
446 sipe_utils_message_debug("HTTP", message
->str
, NULL
, TRUE
);
447 sipe_backend_transport_message(conn_private
->connection
, message
->str
);
448 g_string_free(message
, TRUE
);