security: accept NULL domain for NTLM
[siplcs.git] / src / core / sipe-http-transport.c
blob7ccaa71bf6433235f79685ade991619ba5a9d0dc
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 */
72 static gint timeout_compare(gconstpointer a,
73 gconstpointer b,
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'",
86 conn->host_port);
88 if (conn->connection)
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);
99 g_free(conn);
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",
107 conn->host_port,
108 message ? message : "REASON UNKNOWN");
110 /* this triggers sipe_http_transport_new */
111 g_hash_table_remove(http->connections,
112 conn->host_port);
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,
118 gpointer data)
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;
127 while (1) {
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);
133 if (!conn)
134 break;
136 /* restart timer for next connection */
137 if (conn->timeout > current_time) {
138 start_timer(sipe_private, current_time);
139 break;
142 /* next connection timed-out too, loop around */
146 static void start_timer(struct sipe_core_private *sipe_private,
147 time_t current_time)
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,
155 conn,
156 http->next_timeout - current_time,
157 sipe_http_transport_timeout,
158 NULL);
161 static void sipe_http_transport_update_timeout_queue(struct sipe_http_connection *conn,
162 gboolean remove)
164 struct sipe_core_private *sipe_private = conn->public.sipe_private;
165 struct sipe_http *http = sipe_private->http;
166 GQueue *timeouts = http->timeouts;
167 time_t current_time = time(NULL);
169 /* is this connection at head of queue? */
170 gboolean update = (conn == g_queue_peek_head(timeouts));
172 /* update timeout queue */
173 if (remove) {
174 g_queue_remove(timeouts, conn);
175 } else {
176 conn->timeout = current_time + SIPE_HTTP_DEFAULT_TIMEOUT;
177 g_queue_sort(timeouts,
178 timeout_compare,
179 NULL);
182 /* update timer if necessary */
183 if (update) {
184 sipe_schedule_cancel(sipe_private, SIPE_HTTP_TIMEOUT_ACTION);
185 if (g_queue_is_empty(timeouts)) {
186 http->next_timeout = 0;
187 } else {
188 start_timer(sipe_private, current_time);
193 void sipe_http_free(struct sipe_core_private *sipe_private)
195 struct sipe_http *http = sipe_private->http;
196 if (!http)
197 return;
199 sipe_schedule_cancel(sipe_private, SIPE_HTTP_TIMEOUT_ACTION);
200 g_hash_table_destroy(http->connections);
201 g_queue_free(http->timeouts);
202 g_free(http);
203 sipe_private->http = NULL;
206 static void sipe_http_init(struct sipe_core_private *sipe_private)
208 struct sipe_http *http;
209 if (sipe_private->http)
210 return;
212 sipe_private->http = http = g_new0(struct sipe_http, 1);
213 http->connections = g_hash_table_new_full(g_str_hash, g_str_equal,
214 NULL,
215 sipe_http_transport_free);
216 http->timeouts = g_queue_new();
219 static void sipe_http_transport_connected(struct sipe_transport_connection *connection)
221 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
222 struct sipe_core_private *sipe_private = conn->public.sipe_private;
223 struct sipe_http *http = sipe_private->http;
224 time_t current_time = time(NULL);
226 SIPE_DEBUG_INFO("sipe_http_transport_connected: %s", conn->host_port);
227 conn->public.connected = TRUE;
229 /* add active connection to timeout queue */
230 conn->timeout = current_time + SIPE_HTTP_DEFAULT_TIMEOUT;
231 g_queue_insert_sorted(http->timeouts,
232 conn,
233 timeout_compare,
234 NULL);
236 /* start timeout timer if necessary */
237 if (http->next_timeout == 0)
238 start_timer(sipe_private, current_time);
240 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC);
243 static void sipe_http_transport_input(struct sipe_transport_connection *connection)
245 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
246 char *current = connection->buffer;
248 /* according to the RFC remove CRLF at the beginning */
249 while (*current == '\r' || *current == '\n') {
250 current++;
252 if (current != connection->buffer)
253 sipe_utils_shrink_buffer(connection, current);
255 if ((current = strstr(connection->buffer, "\r\n\r\n")) != NULL) {
256 struct sipmsg *msg;
257 gboolean next;
259 current += 2;
260 current[0] = '\0';
261 msg = sipmsg_parse_header(connection->buffer);
262 if (!msg) {
263 /* restore header for next try */
264 current[0] = '\r';
265 return;
268 /* HTTP/1.1 Transfer-Encoding: chunked */
269 if (msg->bodylen == SIPMSG_BODYLEN_CHUNKED) {
270 gchar *start = current + 2;
271 GSList *chunks = NULL;
272 gboolean incomplete = TRUE;
274 msg->bodylen = 0;
275 while (strlen(start) > 0) {
276 gchar *tmp;
277 guint length = g_ascii_strtoll(start, &tmp, 16);
278 guint remainder;
279 struct _chunk {
280 guint length;
281 const gchar *start;
282 } *chunk;
284 /* Illegal number */
285 if ((length == 0) && (start == tmp))
286 break;
287 msg->bodylen += length;
289 /* Chunk header not finished yet */
290 tmp = strstr(tmp, "\r\n");
291 if (tmp == NULL)
292 break;
294 /* Chunk not finished yet */
295 tmp += 2;
296 remainder = connection->buffer_used - (tmp - connection->buffer);
297 if (remainder < length + 2)
298 break;
300 /* Next chunk */
301 start = tmp + length + 2;
303 /* Body completed */
304 if (length == 0) {
305 gchar *dummy = g_malloc(msg->bodylen + 1);
306 gchar *p = dummy;
307 GSList *entry = chunks;
309 while (entry) {
310 chunk = entry->data;
311 memcpy(p, chunk->start, chunk->length);
312 p += chunk->length;
313 entry = entry->next;
315 p[0] = '\0';
317 msg->body = dummy;
318 sipe_utils_message_debug("HTTP",
319 connection->buffer,
320 msg->body,
321 FALSE);
323 current = start;
324 sipe_utils_shrink_buffer(connection,
325 current);
327 incomplete = FALSE;
328 break;
331 /* Append completed chunk */
332 chunk = g_new0(struct _chunk, 1);
333 chunk->length = length;
334 chunk->start = tmp;
335 chunks = g_slist_append(chunks, chunk);
338 if (chunks) {
339 GSList *entry = chunks;
340 while (entry) {
341 g_free(entry->data);
342 entry = entry->next;
344 g_slist_free(chunks);
347 if (incomplete) {
348 /* restore header for next try */
349 sipmsg_free(msg);
350 current[0] = '\r';
351 return;
354 } else {
355 guint remainder = connection->buffer_used - (current + 2 - connection->buffer);
357 if (remainder >= (guint) msg->bodylen) {
358 char *dummy = g_malloc(msg->bodylen + 1);
359 current += 2;
360 memcpy(dummy, current, msg->bodylen);
361 dummy[msg->bodylen] = '\0';
362 msg->body = dummy;
363 current += msg->bodylen;
364 sipe_utils_message_debug("HTTP",
365 connection->buffer,
366 msg->body,
367 FALSE);
368 sipe_utils_shrink_buffer(connection, current);
369 } else {
370 SIPE_DEBUG_INFO("sipe_http_transport_input: body too short (%d < %d, strlen %" G_GSIZE_FORMAT ") - ignoring message",
371 remainder, msg->bodylen, strlen(connection->buffer));
373 /* restore header for next try */
374 sipmsg_free(msg);
375 current[0] = '\r';
376 return;
380 sipe_http_request_response(SIPE_HTTP_CONNECTION_PUBLIC, msg);
381 next = sipe_http_request_pending(SIPE_HTTP_CONNECTION_PUBLIC);
383 if (sipe_strcase_equal(sipmsg_find_header(msg, "Connection"), "close")) {
384 /* drop backend connection */
385 SIPE_DEBUG_INFO("sipe_http_transport_input: server requested close '%s'",
386 conn->host_port);
387 sipe_backend_transport_disconnect(conn->connection);
388 conn->connection = NULL;
389 conn->public.connected = FALSE;
391 /* if we have pending requests we need to trigger re-connect */
392 if (next)
393 sipe_http_transport_new(conn->public.sipe_private,
394 conn->public.host,
395 conn->public.port);
397 } else if (next) {
398 /* trigger sending of next pending request */
399 sipe_http_request_next(SIPE_HTTP_CONNECTION_PUBLIC);
402 sipmsg_free(msg);
406 static void sipe_http_transport_error(struct sipe_transport_connection *connection,
407 const gchar *msg)
409 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION;
410 sipe_http_transport_drop(conn->public.sipe_private->http,
411 conn,
412 msg);
413 /* conn is no longer valid */
416 struct sipe_http_connection_public *sipe_http_transport_new(struct sipe_core_private *sipe_private,
417 const gchar *host_in,
418 const guint32 port)
420 struct sipe_http *http;
421 struct sipe_http_connection *conn;
422 /* host name matching should be case insensitive */
423 gchar *host = g_ascii_strdown(host_in, -1);
424 gchar *host_port = g_strdup_printf("%s:%" G_GUINT32_FORMAT, host, port);
426 sipe_http_init(sipe_private);
428 http = sipe_private->http;
429 conn = g_hash_table_lookup(http->connections, host_port);
431 if (conn) {
432 /* re-establishing connection */
433 if (!conn->connection) {
434 SIPE_DEBUG_INFO("sipe_http_transport_new: re-establishing %s", host_port);
436 /* will be re-inserted after connect */
437 sipe_http_transport_update_timeout_queue(conn, TRUE);
440 } else {
441 /* new connection */
442 SIPE_DEBUG_INFO("sipe_http_transport_new: new %s", host_port);
444 conn = g_new0(struct sipe_http_connection, 1);
446 conn->public.sipe_private = sipe_private;
447 conn->public.host = g_strdup(host);
448 conn->public.port = port;
450 conn->host_port = host_port;
452 g_hash_table_insert(http->connections,
453 host_port,
454 conn);
455 host_port = NULL; /* conn_private takes ownership of the key */
458 if (!conn->connection) {
459 sipe_connect_setup setup = {
460 SIPE_TRANSPORT_TLS, /* TBD: we only support TLS for now */
461 host,
462 port,
463 conn,
464 sipe_http_transport_connected,
465 sipe_http_transport_input,
466 sipe_http_transport_error
469 conn->public.connected = FALSE;
470 conn->connection = sipe_backend_transport_connect(SIPE_CORE_PUBLIC,
471 &setup);
474 g_free(host_port);
475 g_free(host);
476 return(SIPE_HTTP_CONNECTION_PUBLIC);
479 void sipe_http_transport_send(struct sipe_http_connection_public *conn_public,
480 const gchar *header,
481 const gchar *body)
483 struct sipe_http_connection *conn = SIPE_HTTP_CONNECTION_PRIVATE;
484 GString *message = g_string_new(header);
486 g_string_append_printf(message, "\r\n%s", body ? body : "");
488 sipe_utils_message_debug("HTTP", message->str, NULL, TRUE);
489 sipe_backend_transport_message(conn->connection, message->str);
490 g_string_free(message, TRUE);
492 sipe_http_transport_update_timeout_queue(conn, FALSE);
496 Local Variables:
497 mode: c
498 c-file-style: "bsd"
499 indent-tabs-mode: t
500 tab-width: 8
501 End: