http: implement 3xx response handling
[siplcs.git] / src / core / http-conn.c
blobe5865017ce1f813f87af092b76d8d257d37b1757
1 /**
2 * @file http-conn.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2013 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 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->additional_headers = g_strdup(http_conn->additional_headers);
108 res->auth = http_conn->auth;
109 res->callback = http_conn->callback;
110 res->data = http_conn->data;
112 res->conn = http_conn->conn;
113 res->sec_ctx = http_conn->sec_ctx;
114 res->retries = http_conn->retries;
116 res->do_close = NULL;
118 return res;
121 void
122 http_conn_free(HttpConn* http_conn)
124 if (!http_conn) return;
126 /* make sure also pending connections are released */
127 sipe_backend_transport_disconnect(http_conn->conn);
129 /* don't free "http_conn->http_session" - client should do */
130 g_free(http_conn->method);
131 g_free(http_conn->host);
132 g_free(http_conn->url);
133 g_free(http_conn->body);
134 g_free(http_conn->content_type);
135 g_free(http_conn->additional_headers);
137 if (http_conn->sec_ctx) {
138 sip_sec_destroy_context(http_conn->sec_ctx);
141 g_free(http_conn);
144 gboolean
145 http_conn_is_closed(HttpConn *http_conn)
147 return http_conn->closed;
150 HttpSession *
151 http_conn_session_create()
153 HttpSession *res = g_new0(HttpSession, 1);
154 return res;
157 void
158 http_conn_session_free(HttpSession *http_session)
160 if (!http_session) return;
162 g_free(http_session->cookie);
163 g_free(http_session);
166 void
167 http_conn_set_close(HttpConn* http_conn)
169 http_conn->do_close = http_conn;
172 static void
173 http_conn_close(HttpConn *http_conn, const char *message)
175 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message ? message : "");
176 http_conn_free(http_conn);
180 * Extracts host, port and relative url
181 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
183 * Allocates memory, must be g_free'd.
185 static void
186 http_conn_parse_url(const char *url,
187 char **host,
188 guint *port,
189 char **rel_url)
191 char **parts = g_strsplit(url, "://", 2);
192 char *no_proto;
193 guint port_tmp;
194 char *tmp;
195 char *host_port;
197 /* Make sure we always return valid information */
198 if (host) *host = NULL;
199 if (rel_url) *rel_url = NULL;
201 if(!parts) {
202 return;
203 } else if(!parts[0]) {
204 g_strfreev(parts);
205 return;
208 no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
209 port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
210 g_strfreev(parts);
212 if(!no_proto) {
213 return;
216 tmp = strstr(no_proto, "/");
217 if (tmp && rel_url) *rel_url = g_strdup(tmp);
218 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
219 g_free(no_proto);
221 if(!host_port) {
222 return;
225 parts = g_strsplit(host_port, ":", 2);
227 if(parts) {
228 if (host) *host = g_strdup(parts[0]);
229 if(parts[0]) {
230 port_tmp = parts[1] ? (guint) atoi(parts[1]) : port_tmp;
232 if (port) *port = port_tmp;
233 g_strfreev(parts);
236 g_free(host_port);
239 static void http_conn_error(struct sipe_transport_connection *conn,
240 const gchar *msg)
242 HttpConn *http_conn = HTTP_CONN;
243 if (http_conn->callback) {
244 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, NULL, http_conn, http_conn->data);
246 http_conn_close(http_conn, msg);
249 static void http_conn_send0(HttpConn *http_conn,
250 const char *authorization);
251 static void http_conn_connected(struct sipe_transport_connection *conn)
253 http_conn_send0(HTTP_CONN, NULL);
256 static void http_conn_input(struct sipe_transport_connection *conn);
257 static struct sipe_transport_connection *http_conn_setup(HttpConn *http_conn,
258 struct sipe_core_public *sipe_public,
259 guint type,
260 const gchar *host,
261 guint port) {
262 sipe_connect_setup setup = {
263 type,
264 host,
265 port,
266 http_conn,
267 http_conn_connected,
268 http_conn_input,
269 http_conn_error
272 if (!host) {
273 http_conn_close(http_conn, "Missing host");
274 return NULL;
277 return(sipe_backend_transport_connect(sipe_public, &setup));
280 HttpConn *
281 http_conn_create(struct sipe_core_public *sipe_public,
282 HttpSession *http_session,
283 const char *method,
284 guint conn_type,
285 gboolean allow_redirect,
286 const char *full_url,
287 const char *body,
288 const char *content_type,
289 const gchar *additional_headers,
290 HttpConnAuth *auth,
291 HttpConnCallback callback,
292 void *data)
294 HttpConn *http_conn;
295 struct sipe_transport_connection *conn;
296 gchar *host, *url;
297 guint port;
299 if (!full_url || (strlen(full_url) == 0)) {
300 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
301 return NULL;
304 http_conn_parse_url(full_url, &host, &port, &url);
305 http_conn = g_new0(HttpConn, 1);
306 conn = http_conn_setup(http_conn, sipe_public, conn_type, host, port);
307 if (!conn) {
308 // http_conn_setup deallocates http_conn on error, don't free here
309 g_free(host);
310 g_free(url);
311 return NULL;
314 http_conn->sipe_public = sipe_public;
315 conn->user_data = http_conn;
317 http_conn->http_session = http_session;
318 http_conn->method = g_strdup(method);
319 http_conn->conn_type = conn_type;
320 http_conn->allow_redirect = allow_redirect;
321 http_conn->host = host;
322 http_conn->port = port;
323 http_conn->url = url;
324 http_conn->body = g_strdup(body);
325 http_conn->content_type = g_strdup(content_type);
326 http_conn->additional_headers = g_strdup(additional_headers);
327 http_conn->auth = auth;
328 http_conn->callback = callback;
329 http_conn->data = data;
330 http_conn->conn = conn;
332 return http_conn;
335 /* Data part */
336 static void
337 http_conn_send0(HttpConn *http_conn,
338 const char *authorization)
340 GString *outstr;
342 if (!http_conn->host || !http_conn->url) return;
344 outstr = g_string_new("");
345 g_string_append_printf(outstr, HTTP_CONN_HEADER,
346 http_conn->method ? http_conn->method : "GET",
347 http_conn->url,
348 http_conn->host);
349 if (sipe_strequal(http_conn->method, "POST")) {
350 g_string_append_printf(outstr, "Content-Length: %d\r\n",
351 http_conn->body ? (int)strlen(http_conn->body) : 0);
353 g_string_append_printf(outstr, "Content-Type: %s\r\n",
354 http_conn->content_type ? http_conn->content_type : "text/plain");
356 if (http_conn->http_session && http_conn->http_session->cookie) {
357 g_string_append_printf(outstr, "Cookie: %s\r\n", http_conn->http_session->cookie);
359 if (authorization) {
360 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
362 if (http_conn->additional_headers) {
363 g_string_append(outstr, http_conn->additional_headers);
366 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
368 sipe_utils_message_debug("HTTP", outstr->str, NULL, TRUE);
369 sipe_backend_transport_message(http_conn->conn, outstr->str);
370 g_string_free(outstr, TRUE);
373 void
374 http_conn_send( HttpConn *http_conn,
375 const char *method,
376 const char *full_url,
377 const char *body,
378 const char *content_type,
379 HttpConnCallback callback,
380 void *data)
382 if (!http_conn) {
383 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
384 return;
387 g_free(http_conn->method);
388 g_free(http_conn->url);
389 g_free(http_conn->body);
390 g_free(http_conn->content_type);
391 http_conn->method = g_strdup(method);
392 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
393 http_conn->body = g_strdup(body);
394 http_conn->content_type = g_strdup(content_type);
395 http_conn->callback = callback;
396 http_conn->data = data;
398 http_conn_send0(http_conn, NULL);
401 static void
402 http_conn_process_input_message(HttpConn *http_conn,
403 struct sipmsg *msg)
405 /* Redirect */
406 if ((msg->response == 300 ||
407 msg->response == 301 ||
408 msg->response == 302 ||
409 msg->response == 307) &&
410 http_conn->allow_redirect)
412 const char *location = sipmsg_find_header(msg, "Location");
413 gchar *host, *url;
414 guint port;
416 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location ? location : "");
417 http_conn_parse_url(location, &host, &port, &url);
419 if (host) {
420 http_conn->do_close = http_conn_clone(http_conn);
421 http_conn->sec_ctx = NULL;
423 g_free(http_conn->host);
424 g_free(http_conn->url);
426 http_conn->host = host;
427 http_conn->port = port;
428 http_conn->url = url;
430 http_conn->conn = http_conn_setup(http_conn,
431 http_conn->sipe_public,
432 http_conn->conn_type,
433 host,
434 port);
435 } else {
436 SIPE_DEBUG_ERROR_NOFORMAT("http_conn_process_input_message: no redirect host");
437 g_free(url);
438 return;
441 /* Authentication required */
442 else if (msg->response == 401) {
443 const gchar *auth_hdr = NULL;
444 guint auth_type;
445 const char *auth_name;
446 char *authorization;
447 char *output_toked_base64;
448 HttpConnAuth *auth = http_conn->auth;
449 gboolean ret = FALSE;
451 http_conn->retries++;
452 if (http_conn->retries > 2) {
453 if (http_conn->callback) {
454 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
456 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
457 http_conn_set_close(http_conn);
458 return;
461 #if defined(HAVE_LIBKRB5) || defined(HAVE_SSPI)
462 #define AUTHSTRING "NTLM and Negotiate authentications are"
464 struct sipe_core_public *sipe_public = http_conn->sipe_public;
466 /* Use "Negotiate" unless the user requested "NTLM" */
467 if (SIPE_CORE_PRIVATE->authentication_type != SIPE_AUTHENTICATION_TYPE_NTLM)
468 auth_hdr = sipmsg_find_auth_header(msg, "Negotiate");
470 if (auth_hdr) {
471 auth_type = SIPE_AUTHENTICATION_TYPE_NEGOTIATE;
472 auth_name = "Negotiate";
473 } else
474 #else
475 #define AUTHSTRING "NTLM authentication is"
476 #endif
478 auth_hdr = sipmsg_find_auth_header(msg, "NTLM");
479 auth_type = SIPE_AUTHENTICATION_TYPE_NTLM;
480 auth_name = "NTLM";
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 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Only " AUTHSTRING " supported at the moment, exiting");
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 auth == NULL, /* Single Sign-On flag */
496 TRUE, /* connection-based for HTTP */
497 auth ? auth->domain : NULL,
498 auth ? auth->user : NULL,
499 auth ? 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 SIPE_DEBUG_INFO("http_conn_process_input_message: init context target '%s' token '%s'",
506 spn, parts[1] ? parts[1] : "<NULL>");
507 ret = sip_sec_init_context_step(http_conn->sec_ctx,
508 spn,
509 parts[1],
510 &output_toked_base64,
511 NULL);
512 g_free(spn);
513 g_strfreev(parts);
516 if (!ret) {
517 if (http_conn->callback) {
518 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, NULL, http_conn, http_conn->data);
520 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
521 http_conn_set_close(http_conn);
522 return;
525 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
526 g_free(output_toked_base64);
528 http_conn_send0(http_conn, authorization);
529 g_free(authorization);
531 /* Other response */
532 else {
533 const char *set_cookie_hdr;
534 http_conn->retries = 0;
536 /* Set cookies.
537 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
539 if (http_conn->http_session && (set_cookie_hdr = sipmsg_find_header(msg, "Set-Cookie"))) {
540 char **parts;
541 char *tmp;
542 int i;
544 g_free(http_conn->http_session->cookie);
545 http_conn->http_session->cookie = NULL;
547 parts = g_strsplit(set_cookie_hdr, ";", 0);
548 for (i = 0; parts[i]; i++) {
549 if (!strstr(parts[i], "path=") &&
550 !strstr(parts[i], "domain=") &&
551 !strstr(parts[i], "expires=") &&
552 !strstr(parts[i], "secure"))
554 tmp = http_conn->http_session->cookie;
555 http_conn->http_session->cookie = !tmp ?
556 g_strdup(parts[i]) :
557 g_strconcat(http_conn->http_session->cookie, ";", parts[i], NULL);
558 g_free(tmp);
561 g_strfreev(parts);
562 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
563 http_conn->http_session->cookie ? http_conn->http_session->cookie : "");
566 if (http_conn->callback) {
567 (*http_conn->callback)(msg->response, msg->body, msg->headers, http_conn, http_conn->data);
572 static void http_conn_input(struct sipe_transport_connection *conn)
574 HttpConn *http_conn = HTTP_CONN;
575 char *cur = conn->buffer;
577 /* according to the RFC remove CRLF at the beginning */
578 while (*cur == '\r' || *cur == '\n') {
579 cur++;
581 if (cur != conn->buffer)
582 sipe_utils_shrink_buffer(conn, cur);
584 /* there can only be one response in the buffer at one time */
585 if ((cur = strstr(conn->buffer, "\r\n\r\n")) != NULL) {
586 struct sipmsg *msg;
587 guint remainder;
589 cur += 2;
590 cur[0] = '\0';
591 msg = sipmsg_parse_header(conn->buffer);
593 /* HTTP/1.1 Transfer-Encoding: chunked */
594 if (msg && (msg->bodylen == SIPMSG_BODYLEN_CHUNKED)) {
595 gchar *start = cur + 2;
596 GSList *chunks = NULL;
597 gboolean incomplete = TRUE;
599 msg->bodylen = 0;
600 while (strlen(start) > 0) {
601 gchar *tmp;
602 guint length = strtol(start, &tmp, 16);
603 struct _chunk {
604 guint length;
605 const gchar *start;
606 } *chunk;
608 /* Illegal number */
609 if ((length == 0) && (start == tmp))
610 break;
611 msg->bodylen += length;
613 /* Chunk header not finished yet */
614 tmp = strstr(tmp, "\r\n");
615 if (tmp == NULL)
616 break;
618 /* Chunk not finished yet */
619 tmp += 2;
620 remainder = conn->buffer_used - (tmp - conn->buffer);
621 if (remainder < length + 2)
622 break;
624 /* Next chunk */
625 start = tmp + length + 2;
627 /* Body completed */
628 if (length == 0) {
629 gchar *dummy = g_malloc(msg->bodylen + 1);
630 gchar *p = dummy;
631 GSList *entry = chunks;
633 while (entry) {
634 chunk = entry->data;
635 memcpy(p, chunk->start, chunk->length);
636 p += chunk->length;
637 entry = entry->next;
639 p[0] = '\0';
641 msg->body = dummy;
642 sipe_utils_message_debug("HTTP",
643 conn->buffer,
644 msg->body,
645 FALSE);
647 cur = start;
648 sipe_utils_shrink_buffer(conn, cur);
650 incomplete = FALSE;
651 break;
654 /* Append completed chunk */
655 chunk = g_new0(struct _chunk, 1);
656 chunk->length = length;
657 chunk->start = tmp;
658 chunks = g_slist_append(chunks, chunk);
661 if (chunks) {
662 GSList *entry = chunks;
663 while (entry) {
664 g_free(entry->data);
665 entry = entry->next;
667 g_slist_free(chunks);
670 if (incomplete) {
671 /* restore header for next try */
672 sipmsg_free(msg);
673 cur[0] = '\r';
674 return;
677 } else {
678 cur += 2;
679 remainder = conn->buffer_used - (cur - conn->buffer);
680 if (msg && remainder >= (guint) msg->bodylen) {
681 char *dummy = g_malloc(msg->bodylen + 1);
682 memcpy(dummy, cur, msg->bodylen);
683 dummy[msg->bodylen] = '\0';
684 msg->body = dummy;
685 cur += msg->bodylen;
686 sipe_utils_message_debug("HTTP",
687 conn->buffer,
688 msg->body,
689 FALSE);
690 sipe_utils_shrink_buffer(conn, cur);
691 } else {
692 if (msg){
693 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder, msg->bodylen, (int)strlen(conn->buffer));
694 sipmsg_free(msg);
697 /* restore header for next try */
698 cur[-2] = '\r';
699 return;
703 /* important to set before callback call */
704 if (sipe_strcase_equal(sipmsg_find_header(msg, "Connection"), "close")) {
705 http_conn->closed = TRUE;
708 http_conn_process_input_message(http_conn, msg);
710 sipmsg_free(msg);
713 if (http_conn->closed) {
714 gboolean closing_clone = http_conn != http_conn->do_close;
715 http_conn_close(http_conn->do_close, "Server closed connection");
716 if (closing_clone)
717 http_conn->do_close = NULL;
718 /* http_conn is invalid if closing_clone == FALSE */
719 } else if (http_conn->do_close) {
720 /* user initiated: http_conn == http_conn->do_close */
721 http_conn_close(http_conn->do_close, "User initiated");
722 /* http_conn is invalid */
727 Local Variables:
728 mode: c
729 c-file-style: "bsd"
730 indent-tabs-mode: t
731 tab-width: 8
732 End: