core cleanup: start cleanup up status handling
[siplcs.git] / src / core / http-conn.c
blobe216a4d611780d8554b35064723ee1e0dd924a6e
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 const gchar *additional_headers;
72 HttpConnAuth *auth;
73 HttpConnCallback callback;
74 void *data;
76 struct sipe_transport_connection *conn;
78 SipSecContext sec_ctx;
79 int retries;
81 HttpSession *http_session;
83 /* if server sends "Connection: close" header */
84 gboolean closed;
85 HttpConn* do_close;
87 #define HTTP_CONN ((HttpConn *) conn->user_data)
89 struct http_session_struct {
90 char *cookie;
93 static HttpConn*
94 http_conn_clone(HttpConn* http_conn)
96 HttpConn *res = g_new0(HttpConn, 1);
98 res->http_session = http_conn->http_session;
99 res->method = g_strdup(http_conn->method);
100 res->conn_type = http_conn->conn_type;
101 res->allow_redirect = http_conn->allow_redirect;
102 res->host = g_strdup(http_conn->host);
103 res->port = http_conn->port;
104 res->url = g_strdup(http_conn->url);
105 res->body = g_strdup(http_conn->body);
106 res->content_type = g_strdup(http_conn->content_type);
107 res->auth = http_conn->auth;
108 res->callback = http_conn->callback;
109 res->data = http_conn->data;
111 res->conn = http_conn->conn;
112 res->sec_ctx = http_conn->sec_ctx;
113 res->retries = http_conn->retries;
115 res->do_close = NULL;
117 return res;
120 void
121 http_conn_free(HttpConn* http_conn)
123 if (!http_conn) return;
125 /* don't free "http_conn->http_session" - client should do */
126 g_free(http_conn->method);
127 g_free(http_conn->host);
128 g_free(http_conn->url);
129 g_free(http_conn->body);
130 g_free(http_conn->content_type);
132 if (http_conn->sec_ctx) {
133 sip_sec_destroy_context(http_conn->sec_ctx);
136 g_free(http_conn);
139 gboolean
140 http_conn_is_closed(HttpConn *http_conn)
142 return http_conn->closed;
145 HttpSession *
146 http_conn_session_create()
148 HttpSession *res = g_new0(HttpSession, 1);
149 return res;
152 void
153 http_conn_session_free(HttpSession *http_session)
155 if (!http_session) return;
157 g_free(http_session->cookie);
158 g_free(http_session);
161 void
162 http_conn_auth_free(struct http_conn_auth* auth)
164 g_free(auth->domain);
165 g_free(auth->user);
166 g_free(auth->password);
167 g_free(auth);
170 void
171 http_conn_set_close(HttpConn* http_conn)
173 http_conn->do_close = http_conn;
176 static void
177 http_conn_close(HttpConn *http_conn, const char *message)
179 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message ? message : "");
181 g_return_if_fail(http_conn);
183 sipe_backend_transport_disconnect(http_conn->conn);
184 http_conn_free(http_conn);
188 * Extracts host, port and relative url
189 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
191 * Allocates memory, must be g_free'd.
193 static void
194 http_conn_parse_url(const char *url,
195 char **host,
196 guint *port,
197 char **rel_url)
199 char **parts = g_strsplit(url, "://", 2);
200 char *no_proto;
201 guint port_tmp;
202 char *tmp;
203 char *host_port;
205 /* Make sure we always return valid information */
206 if (host) *host = NULL;
207 if (rel_url) *rel_url = NULL;
209 if(!parts) {
210 return;
211 } else if(!parts[0]) {
212 g_strfreev(parts);
213 return;
216 no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
217 port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
218 g_strfreev(parts);
220 if(!no_proto) {
221 return;
224 tmp = strstr(no_proto, "/");
225 if (tmp && rel_url) *rel_url = g_strdup(tmp);
226 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
227 g_free(no_proto);
229 if(!host_port) {
230 return;
233 parts = g_strsplit(host_port, ":", 2);
235 if(parts) {
236 if (host) *host = g_strdup(parts[0]);
237 if(parts[0]) {
238 port_tmp = parts[1] ? (guint) atoi(parts[1]) : port_tmp;
240 if (port) *port = port_tmp;
241 g_strfreev(parts);
244 g_free(host_port);
247 static void http_conn_error(struct sipe_transport_connection *conn,
248 const gchar *msg)
250 HttpConn *http_conn = HTTP_CONN;
251 if (http_conn->callback) {
252 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, NULL, http_conn, http_conn->data);
254 http_conn_close(http_conn, msg);
257 static void http_conn_send0(HttpConn *http_conn,
258 const char *authorization);
259 static void http_conn_connected(struct sipe_transport_connection *conn)
261 http_conn_send0(HTTP_CONN, NULL);
264 static void http_conn_input(struct sipe_transport_connection *conn);
265 static struct sipe_transport_connection *http_conn_setup(HttpConn *http_conn,
266 struct sipe_core_public *sipe_public,
267 guint type,
268 const gchar *host,
269 guint port) {
270 sipe_connect_setup setup = {
271 type,
272 host,
273 port,
274 http_conn,
275 http_conn_connected,
276 http_conn_input,
277 http_conn_error
280 if (!host) {
281 http_conn_close(http_conn, "Missing host");
282 return NULL;
285 return(sipe_backend_transport_connect(sipe_public, &setup));
288 HttpConn *
289 http_conn_create(struct sipe_core_public *sipe_public,
290 HttpSession *http_session,
291 const char *method,
292 guint conn_type,
293 gboolean allow_redirect,
294 const char *full_url,
295 const char *body,
296 const char *content_type,
297 const gchar *additional_headers,
298 HttpConnAuth *auth,
299 HttpConnCallback callback,
300 void *data)
302 HttpConn *http_conn;
303 struct sipe_transport_connection *conn;
304 gchar *host, *url;
305 guint port;
307 if (!full_url || (strlen(full_url) == 0)) {
308 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
309 return NULL;
312 http_conn_parse_url(full_url, &host, &port, &url);
313 http_conn = g_new0(HttpConn, 1);
314 conn = http_conn_setup(http_conn, sipe_public, conn_type, host, port);
315 if (!conn) {
316 // http_conn_setup deallocates http_conn on error, don't free here
317 g_free(host);
318 g_free(url);
319 return NULL;
322 http_conn->sipe_public = sipe_public;
323 conn->user_data = http_conn;
325 http_conn->http_session = http_session;
326 http_conn->method = g_strdup(method);
327 http_conn->conn_type = conn_type;
328 http_conn->allow_redirect = allow_redirect;
329 http_conn->host = host;
330 http_conn->port = port;
331 http_conn->url = url;
332 http_conn->body = g_strdup(body);
333 http_conn->content_type = g_strdup(content_type);
334 http_conn->additional_headers = additional_headers;
335 http_conn->auth = auth;
336 http_conn->callback = callback;
337 http_conn->data = data;
338 http_conn->conn = conn;
340 return http_conn;
343 /* Data part */
344 static void
345 http_conn_send0(HttpConn *http_conn,
346 const char *authorization)
348 GString *outstr;
350 if (!http_conn->host || !http_conn->url) return;
352 outstr = g_string_new("");
353 g_string_append_printf(outstr, HTTP_CONN_HEADER,
354 http_conn->method ? http_conn->method : "GET",
355 http_conn->url,
356 http_conn->host);
357 if (sipe_strequal(http_conn->method, "POST")) {
358 g_string_append_printf(outstr, "Content-Length: %d\r\n",
359 http_conn->body ? (int)strlen(http_conn->body) : 0);
361 g_string_append_printf(outstr, "Content-Type: %s\r\n",
362 http_conn->content_type ? http_conn->content_type : "text/plain");
364 if (http_conn->http_session && http_conn->http_session->cookie) {
365 g_string_append_printf(outstr, "Cookie: %s\r\n", http_conn->http_session->cookie);
367 if (authorization) {
368 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
370 if (http_conn->additional_headers) {
371 g_string_append(outstr, http_conn->additional_headers);
374 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
376 sipe_utils_message_debug("HTTP", outstr->str, NULL, TRUE);
377 sipe_backend_transport_message(http_conn->conn, outstr->str);
378 g_string_free(outstr, TRUE);
381 void
382 http_conn_send( HttpConn *http_conn,
383 const char *method,
384 const char *full_url,
385 const char *body,
386 const char *content_type,
387 HttpConnCallback callback,
388 void *data)
390 if (!http_conn) {
391 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
392 return;
395 g_free(http_conn->method);
396 g_free(http_conn->url);
397 g_free(http_conn->body);
398 g_free(http_conn->content_type);
399 http_conn->method = g_strdup(method);
400 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
401 http_conn->body = g_strdup(body);
402 http_conn->content_type = g_strdup(content_type);
403 http_conn->callback = callback;
404 http_conn->data = data;
406 http_conn_send0(http_conn, NULL);
409 static void
410 http_conn_process_input_message(HttpConn *http_conn,
411 struct sipmsg *msg)
413 /* Redirect */
414 if ((msg->response == 300 ||
415 msg->response == 301 ||
416 msg->response == 302 ||
417 msg->response == 307) &&
418 http_conn->allow_redirect)
420 const char *location = sipmsg_find_header(msg, "Location");
421 gchar *host, *url;
422 guint port;
424 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location ? location : "");
425 http_conn_parse_url(location, &host, &port, &url);
427 if (host) {
428 http_conn->do_close = http_conn_clone(http_conn);
429 http_conn->sec_ctx = NULL;
431 g_free(http_conn->host);
432 g_free(http_conn->url);
434 http_conn->host = host;
435 http_conn->port = port;
436 http_conn->url = url;
438 http_conn->conn = http_conn_setup(http_conn,
439 http_conn->sipe_public,
440 http_conn->conn_type,
441 host,
442 port);
443 } else {
444 SIPE_DEBUG_ERROR_NOFORMAT("http_conn_process_input_message: no redirect host");
445 g_free(url);
446 return;
449 /* Authentication required */
450 else if (msg->response == 401) {
451 const gchar *auth_hdr = NULL;
452 guint auth_type;
453 const char *auth_name;
454 char *authorization;
455 char *output_toked_base64;
456 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
457 long ret = -1;
459 http_conn->retries++;
460 if (http_conn->retries > 2) {
461 if (http_conn->callback) {
462 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
464 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
465 http_conn_set_close(http_conn);
466 return;
469 #ifdef HAVE_SSPI
470 if (http_conn->auth && http_conn->auth->use_negotiate)
471 auth_hdr = sipmsg_find_auth_header(msg, "Negotiate");
472 if (auth_hdr) {
473 auth_type = AUTH_TYPE_NEGOTIATE;
474 auth_name = "Negotiate";
475 } else {
476 #endif
477 auth_hdr = sipmsg_find_auth_header(msg, "NTLM");
478 auth_type = AUTH_TYPE_NTLM;
479 auth_name = "NTLM";
480 #ifdef HAVE_SSPI
482 #endif
483 if (!auth_hdr) {
484 if (http_conn->callback) {
485 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
487 #ifdef HAVE_SSPI
488 #define AUTHSTRING "NTLM and Negotiate authentications are"
489 #else /* !HAVE_SSPI */
490 #define AUTHSTRING "NTLM authentication is"
491 #endif /* HAVE_SSPI */
492 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
493 AUTHSTRING
495 http_conn_set_close(http_conn);
496 return;
499 if (!http_conn->sec_ctx) {
500 http_conn->sec_ctx =
501 sip_sec_create_context(auth_type,
502 use_sso,
504 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
505 http_conn->auth ? http_conn->auth->user : NULL,
506 http_conn->auth ? http_conn->auth->password : NULL);
509 if (http_conn->sec_ctx) {
510 char **parts = g_strsplit(auth_hdr, " ", 0);
511 char *spn = g_strdup_printf("HTTP/%s", http_conn->host);
512 ret = sip_sec_init_context_step(http_conn->sec_ctx,
513 spn,
514 parts[1],
515 &output_toked_base64,
516 NULL);
517 g_free(spn);
518 g_strfreev(parts);
521 if (ret < 0) {
522 if (http_conn->callback) {
523 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
525 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
526 http_conn_set_close(http_conn);
527 return;
530 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
531 g_free(output_toked_base64);
533 http_conn_send0(http_conn, authorization);
534 g_free(authorization);
536 /* Other response */
537 else {
538 const char *set_cookie_hdr;
539 const char *content_type = sipmsg_find_header(msg, "Content-Type");
540 http_conn->retries = 0;
542 /* Set cookies.
543 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
545 if (http_conn->http_session && (set_cookie_hdr = sipmsg_find_header(msg, "Set-Cookie"))) {
546 char **parts;
547 char *tmp;
548 int i;
550 g_free(http_conn->http_session->cookie);
551 http_conn->http_session->cookie = NULL;
553 parts = g_strsplit(set_cookie_hdr, ";", 0);
554 for (i = 0; parts[i]; i++) {
555 if (!strstr(parts[i], "path=") &&
556 !strstr(parts[i], "domain=") &&
557 !strstr(parts[i], "expires=") &&
558 !strstr(parts[i], "secure"))
560 tmp = http_conn->http_session->cookie;
561 http_conn->http_session->cookie = !tmp ?
562 g_strdup(parts[i]) :
563 g_strconcat(http_conn->http_session->cookie, ";", parts[i], NULL);
564 g_free(tmp);
567 g_strfreev(parts);
568 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
569 http_conn->http_session->cookie ? http_conn->http_session->cookie : "");
572 if (http_conn->callback) {
573 (*http_conn->callback)(msg->response, msg->body, content_type, http_conn, http_conn->data);
578 static void http_conn_input(struct sipe_transport_connection *conn)
580 HttpConn *http_conn = HTTP_CONN;
581 char *cur = conn->buffer;
583 /* according to the RFC remove CRLF at the beginning */
584 while (*cur == '\r' || *cur == '\n') {
585 cur++;
587 if (cur != conn->buffer)
588 sipe_utils_shrink_buffer(conn, cur);
590 while ((cur = strstr(conn->buffer, "\r\n\r\n")) != NULL) {
591 struct sipmsg *msg;
592 guint remainder;
594 cur += 2;
595 cur[0] = '\0';
596 msg = sipmsg_parse_header(conn->buffer);
598 /* HTTP/1.1 Transfer-Encoding: chunked */
599 if (msg && (msg->bodylen == SIPMSG_BODYLEN_CHUNKED)) {
600 gchar *start = cur + 2;
601 GSList *chunks = NULL;
602 gboolean incomplete = TRUE;
604 msg->bodylen = 0;
605 while (strlen(start) > 0) {
606 gchar *tmp;
607 guint length = strtol(start, &tmp, 16);
608 struct _chunk {
609 guint length;
610 const gchar *start;
611 } *chunk;
613 /* Illegal number */
614 if ((length == 0) && (start == tmp))
615 break;
616 msg->bodylen += length;
618 /* Chunk header not finished yet */
619 tmp = strstr(tmp, "\r\n");
620 if (tmp == NULL)
621 break;
623 /* Chunk not finished yet */
624 tmp += 2;
625 remainder = conn->buffer_used - (tmp - conn->buffer);
626 if (remainder < length + 2)
627 break;
629 /* Next chunk */
630 start = tmp + length + 2;
632 /* Body completed */
633 if (length == 0) {
634 gchar *dummy = g_malloc(msg->bodylen + 1);
635 gchar *p = dummy;
636 GSList *entry = chunks;
638 while (entry) {
639 chunk = entry->data;
640 memcpy(p, chunk->start, chunk->length);
641 p += chunk->length;
642 entry = entry->next;
644 p[0] = '\0';
646 msg->body = dummy;
647 sipe_utils_message_debug("HTTP",
648 conn->buffer,
649 msg->body,
650 FALSE);
652 cur = start;
653 sipe_utils_shrink_buffer(conn, cur);
655 incomplete = FALSE;
656 break;
659 /* Append completed chunk */
660 chunk = g_new0(struct _chunk, 1);
661 chunk->length = length;
662 chunk->start = tmp;
663 chunks = g_slist_append(chunks, chunk);
666 if (chunks) {
667 GSList *entry = chunks;
668 while (entry) {
669 g_free(entry->data);
670 entry = entry->next;
672 g_slist_free(chunks);
675 if (incomplete) {
676 /* restore header for next try */
677 sipmsg_free(msg);
678 cur[0] = '\r';
679 return;
682 } else {
683 cur += 2;
684 remainder = conn->buffer_used - (cur - conn->buffer);
685 if (msg && remainder >= (guint) msg->bodylen) {
686 char *dummy = g_malloc(msg->bodylen + 1);
687 memcpy(dummy, cur, msg->bodylen);
688 dummy[msg->bodylen] = '\0';
689 msg->body = dummy;
690 cur += msg->bodylen;
691 sipe_utils_message_debug("HTTP",
692 conn->buffer,
693 msg->body,
694 FALSE);
695 sipe_utils_shrink_buffer(conn, cur);
696 } else {
697 if (msg){
698 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder, msg->bodylen, (int)strlen(conn->buffer));
699 sipmsg_free(msg);
702 /* restore header for next try */
703 cur[-2] = '\r';
704 return;
708 /* important to set before callback call */
709 if (sipe_strcase_equal(sipmsg_find_header(msg, "Connection"), "close")) {
710 http_conn->closed = TRUE;
713 http_conn_process_input_message(http_conn, msg);
715 sipmsg_free(msg);
718 if (http_conn->closed) {
719 http_conn_close(http_conn->do_close, "Server closed connection");
720 } else if (http_conn->do_close) {
721 http_conn_close(http_conn->do_close, "User initiated");
726 Local Variables:
727 mode: c
728 c-file-style: "bsd"
729 indent-tabs-mode: t
730 tab-width: 8
731 End: