New client authentication (basic/digest) code from Joe Shaw
[platform/upstream/libsoup.git] / libsoup / soup-message.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-message.c: Asyncronous Callback-based SOAP Request Queue.
4  *
5  * Authors:
6  *      Alex Graveley (alex@helixcode.com)
7  *
8  * Copyright (C) 2000, Helix Code, Inc.
9  */
10
11 #include "soup-message.h"
12 #include "soup-misc.h"
13 #include "soup-context.h"
14 #include "soup-private.h"
15 #include "soup-transfer.h"
16
17 static SoupErrorCode 
18 authorize_handler (SoupMessage *msg, gboolean proxy)
19 {
20         const char *auth_header;
21         SoupAuth *auth;
22         SoupContext *ctx;
23
24         ctx = proxy ? soup_get_proxy () : msg->context;
25
26         auth_header = 
27                 soup_message_get_response_header (
28                         msg, 
29                         proxy ? "Proxy-Authenticate" : "WWW-Authenticate");
30         if (!auth_header) return SOUP_ERROR_CANT_AUTHENTICATE;
31
32         auth = soup_auth_new_from_header (ctx, auth_header);
33         if (!auth) return SOUP_ERROR_MALFORMED_HEADER;
34
35         if (ctx->auth) {
36                 if (soup_auth_invalidates_prior (auth))
37                         soup_auth_free (ctx->auth);
38                 else {
39                         soup_auth_free (auth);
40                         return SOUP_ERROR_CANT_AUTHENTICATE;
41                 }
42         }
43
44         ctx->auth = auth;
45
46         if (msg->priv->req_header) {
47                 g_string_free (msg->priv->req_header, TRUE);
48                 msg->priv->req_header = NULL;
49         }
50
51         soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
52
53         return SOUP_ERROR_NONE;
54 }
55
56 /**
57  * soup_message_new:
58  * @context: a %SoupContext for the destination endpoint.
59  * @action: a string which will be used as the SOAPAction header for the created
60  * request.
61  * 
62  * Creates a new empty %SoupMessage, which will connect to the URL represented
63  * by @context. The new message has a status of @SOUP_STATUS_IDLE.
64  *
65  * Return value: the new %SoupMessage.
66  */
67 SoupMessage *
68 soup_message_new (SoupContext *context, SoupAction action) 
69 {
70         SoupMessage *ret;
71
72         g_return_val_if_fail (context, NULL);
73
74         ret          = g_new0 (SoupMessage, 1);
75         ret->priv    = g_new0 (SoupMessagePrivate, 1);
76         ret->status  = SOUP_STATUS_IDLE;
77         ret->action  = g_strdup (action);
78         ret->context = context;
79         ret->method  = SOUP_METHOD_POST;
80
81         soup_context_ref (context);
82
83         /*
84          * Add a 401 (Authorization Required) response code handler if the
85          * context URI has a login user name.
86          */
87         if (soup_context_get_uri (context)->user)
88                 soup_message_add_response_code_handler (
89                         ret, 
90                         401, 
91                         SOUP_HANDLER_POST_BODY, 
92                         (SoupHandlerFn) authorize_handler, 
93                         GINT_TO_POINTER (FALSE));
94
95         /*
96          * Always add a 407 (Proxy-Authorization Required) handler, in case the
97          * proxy is reset after message creation.
98          */
99         soup_message_add_response_code_handler (
100                         ret, 
101                         407, 
102                         SOUP_HANDLER_POST_BODY, 
103                         (SoupHandlerFn) authorize_handler, 
104                         GINT_TO_POINTER (TRUE));
105
106         return ret;
107 }
108
109 /**
110  * soup_message_new_full:
111  * @context: a %SoupContext for the destination endpoint.
112  * @action: a string which will be used as the SOAPAction header for the created
113  * request.
114  * @req_owner: the %SoupOwnership of the passed data buffer.
115  * @req_body: a data buffer containing the body of the message request.
116  * @req_length: the byte length of @req_body.
117  * 
118  * Creates a new %SoupMessage, which will connect to the URL represented by
119  * @context. The new message has a status of @SOUP_STATUS_IDLE. The request data
120  * buffer will be filled from @req_owner, @req_body, and @req_length
121  * respectively.
122  *
123  * Return value: the new %SoupMessage.
124  */
125 SoupMessage *
126 soup_message_new_full (SoupContext   *context,
127                        SoupAction     action,
128                        SoupOwnership  req_owner,
129                        gchar         *req_body,
130                        gulong         req_length)
131 {
132         SoupMessage *ret = soup_message_new (context, action);
133
134         ret->request.owner = req_owner;
135         ret->request.body = req_body;
136         ret->request.length = req_length;
137
138         return ret;
139 }
140
141 /**
142  * soup_message_cleanup:
143  * @req: a %SoupMessage.
144  * @action: a string which will be used as the SOAPAction header for the created
145  * request.
146  * 
147  * Frees any temporary resources created in the processing of @req. Request and
148  * response data buffers are left intact.
149  */
150 void 
151 soup_message_cleanup (SoupMessage *req)
152 {
153         g_return_if_fail (req != NULL);
154
155         if (req->priv->read_tag) {
156                 soup_transfer_read_cancel (req->priv->read_tag);
157                 req->priv->read_tag = 0;
158         }
159
160         if (req->priv->write_tag) {
161                 soup_transfer_write_cancel (req->priv->write_tag);
162                 req->priv->write_tag = 0;
163         }
164
165         if (req->priv->connect_tag) {
166                 soup_context_cancel_connect (req->priv->connect_tag);
167                 req->priv->connect_tag = NULL;
168         }
169         if (req->priv->conn) {
170                 soup_connection_release (req->priv->conn);
171                 req->priv->conn = NULL;
172         }
173
174         soup_active_requests = g_slist_remove (soup_active_requests, req);
175 }
176
177 static void
178 soup_message_remove_header (gchar *name, gchar *value, gpointer unused)
179 {
180         g_free (name);
181         g_free (value);
182 }
183
184 /**
185  * soup_message_free:
186  * @req: a %SoupMessage to destroy.
187  * 
188  * Destroys the %SoupMessage pointed to by @req. Request and response headers
189  * are freed. Request and response data buffers are also freed if their
190  * ownership is %SOUP_BUFFER_SYSTEM_OWNED. The message's destination context
191  * will be de-referenced.
192  */
193 void 
194 soup_message_free (SoupMessage *req)
195 {
196         g_return_if_fail (req != NULL);
197
198         soup_message_cleanup (req);
199
200         soup_context_unref (req->context);
201
202         if (req->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
203                 g_free (req->request.body);
204         if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
205                 g_free (req->response.body);
206
207         if (req->priv->req_header) 
208                 g_string_free (req->priv->req_header, TRUE);
209
210         if (req->request_headers) {
211                 g_hash_table_foreach (req->request_headers,
212                                       (GHFunc) soup_message_remove_header,
213                                       NULL);
214                 g_hash_table_destroy (req->request_headers);
215         }
216
217         if (req->response_headers) {
218                 g_hash_table_foreach (req->response_headers,
219                                       (GHFunc) soup_message_remove_header,
220                                       NULL);
221                 g_hash_table_destroy (req->response_headers);
222         }
223
224         g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
225         g_slist_free (req->priv->content_handlers);
226
227         g_free (req->priv);
228         g_free (req->action);
229         g_free (req);
230 }
231
232 /**
233  * soup_message_issue_callback:
234  * @req: a %SoupMessage currently being processed.
235  * @error: a %SoupErrorCode to be passed to %req's completion callback.
236  * 
237  * Finalizes the message request, by first freeing any temporary resources, then
238  * issuing the callback function pointer passed in %soup_message_new or
239  * %soup_message_new_full. If, after returning from the callback, the message
240  * has not been requeued, @msg is destroyed using %soup_message_free.
241  */
242 void
243 soup_message_issue_callback (SoupMessage *req, SoupErrorCode error)
244 {
245         g_return_if_fail (req != NULL);
246
247         /* make sure we don't have some icky recursion if the callback 
248            runs the main loop, and the connection has some data or error 
249            which causes the callback to be run again */
250         soup_message_cleanup (req);
251
252         req->priv->errorcode = error;
253
254         if (req->priv->callback) {
255                 (*req->priv->callback) (req, error, req->priv->user_data);
256
257                 /* Free it only if callback exist, its probably a sync call */
258                 if (req->status != SOUP_STATUS_QUEUED)
259                         soup_message_free (req);
260         }
261 }
262
263 /**
264  * soup_message_cancel:
265  * @req: a %SoupMessage currently being processed.
266  * 
267  * Cancel a running message, and issue completion callback with a
268  * %SoupTransferStatus of %SOUP_ERROR_CANCELLED. If not requeued by the
269  * completion callback, the @msg will be destroyed.
270  */
271 void 
272 soup_message_cancel (SoupMessage *req) 
273 {
274         soup_message_issue_callback (req, SOUP_ERROR_CANCELLED);
275 }
276
277 static void 
278 soup_message_set_header (GHashTable  **hash,
279                          const gchar  *name,
280                          const gchar  *value) 
281 {
282         gpointer old_name, old_value;
283
284         if (!*hash) 
285                 *hash = g_hash_table_new (soup_str_case_hash, 
286                                           soup_str_case_equal);
287         else if (g_hash_table_lookup_extended (*hash, 
288                                                name, 
289                                                &old_name, 
290                                                &old_value)) {
291                 g_hash_table_remove (*hash, name);
292                 g_free (old_name);
293                 g_free (old_value);
294         }
295
296         if (value)
297                 g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
298 }
299
300 /**
301  * soup_message_set_request_header:
302  * @req: a %SoupMessage.
303  * @name: header name.
304  * @value: header value.
305  * 
306  * Adds a new transport header to be sent on an outgoing request. Passing a NULL
307  * @value will remove the header name supplied.
308  */
309 void
310 soup_message_set_request_header (SoupMessage *req,
311                                  const gchar *name,
312                                  const gchar *value) 
313 {
314         g_return_if_fail (req != NULL);
315         g_return_if_fail (name != NULL || name [0] != '\0');
316
317         if (req->priv->req_header) {
318                 g_string_free (req->priv->req_header, TRUE);
319                 req->priv->req_header = NULL;
320         }
321
322         soup_message_set_header (&req->request_headers, name, value);
323 }
324
325 /**
326  * soup_message_get_request_header:
327  * @req: a %SoupMessage.
328  * @name: header name.
329  * 
330  * Lookup the transport request header with a key equal to @name.
331  *
332  * Return value: the header's value or NULL if not found.
333  */
334 const gchar *
335 soup_message_get_request_header (SoupMessage *req,
336                                  const gchar *name) 
337 {
338         g_return_val_if_fail (req != NULL, NULL);
339         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
340
341         return req->request_headers ? 
342                 g_hash_table_lookup (req->request_headers, name) : NULL;
343 }
344
345 /**
346  * soup_message_set_response_header:
347  * @req: a %SoupMessage.
348  * @name: header name.
349  * @value: header value.
350  * 
351  * Adds a new transport header to be sent on an outgoing response. Passing a
352  * NULL @value will remove the header name supplied.
353  */
354 void
355 soup_message_set_response_header (SoupMessage *req,
356                                   const gchar *name,
357                                   const gchar *value) 
358 {
359         g_return_if_fail (req != NULL);
360         g_return_if_fail (name != NULL || name [0] != '\0');
361
362         soup_message_set_header (&req->response_headers, name, value);
363 }
364
365 /**
366  * soup_message_get_response_header:
367  * @req: a %SoupMessage.
368  * @name: header name.
369  * 
370  * Lookup the transport response header with a key equal to @name.
371  *
372  * Return value: the header's value or NULL if not found.
373  */
374 const gchar *
375 soup_message_get_response_header (SoupMessage *req,
376                                   const gchar *name) 
377 {
378         g_return_val_if_fail (req != NULL, NULL);
379         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
380
381         return req->response_headers ? 
382                 g_hash_table_lookup (req->response_headers, name) : NULL;
383 }
384
385 /**
386  * soup_message_send:
387  * @msg: a %SoupMessage.
388  * 
389  * Syncronously send @msg. This call will not return until the transfer is
390  * finished successfully or there is an unrecoverable error. 
391  *
392  * @msg is not free'd upon return.
393  *
394  * Return value: the %SoupErrorCode of the error encountered while sending, or
395  * SOUP_ERROR_NONE.
396  */
397 SoupErrorCode 
398 soup_message_send (SoupMessage *msg)
399 {
400         soup_message_queue (msg, NULL, NULL);
401
402         while (1) {
403                 g_main_iteration (TRUE); 
404                 if (msg->status == SOUP_STATUS_FINISHED ||
405                     msg->priv->errorcode != SOUP_ERROR_NONE)
406                         return msg->priv->errorcode;
407         }
408
409         return SOUP_ERROR_NONE;
410 }
411
412 void
413 soup_message_set_method (SoupMessage *msg, const gchar *method)
414 {
415         g_return_if_fail (msg != NULL);
416         g_return_if_fail (method != NULL);
417
418         msg->method = method;
419 }
420
421 const gchar *
422 soup_message_get_method (SoupMessage *msg)
423 {
424         g_return_val_if_fail (msg != NULL, NULL);
425
426         return msg->method;
427 }
428
429 typedef enum {
430         RESPONSE_HEADER_HANDLER,
431         RESPONSE_CODE_HANDLER,
432         RESPONSE_BODY_HANDLER
433 } SoupHandlerKind;
434
435 typedef struct {
436         SoupHandlerType   type;
437         SoupHandlerFn     handler_cb;
438         gpointer          user_data;
439
440         SoupHandlerKind   kind;
441         const gchar      *header;
442         guint             code;
443 } SoupHandlerData;
444
445 static void 
446 soup_message_add_handler (SoupMessage      *msg,
447                           SoupHandlerType   type,
448                           SoupHandlerFn     handler_cb,
449                           gpointer          user_data,
450                           SoupHandlerKind   kind,
451                           const gchar      *header,
452                           guint             code)
453 {
454         SoupHandlerData *data;
455
456         data = g_new0 (SoupHandlerData, 1);
457         data->type = type;
458         data->handler_cb = handler_cb;
459         data->user_data = user_data;
460         data->kind = kind;
461         data->header = header;
462         data->code = code;
463
464         msg->priv->content_handlers = 
465                 g_slist_append (msg->priv->content_handlers, data);
466 }
467
468 void 
469 soup_message_add_header_handler (SoupMessage      *msg,
470                                  const gchar      *header,
471                                  SoupHandlerType   type,
472                                  SoupHandlerFn     handler_cb,
473                                  gpointer          user_data)
474 {
475         g_return_if_fail (msg != NULL);
476         g_return_if_fail (header != NULL);
477         g_return_if_fail (handler_cb != NULL);
478
479         soup_message_add_handler (msg, 
480                                   type, 
481                                   handler_cb, 
482                                   user_data, 
483                                   RESPONSE_HEADER_HANDLER, 
484                                   header, 
485                                   0);
486 }
487
488 void 
489 soup_message_add_response_code_handler (SoupMessage      *msg,
490                                         guint             code,
491                                         SoupHandlerType   type,
492                                         SoupHandlerFn     handler_cb,
493                                         gpointer          user_data)
494 {
495         g_return_if_fail (msg != NULL);
496         g_return_if_fail (code != 0);
497         g_return_if_fail (handler_cb != NULL);
498
499         soup_message_add_handler (msg, 
500                                   type, 
501                                   handler_cb, 
502                                   user_data, 
503                                   RESPONSE_CODE_HANDLER, 
504                                   NULL, 
505                                   code);
506 }
507
508 void 
509 soup_message_add_body_handler (SoupMessage      *msg,
510                                SoupHandlerType   type,
511                                SoupHandlerFn     handler_cb,
512                                gpointer          user_data)
513 {
514         g_return_if_fail (msg != NULL);
515         g_return_if_fail (handler_cb != NULL);
516
517         soup_message_add_handler (msg, 
518                                   type, 
519                                   handler_cb, 
520                                   user_data, 
521                                   RESPONSE_BODY_HANDLER, 
522                                   NULL, 
523                                   0);
524 }
525
526 SoupErrorCode 
527 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
528 {
529         GSList *list;
530         SoupErrorCode retval = SOUP_ERROR_NONE;
531
532         g_return_val_if_fail (msg != NULL, retval);
533         
534         for (list = msg->priv->content_handlers; list; list = list->next) {
535                 SoupHandlerData *data = list->data;
536                 
537                 if (data->type != invoke_type) continue;
538
539                 switch (data->kind) {
540                 case RESPONSE_HEADER_HANDLER:
541                         if (!soup_message_get_response_header (msg,
542                                                                data->header))
543                                 continue;
544                         break;
545                 case RESPONSE_CODE_HANDLER:
546                         if (msg->response_code != data->code) continue;
547                         break;
548                 case RESPONSE_BODY_HANDLER:
549                         break;
550                 }
551
552                 retval = (*data->handler_cb) (msg, data->user_data);
553
554                 if (retval != SOUP_ERROR_NONE) break;
555                 if (msg->status == SOUP_STATUS_QUEUED) break;
556         }
557
558         return retval;
559 }
560
561 static void
562 soup_message_remove_handler (SoupMessage   *msg, 
563                              SoupHandlerFn  handler_cb,
564                              gpointer       user_data)
565 {
566         GSList *iter = msg->priv->content_handlers;
567
568         while (iter) {
569                 SoupHandlerData *data = iter->data;
570
571                 if (data->handler_cb == handler_cb &&
572                     data->user_data == user_data) {
573                         msg->priv->content_handlers = 
574                                 g_slist_remove_link (
575                                         msg->priv->content_handlers,
576                                         iter);
577                         g_free (data);
578                         break;
579                 }
580                 
581                 iter = iter->next;
582         }
583 }
584
585 static SoupErrorCode 
586 redirect_handler (SoupMessage *msg, gpointer user_data)
587 {
588         const gchar *new_url;
589
590         switch (msg->response_code) {
591         case 300: /* Multiple Choices */
592         case 301: /* Moved Permanently */
593         case 302: /* Moved Temporarily */
594         case 303: /* See Other */
595         case 305: /* Use Proxy */
596                 break;
597         default:
598                 return SOUP_ERROR_NONE;
599         }
600
601         if (!(msg->priv->msg_flags & SOUP_MESSAGE_FOLLOW_REDIRECT)) 
602                 return SOUP_ERROR_NONE;
603
604         new_url = soup_message_get_response_header (msg, "Location");
605
606         if (new_url) {
607                 SoupContext *new_ctx = soup_context_get (new_url);
608                 if (!new_ctx) return SOUP_ERROR_MALFORMED_HEADER;
609
610                 soup_context_unref (msg->context);
611                 msg->context = new_ctx;
612
613                 soup_message_queue (msg,
614                                     msg->priv->callback, 
615                                     msg->priv->user_data);
616         }
617
618         return SOUP_ERROR_NONE;
619 }
620
621 static inline gboolean
622 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
623 {
624         return ((newflags & find) && !(msg->priv->msg_flags & find));
625 }
626
627 static inline gboolean
628 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
629 {
630         return (!(newflags & find) && (msg->priv->msg_flags & find));
631 }
632
633 void
634 soup_message_set_flags (SoupMessage *msg, guint flags)
635 {
636         g_return_if_fail (msg != NULL);
637
638         if (ADDED_FLAG (msg, flags, SOUP_MESSAGE_FOLLOW_REDIRECT))
639                 soup_message_add_header_handler (msg,
640                                                  "Location",
641                                                  SOUP_HANDLER_PRE_BODY,
642                                                  redirect_handler,
643                                                  NULL);
644         else if (REMOVED_FLAG (msg, flags, SOUP_MESSAGE_FOLLOW_REDIRECT))
645                 soup_message_remove_handler (msg, 
646                                              redirect_handler,
647                                              NULL);
648
649         msg->priv->msg_flags = flags;
650 }
651
652 guint
653 soup_message_get_flags (SoupMessage *msg)
654 {
655         return msg->priv->msg_flags;
656 }