core cleanup: separate code for sipe_backend_account_status_and_note()
[siplcs.git] / src / core / http-conn.c
blobf0f0599dbeea4cbaa56792650138a0fe34643379
1 /**
2 * @file http-conn.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 /**
26 * Operates with HTTPS connection.
27 * Support Negotiate (Windows only) and NTLM authentications, redirect, cookie, GET/POST.
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
34 #include <stdlib.h>
35 #include <string.h>
37 #include <glib.h>
39 #include "http-conn.h"
40 #include "sipmsg.h"
41 #include "sip-sec.h"
42 #include "sipe-backend.h"
43 #include "sipe-core.h"
44 #include "sipe-core-private.h"
45 #include "sipe-utils.h"
47 /**
48 * HTTP header
49 * @param method (%s) Ex.: GET or POST
50 * @param url (%s) Ex.: /EWS/Exchange.asmx
51 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
53 #define HTTP_CONN_HEADER \
54 "%s %s HTTP/1.1\r\n"\
55 "Host: %s\r\n"\
56 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"
59 struct http_conn_struct {
60 struct sipe_core_public *sipe_public;
62 /* GET, POST */
63 char *method;
64 guint conn_type;
65 gboolean allow_redirect;
66 char *host;
67 guint port;
68 char *url;
69 char *body;
70 char *content_type;
71 HttpConnAuth *auth;
72 HttpConnCallback callback;
73 void *data;
75 struct sipe_transport_connection *conn;
77 SipSecContext sec_ctx;
78 int retries;
80 HttpSession *http_session;
82 /* if server sends "Connection: close" header */
83 gboolean closed;
84 HttpConn* do_close;
86 #define HTTP_CONN ((HttpConn *) conn->user_data)
88 struct http_session_struct {
89 char *cookie;
92 static HttpConn*
93 http_conn_clone(HttpConn* http_conn)
95 HttpConn *res = g_new0(HttpConn, 1);
97 res->http_session = http_conn->http_session;
98 res->method = g_strdup(http_conn->method);
99 res->conn_type = http_conn->conn_type;
100 res->allow_redirect = http_conn->allow_redirect;
101 res->host = g_strdup(http_conn->host);
102 res->port = http_conn->port;
103 res->url = g_strdup(http_conn->url);
104 res->body = g_strdup(http_conn->body);
105 res->content_type = g_strdup(http_conn->content_type);
106 res->auth = http_conn->auth;
107 res->callback = http_conn->callback;
108 res->data = http_conn->data;
110 res->conn = http_conn->conn;
111 res->sec_ctx = http_conn->sec_ctx;
112 res->retries = http_conn->retries;
114 res->do_close = NULL;
116 return res;
119 void
120 http_conn_free(HttpConn* http_conn)
122 if (!http_conn) return;
124 /* don't free "http_conn->http_session" - client should do */
125 g_free(http_conn->method);
126 g_free(http_conn->host);
127 g_free(http_conn->url);
128 g_free(http_conn->body);
129 g_free(http_conn->content_type);
131 if (http_conn->sec_ctx) {
132 sip_sec_destroy_context(http_conn->sec_ctx);
135 g_free(http_conn);
138 gboolean
139 http_conn_is_closed(HttpConn *http_conn)
141 return http_conn->closed;
144 HttpSession *
145 http_conn_session_create()
147 HttpSession *res = g_new0(HttpSession, 1);
148 return res;
151 void
152 http_conn_session_free(HttpSession *http_session)
154 if (!http_session) return;
156 g_free(http_session->cookie);
157 g_free(http_session);
160 void
161 http_conn_auth_free(struct http_conn_auth* auth)
163 g_free(auth->domain);
164 g_free(auth->user);
165 g_free(auth->password);
166 g_free(auth);
169 void
170 http_conn_set_close(HttpConn* http_conn)
172 http_conn->do_close = http_conn;
175 static void
176 http_conn_close(HttpConn *http_conn, const char *message)
178 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message ? message : "");
180 g_return_if_fail(http_conn);
182 sipe_backend_transport_disconnect(http_conn->conn);
183 http_conn_free(http_conn);
187 * Extracts host, port and relative url
188 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
190 * Allocates memory, must be g_free'd.
192 static void
193 http_conn_parse_url(const char *url,
194 char **host,
195 guint *port,
196 char **rel_url)
198 char **parts = g_strsplit(url, "://", 2);
199 char *no_proto;
200 guint port_tmp;
201 char *tmp;
202 char *host_port;
204 /* Make sure we always return valid information */
205 if (host) *host = NULL;
206 if (rel_url) *rel_url = NULL;
208 if(!parts) {
209 return;
210 } else if(!parts[0]) {
211 g_strfreev(parts);
212 return;
215 no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
216 port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
217 g_strfreev(parts);
219 if(!no_proto) {
220 return;
223 tmp = strstr(no_proto, "/");
224 if (tmp && rel_url) *rel_url = g_strdup(tmp);
225 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
226 g_free(no_proto);
228 if(!host_port) {
229 return;
232 parts = g_strsplit(host_port, ":", 2);
234 if(parts) {
235 if (host) *host = g_strdup(parts[0]);
236 if(parts[0]) {
237 port_tmp = parts[1] ? (guint) atoi(parts[1]) : port_tmp;
239 if (port) *port = port_tmp;
240 g_strfreev(parts);
243 g_free(host_port);
246 static void http_conn_error(struct sipe_transport_connection *conn,
247 const gchar *msg)
249 HttpConn *http_conn = HTTP_CONN;
250 if (http_conn->callback) {
251 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, NULL, http_conn, http_conn->data);
253 http_conn_close(http_conn, msg);
256 static void http_conn_send0(HttpConn *http_conn,
257 const char *authorization);
258 static void http_conn_connected(struct sipe_transport_connection *conn)
260 http_conn_send0(HTTP_CONN, NULL);
263 static void http_conn_input(struct sipe_transport_connection *conn);
264 static struct sipe_transport_connection *http_conn_setup(HttpConn *http_conn,
265 struct sipe_core_public *sipe_public,
266 guint type,
267 const gchar *host,
268 guint port) {
269 sipe_connect_setup setup = {
270 type,
271 host,
272 port,
273 http_conn,
274 http_conn_connected,
275 http_conn_input,
276 http_conn_error
279 if (!host) {
280 http_conn_close(http_conn, "Missing host");
281 return NULL;
284 return(sipe_backend_transport_connect(sipe_public, &setup));
287 HttpConn *
288 http_conn_create(struct sipe_core_public *sipe_public,
289 HttpSession *http_session,
290 const char *method,
291 guint conn_type,
292 gboolean allow_redirect,
293 const char *full_url,
294 const char *body,
295 const char *content_type,
296 HttpConnAuth *auth,
297 HttpConnCallback callback,
298 void *data)
300 HttpConn *http_conn;
301 struct sipe_transport_connection *conn;
302 gchar *host, *url;
303 guint port;
305 if (!full_url || (strlen(full_url) == 0)) {
306 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
307 return NULL;
310 http_conn_parse_url(full_url, &host, &port, &url);
311 http_conn = g_new0(HttpConn, 1);
312 conn = http_conn_setup(http_conn, sipe_public, conn_type, host, port);
313 if (!conn) {
314 // http_conn_setup deallocates http_conn on error, don't free here
315 g_free(host);
316 g_free(url);
317 return NULL;
320 http_conn->sipe_public = sipe_public;
321 conn->user_data = http_conn;
323 http_conn->http_session = http_session;
324 http_conn->method = g_strdup(method);
325 http_conn->conn_type = conn_type;
326 http_conn->allow_redirect = allow_redirect;
327 http_conn->host = host;
328 http_conn->port = port;
329 http_conn->url = url;
330 http_conn->body = g_strdup(body);
331 http_conn->content_type = g_strdup(content_type);
332 http_conn->auth = auth;
333 http_conn->callback = callback;
334 http_conn->data = data;
335 http_conn->conn = conn;
337 return http_conn;
340 /* Data part */
341 static void
342 http_conn_send0(HttpConn *http_conn,
343 const char *authorization)
345 GString *outstr;
347 if (!http_conn->host || !http_conn->url) return;
349 outstr = g_string_new("");
350 g_string_append_printf(outstr, HTTP_CONN_HEADER,
351 http_conn->method ? http_conn->method : "GET",
352 http_conn->url,
353 http_conn->host);
354 if (sipe_strequal(http_conn->method, "POST")) {
355 g_string_append_printf(outstr, "Content-Length: %d\r\n",
356 http_conn->body ? (int)strlen(http_conn->body) : 0);
358 g_string_append_printf(outstr, "Content-Type: %s\r\n",
359 http_conn->content_type ? http_conn->content_type : "text/plain");
361 if (http_conn->http_session && http_conn->http_session->cookie) {
362 g_string_append_printf(outstr, "Cookie: %s\r\n", http_conn->http_session->cookie);
364 if (authorization) {
365 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
367 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
369 sipe_utils_message_debug("HTTP", outstr->str, NULL, TRUE);
370 sipe_backend_transport_message(http_conn->conn, outstr->str);
371 g_string_free(outstr, TRUE);
374 void
375 http_conn_send( HttpConn *http_conn,
376 const char *method,
377 const char *full_url,
378 const char *body,
379 const char *content_type,
380 HttpConnCallback callback,
381 void *data)
383 if (!http_conn) {
384 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
385 return;
388 g_free(http_conn->method);
389 g_free(http_conn->url);
390 g_free(http_conn->body);
391 g_free(http_conn->content_type);
392 http_conn->method = g_strdup(method);
393 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
394 http_conn->body = g_strdup(body);
395 http_conn->content_type = g_strdup(content_type);
396 http_conn->callback = callback;
397 http_conn->data = data;
399 http_conn_send0(http_conn, NULL);
402 static void
403 http_conn_process_input_message(HttpConn *http_conn,
404 struct sipmsg *msg)
406 /* Redirect */
407 if ((msg->response == 300 ||
408 msg->response == 301 ||
409 msg->response == 302 ||
410 msg->response == 307) &&
411 http_conn->allow_redirect)
413 const char *location = sipmsg_find_header(msg, "Location");
414 gchar *host, *url;
415 guint port;
417 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location ? location : "");
418 http_conn_parse_url(location, &host, &port, &url);
420 if (host) {
421 http_conn->do_close = http_conn_clone(http_conn);
422 http_conn->sec_ctx = NULL;
424 g_free(http_conn->host);
425 g_free(http_conn->url);
427 http_conn->host = host;
428 http_conn->port = port;
429 http_conn->url = url;
431 http_conn->conn = http_conn_setup(http_conn,
432 http_conn->sipe_public,
433 http_conn->conn_type,
434 host,
435 port);
436 } else {
437 SIPE_DEBUG_ERROR_NOFORMAT("http_conn_process_input_message: no redirect host");
438 g_free(url);
439 return;
442 /* Authentication required */
443 else if (msg->response == 401) {
444 const gchar *auth_hdr = NULL;
445 guint auth_type;
446 const char *auth_name;
447 char *authorization;
448 char *output_toked_base64;
449 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
450 long ret = -1;
452 http_conn->retries++;
453 if (http_conn->retries > 2) {
454 if (http_conn->callback) {
455 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
457 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
458 http_conn_set_close(http_conn);
459 return;
462 #ifdef HAVE_SSPI
463 if (http_conn->auth && http_conn->auth->use_negotiate)
464 auth_hdr = sipmsg_find_auth_header(msg, "Negotiate");
465 if (auth_hdr) {
466 auth_type = AUTH_TYPE_NEGOTIATE;
467 auth_name = "Negotiate";
468 } else {
469 #endif
470 auth_hdr = sipmsg_find_auth_header(msg, "NTLM");
471 auth_type = AUTH_TYPE_NTLM;
472 auth_name = "NTLM";
473 #ifdef HAVE_SSPI
475 #endif
476 if (!auth_hdr) {
477 if (http_conn->callback) {
478 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
480 #ifdef HAVE_SSPI
481 #define AUTHSTRING "NTLM and Negotiate authentications are"
482 #else /* !HAVE_SSPI */
483 #define AUTHSTRING "NTLM authentication is"
484 #endif /* HAVE_SSPI */
485 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
486 AUTHSTRING
488 http_conn_set_close(http_conn);
489 return;
492 if (!http_conn->sec_ctx) {
493 http_conn->sec_ctx =
494 sip_sec_create_context(auth_type,
495 use_sso,
497 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
498 http_conn->auth ? http_conn->auth->user : NULL,
499 http_conn->auth ? http_conn->auth->password : NULL);
502 if (http_conn->sec_ctx) {
503 char **parts = g_strsplit(auth_hdr, " ", 0);
504 char *spn = g_strdup_printf("HTTP/%s", http_conn->host);
505 ret = sip_sec_init_context_step(http_conn->sec_ctx,
506 spn,
507 parts[1],
508 &output_toked_base64,
509 NULL);
510 g_free(spn);
511 g_strfreev(parts);
514 if (ret < 0) {
515 if (http_conn->callback) {
516 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
518 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
519 http_conn_set_close(http_conn);
520 return;
523 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
524 g_free(output_toked_base64);
526 http_conn_send0(http_conn, authorization);
527 g_free(authorization);
529 /* Other response */
530 else {
531 const char *set_cookie_hdr;
532 const char *content_type = sipmsg_find_header(msg, "Content-Type");
533 http_conn->retries = 0;
535 /* Set cookies.
536 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
538 if (http_conn->http_session && (set_cookie_hdr = sipmsg_find_header(msg, "Set-Cookie"))) {
539 char **parts;
540 char *tmp;
541 int i;
543 g_free(http_conn->http_session->cookie);
544 http_conn->http_session->cookie = NULL;
546 parts = g_strsplit(set_cookie_hdr, ";", 0);
547 for (i = 0; parts[i]; i++) {
548 if (!strstr(parts[i], "path=") &&
549 !strstr(parts[i], "domain=") &&
550 !strstr(parts[i], "expires=") &&
551 !strstr(parts[i], "secure"))
553 tmp = http_conn->http_session->cookie;
554 http_conn->http_session->cookie = !tmp ?
555 g_strdup(parts[i]) :
556 g_strconcat(http_conn->http_session->cookie, ";", parts[i], NULL);
557 g_free(tmp);
560 g_strfreev(parts);
561 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
562 http_conn->http_session->cookie ? http_conn->http_session->cookie : "");
565 if (http_conn->callback) {
566 (*http_conn->callback)(msg->response, msg->body, content_type, http_conn, http_conn->data);
571 static void http_conn_input(struct sipe_transport_connection *conn)
573 HttpConn *http_conn = HTTP_CONN;
574 char *cur = conn->buffer;
576 /* according to the RFC remove CRLF at the beginning */
577 while (*cur == '\r' || *cur == '\n') {
578 cur++;
580 if (cur != conn->buffer)
581 sipe_utils_shrink_buffer(conn, cur);
583 while ((cur = strstr(conn->buffer, "\r\n\r\n")) != NULL) {
584 struct sipmsg *msg;
585 guint remainder;
587 cur += 2;
588 cur[0] = '\0';
589 msg = sipmsg_parse_header(conn->buffer);
591 /* HTTP/1.1 Transfer-Encoding: chunked */
592 if (msg && (msg->bodylen == SIPMSG_BODYLEN_CHUNKED)) {
593 gchar *start = cur + 2;
594 GSList *chunks = NULL;
595 gboolean incomplete = TRUE;
597 msg->bodylen = 0;
598 while (strlen(start) > 0) {
599 gchar *tmp;
600 guint length = strtol(start, &tmp, 16);
601 struct _chunk {
602 guint length;
603 const gchar *start;
604 } *chunk;
606 /* Illegal number */
607 if ((length == 0) && (start == tmp))
608 break;
609 msg->bodylen += length;
611 /* Chunk header not finished yet */
612 tmp = strstr(tmp, "\r\n");
613 if (tmp == NULL)
614 break;
616 /* Chunk not finished yet */
617 tmp += 2;
618 remainder = conn->buffer_used - (tmp - conn->buffer);
619 if (remainder < length + 2)
620 break;
622 /* Next chunk */
623 start = tmp + length + 2;
625 /* Body completed */
626 if (length == 0) {
627 gchar *dummy = g_malloc(msg->bodylen + 1);
628 gchar *p = dummy;
629 GSList *entry = chunks;
631 while (entry) {
632 chunk = entry->data;
633 memcpy(p, chunk->start, chunk->length);
634 p += chunk->length;
635 entry = entry->next;
637 p[0] = '\0';
639 msg->body = dummy;
640 sipe_utils_message_debug("HTTP",
641 conn->buffer,
642 msg->body,
643 FALSE);
645 cur = start;
646 sipe_utils_shrink_buffer(conn, cur);
648 incomplete = FALSE;
649 break;
652 /* Append completed chunk */
653 chunk = g_new0(struct _chunk, 1);
654 chunk->length = length;
655 chunk->start = tmp;
656 chunks = g_slist_append(chunks, chunk);
659 if (chunks) {
660 GSList *entry = chunks;
661 while (entry) {
662 g_free(entry->data);
663 entry = entry->next;
665 g_slist_free(chunks);
668 if (incomplete) {
669 /* restore header for next try */
670 sipmsg_free(msg);
671 cur[0] = '\r';
672 return;
675 } else {
676 cur += 2;
677 remainder = conn->buffer_used - (cur - conn->buffer);
678 if (msg && remainder >= (guint) msg->bodylen) {
679 char *dummy = g_malloc(msg->bodylen + 1);
680 memcpy(dummy, cur, msg->bodylen);
681 dummy[msg->bodylen] = '\0';
682 msg->body = dummy;
683 cur += msg->bodylen;
684 sipe_utils_message_debug("HTTP",
685 conn->buffer,
686 msg->body,
687 FALSE);
688 sipe_utils_shrink_buffer(conn, cur);
689 } else {
690 if (msg){
691 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder, msg->bodylen, (int)strlen(conn->buffer));
692 sipmsg_free(msg);
695 /* restore header for next try */
696 cur[-2] = '\r';
697 return;
701 /* important to set before callback call */
702 if (sipe_strcase_equal(sipmsg_find_header(msg, "Connection"), "close")) {
703 http_conn->closed = TRUE;
706 http_conn_process_input_message(http_conn, msg);
708 sipmsg_free(msg);
711 if (http_conn->closed) {
712 http_conn_close(http_conn->do_close, "Server closed connection");
713 } else if (http_conn->do_close) {
714 http_conn_close(http_conn->do_close, "User initiated");
719 Local Variables:
720 mode: c
721 c-file-style: "bsd"
722 indent-tabs-mode: t
723 tab-width: 8
724 End: