http: warn if pending requests exist at shutdown
[siplcs.git] / src / core / sipe-http-request.c
blob8a688c2e67b7b6c1912ff9855517b0bd45d15db7
1 /**
2 * @file sipe-http-request.c
4 * pidgin-sipe
6 * Copyright (C) 2013-2018 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 request layer implementation
26 * - request handling: creation, parameters, deletion, cancelling
27 * - session handling: creation, closing
28 * - client authorization handling
29 * - connection request queue handling
30 * - compile HTTP header contents and hand-off to transport layer
31 * - process HTTP response and hand-off to user callback
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
38 #include <string.h>
40 #include <glib.h>
42 #include "sipe-common.h"
43 #include "sipmsg.h"
44 #include "sip-sec.h"
45 #include "sipe-backend.h"
46 #include "sipe-core.h"
47 #include "sipe-core-private.h"
48 #include "sipe-http.h"
50 #define _SIPE_HTTP_PRIVATE_IF_REQUEST
51 #include "sipe-http-request.h"
52 #define _SIPE_HTTP_PRIVATE_IF_TRANSPORT
53 #include "sipe-http-transport.h"
55 struct sipe_http_session {
56 GHashTable *cookie_jar;
59 struct sipe_http_request {
60 struct sipe_http_connection_public *connection;
62 struct sipe_http_session *session;
64 gchar *path;
65 gchar *headers;
66 gchar *body; /* NULL for GET */
67 gchar *content_type; /* NULL if body == NULL */
68 gchar *authorization;
70 const gchar *user; /* not copied */
71 const gchar *password; /* not copied */
73 sipe_http_response_callback *cb;
74 gpointer cb_data;
76 guint32 flags;
79 #define SIPE_HTTP_REQUEST_FLAG_FIRST 0x00000001
80 #define SIPE_HTTP_REQUEST_FLAG_REDIRECT 0x00000002
81 #define SIPE_HTTP_REQUEST_FLAG_AUTHDATA 0x00000004
82 #define SIPE_HTTP_REQUEST_FLAG_HANDSHAKE 0x00000008
84 static void sipe_http_request_free(struct sipe_core_private *sipe_private,
85 struct sipe_http_request *req,
86 guint status)
88 if (req->cb)
89 /* Callback: aborted/failed/cancelled */
90 (*req->cb)(sipe_private,
91 status,
92 NULL,
93 NULL,
94 req->cb_data);
95 g_free(req->path);
96 g_free(req->headers);
97 g_free(req->body);
98 g_free(req->content_type);
99 g_free(req->authorization);
100 g_free(req);
103 static void add_cookie_cb(SIPE_UNUSED_PARAMETER const gchar *key,
104 const gchar *cookie,
105 GString *string)
107 g_string_append_printf(string, "Cookie: %s\r\n", cookie);
110 static void sipe_http_request_send(struct sipe_http_connection_public *conn_public)
112 struct sipe_http_request *req = conn_public->pending_requests->data;
113 gchar *header;
114 gchar *content = NULL;
115 gchar *cookie = NULL;
117 if (req->body)
118 content = g_strdup_printf("Content-Length: %" G_GSIZE_FORMAT "\r\n"
119 "Content-Type: %s\r\n",
120 strlen(req->body),
121 req->content_type);
123 if (req->session && g_hash_table_size(req->session->cookie_jar)) {
124 GString *cookies = g_string_new("");
125 g_hash_table_foreach(req->session->cookie_jar,
126 (GHFunc) add_cookie_cb,
127 cookies);
128 cookie = g_string_free(cookies, FALSE);
131 header = g_strdup_printf("%s /%s HTTP/1.1\r\n"
132 "Host: %s\r\n"
133 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"
134 "%s%s%s%s",
135 content ? "POST" : "GET",
136 req->path,
137 conn_public->host,
138 conn_public->cached_authorization ? conn_public->cached_authorization :
139 req->authorization ? req->authorization : "",
140 req->headers ? req->headers : "",
141 cookie ? cookie : "",
142 content ? content : "");
143 g_free(cookie);
144 g_free(content);
146 /* only use authorization once */
147 g_free(req->authorization);
148 req->authorization = NULL;
150 sipe_http_transport_send(conn_public,
151 header,
152 req->body);
153 g_free(header);
156 gboolean sipe_http_request_pending(struct sipe_http_connection_public *conn_public)
158 return(conn_public->pending_requests != NULL);
161 void sipe_http_request_next(struct sipe_http_connection_public *conn_public)
163 sipe_http_request_send(conn_public);
166 static void sipe_http_request_enqueue(struct sipe_core_private *sipe_private,
167 struct sipe_http_request *req,
168 const struct sipe_http_parsed_uri *parsed_uri)
170 struct sipe_http_connection_public *conn_public;
172 req->path = g_strdup(parsed_uri->path);
173 req->connection = conn_public = sipe_http_transport_new(sipe_private,
174 parsed_uri->host,
175 parsed_uri->port,
176 parsed_uri->tls);
177 if (!sipe_http_request_pending(conn_public))
178 req->flags |= SIPE_HTTP_REQUEST_FLAG_FIRST;
180 conn_public->pending_requests = g_slist_append(conn_public->pending_requests,
181 req);
184 static void sipe_http_request_drop_context(struct sipe_http_connection_public *conn_public)
186 g_free(conn_public->cached_authorization);
187 conn_public->cached_authorization = NULL;
188 sip_sec_destroy_context(conn_public->context);
189 conn_public->context = NULL;
192 static void sipe_http_request_finalize_negotiate(struct sipe_http_request *req,
193 struct sipmsg *msg)
195 #if defined(HAVE_GSSAPI_GSSAPI_H) || defined(HAVE_SSPI)
197 * Negotiate can send a final package in the successful response.
198 * We need to forward this to the context or otherwise it will
199 * never reach the ready state.
201 struct sipe_http_connection_public *conn_public = req->connection;
203 if (sip_sec_context_type(conn_public->context) == SIPE_AUTHENTICATION_TYPE_NEGOTIATE) {
204 const gchar *header = sipmsg_find_auth_header(msg, "Negotiate");
206 if (header) {
207 gchar **parts = g_strsplit(header, " ", 0);
208 gchar *spn = g_strdup_printf("HTTP/%s", conn_public->host);
209 gchar *token;
211 SIPE_DEBUG_INFO("sipe_http_request_finalize_negotiate: init context target '%s' token '%s'",
212 spn, parts[1] ? parts[1] : "<NULL>");
214 if (sip_sec_init_context_step(conn_public->context,
215 spn,
216 parts[1],
217 &token,
218 NULL)) {
219 g_free(token);
220 } else {
221 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_finalize_negotiate: security context init step failed, throwing away context");
222 sipe_http_request_drop_context(conn_public);
225 g_free(spn);
226 g_strfreev(parts);
229 #else
230 (void) req; /* keep compiler happy */
231 (void) msg; /* keep compiler happy */
232 #endif
236 /* TRUE indicates failure */
237 static gboolean sipe_http_request_response_redirection(struct sipe_core_private *sipe_private,
238 struct sipe_http_request *req,
239 struct sipmsg *msg)
241 const gchar *location = sipmsg_find_header(msg, "Location");
242 gboolean failed = TRUE;
244 sipe_http_request_finalize_negotiate(req, msg);
246 if (location) {
247 struct sipe_http_parsed_uri *parsed_uri = sipe_http_parse_uri(location);
249 if (parsed_uri) {
250 /* remove request from old connection */
251 struct sipe_http_connection_public *conn_public = req->connection;
252 conn_public->pending_requests = g_slist_remove(conn_public->pending_requests,
253 req);
255 /* free old request data */
256 g_free(req->path);
257 req->flags &= ~( SIPE_HTTP_REQUEST_FLAG_FIRST |
258 SIPE_HTTP_REQUEST_FLAG_HANDSHAKE );
260 /* resubmit request on other connection */
261 sipe_http_request_enqueue(sipe_private, req, parsed_uri);
262 failed = FALSE;
264 sipe_http_parsed_uri_free(parsed_uri);
265 } else
266 SIPE_DEBUG_INFO("sipe_http_request_response_redirection: invalid redirection to '%s'",
267 location);
268 } else
269 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_redirection: no URL found?!?");
271 return(failed);
274 /* TRUE indicates failure */
275 static gboolean sipe_http_request_response_unauthorized(struct sipe_core_private *sipe_private,
276 struct sipe_http_request *req,
277 struct sipmsg *msg)
279 struct sipe_http_connection_public *conn_public = req->connection;
280 const gchar *header = NULL;
281 guint type;
282 gboolean failed = TRUE;
285 * There are some buggy HTTP servers out there that add superfluous
286 * WWW-Authenticate: headers during the authentication handshake.
287 * Look only for the header of the active security context.
289 if (conn_public->context) {
290 const gchar *name = sip_sec_context_name(conn_public->context);
292 header = sipmsg_find_auth_header(msg, name);
293 type = sip_sec_context_type(conn_public->context);
295 if (!header) {
296 SIPE_DEBUG_INFO("sipe_http_request_response_unauthorized: expected authentication scheme %s not found",
297 name);
298 return(failed);
301 if (conn_public->cached_authorization) {
303 * The "Basic" scheme doesn't have any state.
305 * If we enter here then we have already tried "Basic"
306 * authentication once for this request and it was
307 * rejected by the server. As all future requests will
308 * also be rejected, we need to abort here in order to
309 * prevent an endless request/401/request/... loop.
311 SIPE_DEBUG_INFO("sipe_http_request_response_unauthorized: Basic authentication has failed for host '%s', please check user name and password!",
312 conn_public->host);
313 return(failed);
316 } else {
317 #if defined(HAVE_GSSAPI_GSSAPI_H) || defined(HAVE_SSPI)
318 #define DEBUG_STRING ", NTLM and Negotiate"
319 /* Use "Negotiate" unless the user requested "NTLM" */
320 if (sipe_private->authentication_type != SIPE_AUTHENTICATION_TYPE_NTLM)
321 header = sipmsg_find_auth_header(msg, "Negotiate");
322 if (header) {
323 type = SIPE_AUTHENTICATION_TYPE_NEGOTIATE;
324 } else
325 #else
326 #define DEBUG_STRING " and NTLM"
327 (void) sipe_private; /* keep compiler happy */
328 #endif
330 header = sipmsg_find_auth_header(msg, "NTLM");
331 type = SIPE_AUTHENTICATION_TYPE_NTLM;
334 /* only fall back to "Basic" after everything else fails */
335 if (!header) {
336 header = sipmsg_find_auth_header(msg, "Basic");
337 type = SIPE_AUTHENTICATION_TYPE_BASIC;
341 if (header) {
342 if (!conn_public->context) {
343 gboolean valid = req->flags & SIPE_HTTP_REQUEST_FLAG_AUTHDATA;
344 conn_public->context = sip_sec_create_context(type,
345 !valid, /* Single Sign-On flag */
346 TRUE, /* connection-based for HTTP */
347 valid ? req->user : NULL,
348 valid ? req->password : NULL);
351 if (conn_public->context) {
352 gchar **parts = g_strsplit(header, " ", 0);
353 gchar *spn = g_strdup_printf("HTTP/%s", conn_public->host);
354 gchar *token_out;
355 const gchar *token_in = parts[1];
357 SIPE_DEBUG_INFO("sipe_http_request_response_unauthorized: init context target '%s' token '%s'",
358 spn, token_in ? token_in : "<NULL>");
361 * If we receive a NULL token during the handshake
362 * then the authentication scheme has failed.
364 if ((req->flags & SIPE_HTTP_REQUEST_FLAG_HANDSHAKE) &&
365 !token_in) {
366 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_unauthorized: authentication failed, throwing away context");
367 sipe_http_request_drop_context(conn_public);
369 } else if (sip_sec_init_context_step(conn_public->context,
370 spn,
371 token_in,
372 &token_out,
373 NULL)) {
375 /* handshake has started */
376 req->flags |= SIPE_HTTP_REQUEST_FLAG_HANDSHAKE;
378 /* generate authorization header */
379 req->authorization = g_strdup_printf("Authorization: %s %s\r\n",
380 sip_sec_context_name(conn_public->context),
381 token_out ? token_out : "");
382 g_free(token_out);
385 * authorization never changes for Basic
386 * authentication scheme, so we can keep it.
388 if (type == SIPE_AUTHENTICATION_TYPE_BASIC) {
389 g_free(conn_public->cached_authorization);
390 conn_public->cached_authorization = g_strdup(req->authorization);
394 * Keep the request in the queue. As it is at
395 * the head it will be pulled automatically
396 * by the transport layer after returning.
398 failed = FALSE;
400 } else {
401 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_unauthorized: security context init step failed, throwing away context");
402 sipe_http_request_drop_context(conn_public);
405 g_free(spn);
406 g_strfreev(parts);
407 } else
408 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_unauthorized: security context creation failed");
409 } else
410 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_unauthorized: only Basic" DEBUG_STRING " authentication schemes are supported");
412 return(failed);
415 static void sipe_http_request_response_callback(struct sipe_core_private *sipe_private,
416 struct sipe_http_request *req,
417 struct sipmsg *msg)
419 sipe_http_request_finalize_negotiate(req, msg);
421 /* Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net */
422 if (req->session) {
423 guint instance = 0;
424 const gchar *hdr;
426 /* extract all cookies from header */
427 while ((hdr = sipmsg_find_header_instance(msg,
428 "Set-Cookie",
429 instance++)) != NULL) {
430 gchar **parts, **current;
431 const gchar *part;
432 gchar *new = NULL;
434 current = parts = g_strsplit(hdr, ";", 0);
435 while ((part = *current++) != NULL) {
436 /* strip these parts from cookie */
437 if (!(strstr(part, "path=") ||
438 strstr(part, "domain=") ||
439 strstr(part, "expires=") ||
440 strstr(part, "secure"))) {
441 gchar *tmp = new;
442 new = new ?
443 g_strconcat(new, ";", part, NULL) :
444 g_strdup(part);
445 g_free(tmp);
449 if (new) {
450 g_hash_table_insert(req->session->cookie_jar,
451 g_strdup(*parts),
452 new);
453 SIPE_DEBUG_INFO("sipe_http_request_response_callback: cookie: %s", new);
455 g_strfreev(parts);
459 /* Callback: success */
460 (*req->cb)(sipe_private,
461 msg->response,
462 msg->headers,
463 msg->body,
464 req->cb_data);
466 /* remove completed request */
467 sipe_http_request_cancel(req);
470 void sipe_http_request_response(struct sipe_http_connection_public *conn_public,
471 struct sipmsg *msg)
473 struct sipe_core_private *sipe_private = conn_public->sipe_private;
474 struct sipe_http_request *req = conn_public->pending_requests->data;
475 gboolean failed;
477 if ((req->flags & SIPE_HTTP_REQUEST_FLAG_REDIRECT) &&
478 (msg->response >= SIPE_HTTP_STATUS_REDIRECTION) &&
479 (msg->response < SIPE_HTTP_STATUS_CLIENT_ERROR)) {
480 failed = sipe_http_request_response_redirection(sipe_private,
481 req,
482 msg);
484 } else if (msg->response == SIPE_HTTP_STATUS_CLIENT_UNAUTHORIZED) {
485 failed = sipe_http_request_response_unauthorized(sipe_private,
486 req,
487 msg);
489 } else {
490 /* On some errors throw away the security context */
491 if (((msg->response == SIPE_HTTP_STATUS_CLIENT_FORBIDDEN) ||
492 (msg->response == SIPE_HTTP_STATUS_CLIENT_PROXY_AUTH) ||
493 (msg->response >= SIPE_HTTP_STATUS_SERVER_ERROR)) &&
494 conn_public->context) {
495 SIPE_DEBUG_INFO("sipe_http_request_response: response was %d, throwing away security context",
496 msg->response);
497 sipe_http_request_drop_context(conn_public);
500 /* All other cases are passed on to the user */
501 sipe_http_request_response_callback(sipe_private, req, msg);
503 /* req is no longer valid */
504 failed = FALSE;
507 if (failed) {
508 /* Callback: request failed */
509 (*req->cb)(sipe_private,
510 SIPE_HTTP_STATUS_FAILED,
511 msg->headers,
512 NULL,
513 req->cb_data);
515 /* remove failed request */
516 sipe_http_request_cancel(req);
520 void sipe_http_request_shutdown(struct sipe_http_connection_public *conn_public,
521 gboolean abort)
523 if (conn_public->pending_requests) {
524 GSList *entry = conn_public->pending_requests;
525 guint status = abort ?
526 SIPE_HTTP_STATUS_ABORTED :
527 SIPE_HTTP_STATUS_FAILED;
528 gboolean warn = conn_public->connected && !abort;
529 while (entry) {
530 struct sipe_http_request *req = entry->data;
532 if (warn) {
533 SIPE_DEBUG_ERROR("sipe_http_request_shutdown: pending request at shutdown: could indicate missing _ready() call on request. Debugging information:\n"
534 "Host: %s\n"
535 "Port: %d\n"
536 "Path: %s\n"
537 "Method: %s\n",
538 conn_public->host,
539 conn_public->port,
540 req->path,
541 req->body ? "POST" : "GET");
544 sipe_http_request_free(conn_public->sipe_private,
545 req,
546 status);
547 entry = entry->next;
549 g_slist_free(conn_public->pending_requests);
550 conn_public->pending_requests = NULL;
553 if (conn_public->context) {
554 g_free(conn_public->cached_authorization);
555 conn_public->cached_authorization = NULL;
556 sip_sec_destroy_context(conn_public->context);
557 conn_public->context = NULL;
561 struct sipe_http_request *sipe_http_request_new(struct sipe_core_private *sipe_private,
562 const struct sipe_http_parsed_uri *parsed_uri,
563 const gchar *headers,
564 const gchar *body,
565 const gchar *content_type,
566 sipe_http_response_callback *callback,
567 gpointer callback_data)
569 struct sipe_http_request *req;
570 if (!parsed_uri)
571 return(NULL);
572 if (sipe_http_shutting_down(sipe_private)) {
573 SIPE_DEBUG_ERROR("sipe_http_request_new: new HTTP request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
574 "Host: %s\n"
575 "Port: %d\n"
576 "Path: %s\n"
577 "Headers: %s\n"
578 "Body: %s\n",
579 parsed_uri->host,
580 parsed_uri->port,
581 parsed_uri->path,
582 headers ? headers : "<NONE>",
583 body ? body : "<EMPTY>");
584 return(NULL);
587 req = g_new0(struct sipe_http_request, 1);
588 req->flags = 0;
589 req->cb = callback;
590 req->cb_data = callback_data;
591 if (headers)
592 req->headers = g_strdup(headers);
593 if (body) {
594 req->body = g_strdup(body);
595 req->content_type = g_strdup(content_type);
598 /* default authentication */
599 if (!SIPE_CORE_PRIVATE_FLAG_IS(SSO))
600 sipe_http_request_authentication(req,
601 sipe_private->authuser,
602 sipe_private->password);
604 sipe_http_request_enqueue(sipe_private, req, parsed_uri);
606 return(req);
609 void sipe_http_request_ready(struct sipe_http_request *request)
611 struct sipe_http_connection_public *conn_public = request->connection;
613 /* pass first request on already opened connection through directly */
614 if ((request->flags & SIPE_HTTP_REQUEST_FLAG_FIRST) &&
615 conn_public->connected)
616 sipe_http_request_send(conn_public);
619 struct sipe_http_session *sipe_http_session_start(void)
621 struct sipe_http_session *session = g_new0(struct sipe_http_session, 1);
622 session->cookie_jar = g_hash_table_new_full(g_str_hash,
623 g_str_equal,
624 g_free,
625 g_free);
626 return(session);
629 void sipe_http_session_close(struct sipe_http_session *session)
631 if (session) {
632 g_hash_table_destroy(session->cookie_jar);
633 g_free(session);
637 void sipe_http_request_cancel(struct sipe_http_request *request)
639 struct sipe_http_connection_public *conn_public = request->connection;
640 conn_public->pending_requests = g_slist_remove(conn_public->pending_requests,
641 request);
643 /* cancelled by requester, don't use callback */
644 request->cb = NULL;
646 sipe_http_request_free(conn_public->sipe_private,
647 request,
648 SIPE_HTTP_STATUS_CANCELLED);
651 void sipe_http_request_session(struct sipe_http_request *request,
652 struct sipe_http_session *session)
654 request->session = session;
657 void sipe_http_request_allow_redirect(struct sipe_http_request *request)
659 request->flags |= SIPE_HTTP_REQUEST_FLAG_REDIRECT;
662 void sipe_http_request_authentication(struct sipe_http_request *request,
663 const gchar *user,
664 const gchar *password)
666 request->flags |= SIPE_HTTP_REQUEST_FLAG_AUTHDATA;
667 request->user = user;
668 request->password = password;
672 Local Variables:
673 mode: c
674 c-file-style: "bsd"
675 indent-tabs-mode: t
676 tab-width: 8
677 End: