http: small cleanup in sipe-http-request.c
[siplcs.git] / src / core / sipe-http-transport.c
bloba791eb7a3bfce6dd78cac05beabc8306ba472ed2
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_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;
57 gchar *host_port;
58 time_t timeout; /* in seconds from epoch */
61 struct sipe_http {
62 GHashTable *connections;
63 GQueue *timeouts;
64 time_t next_timeout; /* in seconds from epoch, 0 if timer isn't running */
67 static gint timeout_compare(gconstpointer a,
68 gconstpointer b,
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);
88 g_free(conn_private);
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,
96 const gchar *message)
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,
110 gpointer data)
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;
119 while (1) {
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);
125 if (!conn_private)
126 break;
128 /* restart timer for next connection */
129 if (conn_private->timeout > current_time) {
130 start_timer(sipe_private, current_time);
131 break;
134 /* next connection timed-out too, loop around */
138 static void start_timer(struct sipe_core_private *sipe_private,
139 time_t current_time)
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,
147 conn_private,
148 http->next_timeout - current_time,
149 sipe_http_transport_timeout,
150 NULL);
153 void sipe_http_free(struct sipe_core_private *sipe_private)
155 struct sipe_http *http = sipe_private->http;
156 if (!http)
157 return;
159 sipe_schedule_cancel(sipe_private, SIPE_HTTP_TIMEOUT_ACTION);
160 g_hash_table_destroy(http->connections);
161 g_queue_free(http->timeouts);
162 g_free(http);
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)
170 return;
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,
174 NULL,
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') {
196 current++;
198 if (current != connection->buffer)
199 sipe_utils_shrink_buffer(connection, current);
201 if ((current = strstr(connection->buffer, "\r\n\r\n")) != NULL) {
202 struct sipmsg *msg;
203 gboolean next;
205 current += 2;
206 current[0] = '\0';
207 msg = sipmsg_parse_header(connection->buffer);
208 if (!msg) {
209 /* restore header for next try */
210 current[0] = '\r';
211 return;
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;
220 msg->bodylen = 0;
221 while (strlen(start) > 0) {
222 gchar *tmp;
223 guint length = g_ascii_strtoll(start, &tmp, 16);
224 guint remainder;
225 struct _chunk {
226 guint length;
227 const gchar *start;
228 } *chunk;
230 /* Illegal number */
231 if ((length == 0) && (start == tmp))
232 break;
233 msg->bodylen += length;
235 /* Chunk header not finished yet */
236 tmp = strstr(tmp, "\r\n");
237 if (tmp == NULL)
238 break;
240 /* Chunk not finished yet */
241 tmp += 2;
242 remainder = connection->buffer_used - (tmp - connection->buffer);
243 if (remainder < length + 2)
244 break;
246 /* Next chunk */
247 start = tmp + length + 2;
249 /* Body completed */
250 if (length == 0) {
251 gchar *dummy = g_malloc(msg->bodylen + 1);
252 gchar *p = dummy;
253 GSList *entry = chunks;
255 while (entry) {
256 chunk = entry->data;
257 memcpy(p, chunk->start, chunk->length);
258 p += chunk->length;
259 entry = entry->next;
261 p[0] = '\0';
263 msg->body = dummy;
264 sipe_utils_message_debug("HTTP",
265 connection->buffer,
266 msg->body,
267 FALSE);
269 current = start;
270 sipe_utils_shrink_buffer(connection,
271 current);
273 incomplete = FALSE;
274 break;
277 /* Append completed chunk */
278 chunk = g_new0(struct _chunk, 1);
279 chunk->length = length;
280 chunk->start = tmp;
281 chunks = g_slist_append(chunks, chunk);
284 if (chunks) {
285 GSList *entry = chunks;
286 while (entry) {
287 g_free(entry->data);
288 entry = entry->next;
290 g_slist_free(chunks);
293 if (incomplete) {
294 /* restore header for next try */
295 sipmsg_free(msg);
296 current[0] = '\r';
297 return;
300 } else {
301 guint remainder = connection->buffer_used - (current + 2 - connection->buffer);
303 if (remainder >= (guint) msg->bodylen) {
304 char *dummy = g_malloc(msg->bodylen + 1);
305 current += 2;
306 memcpy(dummy, current, msg->bodylen);
307 dummy[msg->bodylen] = '\0';
308 msg->body = dummy;
309 current += msg->bodylen;
310 sipe_utils_message_debug("HTTP",
311 connection->buffer,
312 msg->body,
313 FALSE);
314 sipe_utils_shrink_buffer(connection, current);
315 } else {
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 */
320 sipmsg_free(msg);
321 current[0] = '\r';
322 return;
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 */
339 if (next)
340 sipe_http_transport_new(conn_public->sipe_private,
341 conn_public->host,
342 conn_public->port);
344 } else if (next) {
345 /* trigger sending of next pending request */
346 sipe_http_request_next(conn_public);
349 sipmsg_free(msg);
353 static void sipe_http_transport_error(struct sipe_transport_connection *connection,
354 const gchar *msg)
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,
359 msg);
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,
365 const guint32 port)
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);
379 if (conn_public) {
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);
387 } else {
388 /* new connection */
389 SIPE_DEBUG_INFO("sipe_http_transport_new: new %s", host_port);
391 conn_public = sipe_http_connection_new(sipe_private,
392 host,
393 port);
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,
400 host_port,
401 conn_public);
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 */
409 host,
410 port,
411 conn_public,
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,
419 &setup);
420 conn_private->timeout = current_time + SIPE_HTTP_DEFAULT_TIMEOUT;
422 g_queue_insert_sorted(http->timeouts,
423 conn_private,
424 timeout_compare,
425 NULL);
427 /* start timeout timer if necessary */
428 if (http->next_timeout == 0)
429 start_timer(sipe_private, current_time);
432 g_free(host_port);
433 g_free(host);
434 return(conn_public);
437 void sipe_http_transport_send(struct sipe_http_connection_public *conn_public,
438 const gchar *header,
439 const gchar *body)
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);
452 Local Variables:
453 mode: c
454 c-file-style: "bsd"
455 indent-tabs-mode: t
456 tab-width: 8
457 End: