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 *) 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 */
67 GHashTable
*connections
;
69 time_t next_timeout
; /* in seconds from epoch, 0 if timer isn't running */
72 static gint
timeout_compare(gconstpointer a
,
74 SIPE_UNUSED_PARAMETER gpointer user_data
)
76 return(((struct sipe_http_connection
*) a
)->timeout
-
77 ((struct sipe_http_connection
*) b
)->timeout
);
80 static void sipe_http_transport_free(gpointer data
)
82 struct sipe_http_connection
*conn
= data
;
83 struct sipe_http
*http
= conn
->public.sipe_private
->http
;
85 SIPE_DEBUG_INFO("sipe_http_transport_free: destroying connection '%s'",
89 sipe_backend_transport_disconnect(conn
->connection
);
90 conn
->connection
= NULL
;
92 g_queue_remove(http
->timeouts
, conn
);
94 sipe_http_request_shutdown(SIPE_HTTP_CONNECTION_PUBLIC
);
96 g_free(conn
->public.host
);
98 g_free(conn
->host_port
);
102 static void sipe_http_transport_drop(struct sipe_http
*http
,
103 struct sipe_http_connection
*conn
,
104 const gchar
*message
)
106 SIPE_DEBUG_INFO("sipe_http_transport_drop: dropping connection '%s': %s",
108 message
? message
: "REASON UNKNOWN");
110 /* this triggers sipe_http_transport_new */
111 g_hash_table_remove(http
->connections
,
115 static void start_timer(struct sipe_core_private
*sipe_private
,
116 time_t current_time
);
117 static void sipe_http_transport_timeout(struct sipe_core_private
*sipe_private
,
120 struct sipe_http
*http
= sipe_private
->http
;
121 struct sipe_http_connection
*conn
= data
;
122 time_t current_time
= time(NULL
);
124 /* timer has expired */
125 http
->next_timeout
= 0;
128 sipe_http_transport_drop(http
, conn
, "timeout");
129 /* conn is no longer valid */
131 /* is there another active connection? */
132 conn
= g_queue_peek_head(http
->timeouts
);
136 /* restart timer for next connection */
137 if (conn
->timeout
> current_time
) {
138 start_timer(sipe_private
, current_time
);
142 /* next connection timed-out too, loop around */
146 static void start_timer(struct sipe_core_private
*sipe_private
,
149 struct sipe_http
*http
= sipe_private
->http
;
150 struct sipe_http_connection
*conn
= g_queue_peek_head(http
->timeouts
);
152 http
->next_timeout
= conn
->timeout
;
153 sipe_schedule_seconds(sipe_private
,
154 SIPE_HTTP_TIMEOUT_ACTION
,
156 http
->next_timeout
- current_time
,
157 sipe_http_transport_timeout
,
161 void sipe_http_free(struct sipe_core_private
*sipe_private
)
163 struct sipe_http
*http
= sipe_private
->http
;
167 sipe_schedule_cancel(sipe_private
, SIPE_HTTP_TIMEOUT_ACTION
);
168 g_hash_table_destroy(http
->connections
);
169 g_queue_free(http
->timeouts
);
171 sipe_private
->http
= NULL
;
174 static void sipe_http_init(struct sipe_core_private
*sipe_private
)
176 struct sipe_http
*http
;
177 if (sipe_private
->http
)
180 sipe_private
->http
= http
= g_new0(struct sipe_http
, 1);
181 http
->connections
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
183 sipe_http_transport_free
);
184 http
->timeouts
= g_queue_new();
187 static void sipe_http_transport_connected(struct sipe_transport_connection
*connection
)
189 struct sipe_http_connection
*conn
= SIPE_HTTP_CONNECTION
;
191 SIPE_DEBUG_INFO("sipe_http_transport_connected: %s", conn
->host_port
);
192 conn
->public.connected
= TRUE
;
193 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC
);
196 static void sipe_http_transport_input(struct sipe_transport_connection
*connection
)
198 struct sipe_http_connection
*conn
= SIPE_HTTP_CONNECTION
;
199 char *current
= connection
->buffer
;
201 /* according to the RFC remove CRLF at the beginning */
202 while (*current
== '\r' || *current
== '\n') {
205 if (current
!= connection
->buffer
)
206 sipe_utils_shrink_buffer(connection
, current
);
208 if ((current
= strstr(connection
->buffer
, "\r\n\r\n")) != NULL
) {
214 msg
= sipmsg_parse_header(connection
->buffer
);
216 /* restore header for next try */
221 /* HTTP/1.1 Transfer-Encoding: chunked */
222 if (msg
->bodylen
== SIPMSG_BODYLEN_CHUNKED
) {
223 gchar
*start
= current
+ 2;
224 GSList
*chunks
= NULL
;
225 gboolean incomplete
= TRUE
;
228 while (strlen(start
) > 0) {
230 guint length
= g_ascii_strtoll(start
, &tmp
, 16);
238 if ((length
== 0) && (start
== tmp
))
240 msg
->bodylen
+= length
;
242 /* Chunk header not finished yet */
243 tmp
= strstr(tmp
, "\r\n");
247 /* Chunk not finished yet */
249 remainder
= connection
->buffer_used
- (tmp
- connection
->buffer
);
250 if (remainder
< length
+ 2)
254 start
= tmp
+ length
+ 2;
258 gchar
*dummy
= g_malloc(msg
->bodylen
+ 1);
260 GSList
*entry
= chunks
;
264 memcpy(p
, chunk
->start
, chunk
->length
);
271 sipe_utils_message_debug("HTTP",
277 sipe_utils_shrink_buffer(connection
,
284 /* Append completed chunk */
285 chunk
= g_new0(struct _chunk
, 1);
286 chunk
->length
= length
;
288 chunks
= g_slist_append(chunks
, chunk
);
292 GSList
*entry
= chunks
;
297 g_slist_free(chunks
);
301 /* restore header for next try */
308 guint remainder
= connection
->buffer_used
- (current
+ 2 - connection
->buffer
);
310 if (remainder
>= (guint
) msg
->bodylen
) {
311 char *dummy
= g_malloc(msg
->bodylen
+ 1);
313 memcpy(dummy
, current
, msg
->bodylen
);
314 dummy
[msg
->bodylen
] = '\0';
316 current
+= msg
->bodylen
;
317 sipe_utils_message_debug("HTTP",
321 sipe_utils_shrink_buffer(connection
, current
);
323 SIPE_DEBUG_INFO("sipe_http_transport_input: body too short (%d < %d, strlen %" G_GSIZE_FORMAT
") - ignoring message",
324 remainder
, msg
->bodylen
, strlen(connection
->buffer
));
326 /* restore header for next try */
333 sipe_http_request_response(SIPE_HTTP_CONNECTION_PUBLIC
, msg
);
334 next
= sipe_http_request_pending(SIPE_HTTP_CONNECTION_PUBLIC
);
336 if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
337 /* drop backend connection */
338 SIPE_DEBUG_INFO("sipe_http_transport_input: server requested close '%s'",
340 sipe_backend_transport_disconnect(conn
->connection
);
341 conn
->connection
= NULL
;
342 conn
->public.connected
= FALSE
;
344 /* if we have pending requests we need to trigger re-connect */
346 sipe_http_transport_new(conn
->public.sipe_private
,
351 /* trigger sending of next pending request */
352 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC
);
359 static void sipe_http_transport_error(struct sipe_transport_connection
*connection
,
362 struct sipe_http_connection
*conn
= SIPE_HTTP_CONNECTION
;
363 sipe_http_transport_drop(conn
->public.sipe_private
->http
,
366 /* conn is no longer valid */
369 struct sipe_http_connection_public
*sipe_http_transport_new(struct sipe_core_private
*sipe_private
,
370 const gchar
*host_in
,
373 struct sipe_http
*http
;
374 struct sipe_http_connection
*conn
;
375 /* host name matching should be case insensitive */
376 gchar
*host
= g_ascii_strdown(host_in
, -1);
377 gchar
*host_port
= g_strdup_printf("%s:%" G_GUINT32_FORMAT
, host
, port
);
379 sipe_http_init(sipe_private
);
381 http
= sipe_private
->http
;
382 conn
= g_hash_table_lookup(http
->connections
, host_port
);
385 /* re-establishing connection */
386 if (!conn
->connection
) {
387 SIPE_DEBUG_INFO("sipe_http_transport_new: re-establishing %s", host_port
);
388 g_queue_remove(http
->timeouts
, conn
);
393 SIPE_DEBUG_INFO("sipe_http_transport_new: new %s", host_port
);
395 conn
= g_new0(struct sipe_http_connection
, 1);
397 conn
->public.sipe_private
= sipe_private
;
398 conn
->public.host
= g_strdup(host
);
399 conn
->public.port
= port
;
401 conn
->host_port
= host_port
;
403 g_hash_table_insert(http
->connections
,
406 host_port
= NULL
; /* conn_private takes ownership of the key */
409 if (!conn
->connection
) {
410 time_t current_time
= time(NULL
);
411 sipe_connect_setup setup
= {
412 SIPE_TRANSPORT_TLS
, /* TBD: we only support TLS for now */
416 sipe_http_transport_connected
,
417 sipe_http_transport_input
,
418 sipe_http_transport_error
421 conn
->public.connected
= FALSE
;
422 conn
->connection
= sipe_backend_transport_connect(SIPE_CORE_PUBLIC
,
425 conn
->timeout
= current_time
+ SIPE_HTTP_DEFAULT_TIMEOUT
;
426 g_queue_insert_sorted(http
->timeouts
,
431 /* start timeout timer if necessary */
432 if (http
->next_timeout
== 0)
433 start_timer(sipe_private
, current_time
);
438 return(SIPE_HTTP_CONNECTION_PUBLIC
);
441 void sipe_http_transport_send(struct sipe_http_connection_public
*conn_public
,
445 struct sipe_http_connection
*conn
= SIPE_HTTP_CONNECTION_PRIVATE
;
446 GString
*message
= g_string_new(header
);
448 g_string_append_printf(message
, "\r\n%s", body
? body
: "");
450 sipe_utils_message_debug("HTTP", message
->str
, NULL
, TRUE
);
451 sipe_backend_transport_message(conn
->connection
, message
->str
);
452 g_string_free(message
, TRUE
);