http: implement 401 response handling
[siplcs.git] / src / core / sipe-http-request.c
blob6cd097a20e8b716b00874a354220e61d47da9dc4
1 /**
2 * @file sipe-http-request.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 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 "sipmsg.h"
43 #include "sip-sec.h"
44 #include "sipe-backend.h"
45 #include "sipe-core.h"
46 #include "sipe-core-private.h"
47 #include "sipe-http.h"
49 #define _SIPE_HTTP_PRIVATE_IF_REQUEST
50 #include "sipe-http-request.h"
51 #define _SIPE_HTTP_PRIVATE_IF_TRANSPORT
52 #include "sipe-http-transport.h"
54 struct sipe_http_session {
55 gchar *cookie; /* extremely simplistic cookie jar :-) */
58 struct sipe_http_request {
59 struct sipe_http_connection_public *connection;
61 struct sipe_http_session *session;
63 gchar *path;
64 gchar *headers;
65 gchar *body; /* NULL for GET */
66 gchar *content_type; /* NULL if body == NULL */
67 gchar *authorization;
69 const gchar *domain; /* not copied */
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
83 static void sipe_http_request_free(struct sipe_core_private *sipe_private,
84 struct sipe_http_request *req)
86 if (req->cb)
87 /* Callback: aborted */
88 (*req->cb)(sipe_private, 0, NULL, NULL, req->cb_data);
89 g_free(req->path);
90 g_free(req->headers);
91 g_free(req->body);
92 g_free(req->content_type);
93 g_free(req->authorization);
94 g_free(req);
97 static void sipe_http_request_send(struct sipe_http_connection_public *conn_public)
99 struct sipe_http_request *req = conn_public->pending_requests->data;
100 gchar *header;
101 gchar *content = NULL;
102 gchar *cookie = NULL;
104 if (req->body)
105 content = g_strdup_printf("Content-Length: %" G_GSIZE_FORMAT "\r\n"
106 "Content-Type: %s\r\n",
107 strlen(req->body),
108 req->content_type);
110 if (req->session && req->session->cookie)
111 cookie = g_strdup_printf("Cookie: %s\r\n", req->session->cookie);
113 header = g_strdup_printf("%s /%s HTTP/1.1\r\n"
114 "Host: %s\r\n"
115 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"
116 "%s%s%s%s",
117 content ? "POST" : "GET",
118 req->path,
119 conn_public->host,
120 req->authorization ? req->authorization : "",
121 req->headers ? req->headers : "",
122 cookie ? cookie : "",
123 content ? content : "");
124 g_free(cookie);
125 g_free(content);
127 /* only use authorization once */
128 g_free(req->authorization);
129 req->authorization = NULL;
131 sipe_http_transport_send(conn_public,
132 header,
133 req->body);
134 g_free(header);
137 gboolean sipe_http_request_pending(struct sipe_http_connection_public *conn_public)
139 return(conn_public->pending_requests != NULL);
142 void sipe_http_request_next(struct sipe_http_connection_public *conn_public)
144 sipe_http_request_send(conn_public);
147 static void sipe_http_request_response_unauthorized(struct sipe_core_private *sipe_private,
148 struct sipe_http_request *req,
149 struct sipmsg *msg)
151 const gchar *header = NULL;
152 const gchar *name;
153 guint type;
154 gboolean failed = TRUE;
156 #if defined(HAVE_LIBKRB5) || defined(HAVE_SSPI)
157 #define DEBUG_STRING "NTLM and Negotiate authentications are"
158 /* Use "Negotiate" unless the user requested "NTLM" */
159 if (sipe_private->authentication_type != SIPE_AUTHENTICATION_TYPE_NTLM)
160 header = sipmsg_find_auth_header(msg, "Negotiate");
161 if (header) {
162 type = SIPE_AUTHENTICATION_TYPE_NEGOTIATE;
163 name = "Negotiate";
164 } else
165 #else
166 #define DEBUG_STRING "NTLM authentication is"
167 #endif
169 header = sipmsg_find_auth_header(msg, "NTLM");
170 type = SIPE_AUTHENTICATION_TYPE_NTLM;
171 name = "NTLM";
174 if (header) {
175 struct sipe_http_connection_public *conn_public = req->connection;
177 if (!conn_public->context) {
178 gboolean valid = req->flags & SIPE_HTTP_REQUEST_FLAG_AUTHDATA;
179 conn_public->context = sip_sec_create_context(type,
180 !valid, /* Single Sign-On flag */
181 TRUE, /* connection-based for HTTP */
182 valid ? req->domain : NULL,
183 valid ? req->user : NULL,
184 valid ? req->password : NULL);
188 if (conn_public->context) {
189 gchar **parts = g_strsplit(header, " ", 0);
190 gchar *spn = g_strdup_printf("HTTP/%s", conn_public->host);
191 gchar *token;
193 SIPE_DEBUG_INFO("sipe_http_request_response_unauthorized: init context target '%s' token '%s'",
194 spn, parts[1] ? parts[1] : "<NULL>");
196 if (sip_sec_init_context_step(conn_public->context,
197 spn,
198 parts[1],
199 &token,
200 NULL)) {
202 /* generate authorization header */
203 req->authorization = g_strdup_printf("Authorization: %s %s\r\n",
204 name,
205 token ? token : "");
206 g_free(token);
208 /* resend request with authorization */
209 sipe_http_request_send(conn_public);
210 failed = FALSE;
212 } else
213 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_unauthorized: security context init step failed");
215 g_free(spn);
216 g_strfreev(parts);
217 } else
218 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_unauthorized: security context creation failed");
219 } else
220 SIPE_DEBUG_INFO_NOFORMAT("sipe_http_request_response_unauthorized: only " DEBUG_STRING " supported");
222 if (failed) {
223 /* Callback: authentication failed */
224 (*req->cb)(sipe_private, 0, NULL, NULL, req->cb_data);
226 /* remove completed request */
227 sipe_http_request_cancel(req);
231 static void sipe_http_request_response_callback(struct sipe_core_private *sipe_private,
232 struct sipe_http_request *req,
233 struct sipmsg *msg)
235 const gchar *hdr;
237 /* Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net */
238 if (req->session &&
239 ((hdr = sipmsg_find_header(msg, "Set-Cookie")) != NULL)) {
240 gchar **parts, **current;
241 const gchar *part;
242 gchar *new = NULL;
244 g_free(req->session->cookie);
245 req->session->cookie = NULL;
247 current = parts = g_strsplit(hdr, ";", 0);
248 while ((part = *current++) != NULL) {
249 /* strip these parts from cookie */
250 if (!(strstr(part, "path=") ||
251 strstr(part, "domain=") ||
252 strstr(part, "expires=") ||
253 strstr(part, "secure"))) {
254 gchar *tmp = new;
255 new = new ?
256 g_strconcat(new, ";", part, NULL) :
257 g_strdup(part);
258 g_free(tmp);
261 g_strfreev(parts);
263 if (new) {
264 req->session->cookie = new;
265 SIPE_DEBUG_INFO("sipe_http_request_response_callback: cookie: %s", new);
269 /* Callback: success */
270 (*req->cb)(sipe_private,
271 msg->response,
272 msg->headers,
273 msg->body,
274 req->cb_data);
276 /* remove completed request */
277 sipe_http_request_cancel(req);
280 void sipe_http_request_response(struct sipe_http_connection_public *conn_public,
281 struct sipmsg *msg)
283 struct sipe_core_private *sipe_private = conn_public->sipe_private;
284 struct sipe_http_request *req = conn_public->pending_requests->data;
286 if (msg->response == SIPE_HTTP_STATUS_CLIENT_UNAUTHORIZED) {
287 sipe_http_request_response_unauthorized(sipe_private, req, msg);
288 /* req may no longer be valid */
289 } else {
290 /* All other cases are passed on to the user */
291 sipe_http_request_response_callback(sipe_private, req, msg);
292 /* req is no longer valid */
296 void sipe_http_request_shutdown(struct sipe_http_connection_public *conn_public)
298 if (conn_public->pending_requests) {
299 GSList *entry = conn_public->pending_requests;
300 while (entry) {
301 sipe_http_request_free(conn_public->sipe_private,
302 entry->data);
303 entry = entry->next;
305 g_slist_free(conn_public->pending_requests);
306 conn_public->pending_requests = NULL;
309 if (conn_public->context) {
310 sip_sec_destroy_context(conn_public->context);
311 conn_public->context = NULL;
315 struct sipe_http_request *sipe_http_request_new(struct sipe_core_private *sipe_private,
316 const gchar *host,
317 guint32 port,
318 const gchar *path,
319 const gchar *headers,
320 const gchar *body,
321 const gchar *content_type,
322 sipe_http_response_callback *callback,
323 gpointer callback_data)
325 struct sipe_http_request *req = g_new0(struct sipe_http_request, 1);
326 struct sipe_http_connection_public *conn_public;
328 req->path = g_strdup(path);
329 if (headers)
330 req->headers = g_strdup(headers);
331 if (body) {
332 req->body = g_strdup(body);
333 req->content_type = g_strdup(content_type);
336 req->flags = 0;
338 /* default authentication */
339 if (!SIPE_CORE_PRIVATE_FLAG_IS(SSO))
340 sipe_http_request_authentication(req,
341 sipe_private->authdomain,
342 sipe_private->authuser,
343 sipe_private->password);
345 req->cb = callback;
346 req->cb_data = callback_data;
348 req->connection = conn_public = sipe_http_transport_new(sipe_private,
349 host,
350 port);
351 if (!sipe_http_request_pending(conn_public))
352 req->flags |= SIPE_HTTP_REQUEST_FLAG_FIRST;
354 conn_public->pending_requests = g_slist_append(conn_public->pending_requests,
355 req);
357 return(req);
360 void sipe_http_request_ready(struct sipe_http_request *request)
362 struct sipe_http_connection_public *conn_public = request->connection;
364 /* pass first request on already opened connection through directly */
365 if ((request->flags & SIPE_HTTP_REQUEST_FLAG_FIRST) &&
366 conn_public->connected)
367 sipe_http_request_send(conn_public);
370 struct sipe_http_session *sipe_http_session_start(void)
372 return(g_new0(struct sipe_http_session, 1));
375 void sipe_http_session_close(struct sipe_http_session *session)
377 if (session) {
378 g_free(session->cookie);
379 g_free(session);
383 void sipe_http_request_cancel(struct sipe_http_request *request)
385 struct sipe_http_connection_public *conn_public = request->connection;
386 conn_public->pending_requests = g_slist_remove(conn_public->pending_requests,
387 request);
389 /* cancelled by requester, don't use callback */
390 request->cb = NULL;
392 sipe_http_request_free(conn_public->sipe_private, request);
395 void sipe_http_request_session(struct sipe_http_request *request,
396 struct sipe_http_session *session)
398 request->session = session;
401 void sipe_http_request_allow_redirect(struct sipe_http_request *request)
403 request->flags |= SIPE_HTTP_REQUEST_FLAG_REDIRECT;
406 void sipe_http_request_authentication(struct sipe_http_request *request,
407 const gchar *domain,
408 const gchar *user,
409 const gchar *password)
411 request->flags |= SIPE_HTTP_REQUEST_FLAG_AUTHDATA;
412 request->domain = domain;
413 request->user = user;
414 request->password = password;
418 Local Variables:
419 mode: c
420 c-file-style: "bsd"
421 indent-tabs-mode: t
422 tab-width: 8
423 End: