Handle redirects when source uri has auth data by copying auth to new url.
[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-auth.h"
12 #include "soup-error.h"
13 #include "soup-message.h"
14 #include "soup-misc.h"
15 #include "soup-context.h"
16 #include "soup-private.h"
17 #include "soup-queue.h"
18 #include "soup-transfer.h"
19
20 /**
21  * soup_message_new:
22  * @context: a %SoupContext for the destination endpoint.
23  * @method: a string which will be used as the HTTP method for the created
24  * request.
25  * 
26  * Creates a new empty %SoupMessage, which will connect to the URL represented
27  * by @context. The new message has a status of @SOUP_STATUS_IDLE.
28  *
29  * Return value: the new %SoupMessage.
30  */
31 SoupMessage *
32 soup_message_new (SoupContext *context, const gchar *method) 
33 {
34         SoupMessage *ret;
35
36         g_return_val_if_fail (context, NULL);
37
38         ret          = g_new0 (SoupMessage, 1);
39         ret->priv    = g_new0 (SoupMessagePrivate, 1);
40         ret->status  = SOUP_STATUS_IDLE;
41         ret->context = context;
42         ret->method  = method ? method : SOUP_METHOD_POST;
43
44         ret->request_headers = g_hash_table_new (soup_str_case_hash, 
45                                                  soup_str_case_equal);
46
47         ret->response_headers = g_hash_table_new (soup_str_case_hash, 
48                                                   soup_str_case_equal);
49
50         ret->priv->http_version = SOUP_HTTP_1_1;
51
52         soup_context_ref (context);
53
54         return ret;
55 }
56
57 /**
58  * soup_message_new_full:
59  * @context: a %SoupContext for the destination endpoint.
60  * @method: a string which will be used as the HTTP method for the created
61  * request.
62  * @req_owner: the %SoupOwnership of the passed data buffer.
63  * @req_body: a data buffer containing the body of the message request.
64  * @req_length: the byte length of @req_body.
65  * 
66  * Creates a new %SoupMessage, which will connect to the URL represented by
67  * @context. The new message has a status of @SOUP_STATUS_IDLE. The request data
68  * buffer will be filled from @req_owner, @req_body, and @req_length
69  * respectively.
70  *
71  * Return value: the new %SoupMessage.
72  */
73 SoupMessage *
74 soup_message_new_full (SoupContext   *context,
75                        const gchar   *method,
76                        SoupOwnership  req_owner,
77                        gchar         *req_body,
78                        gulong         req_length)
79 {
80         SoupMessage *ret = soup_message_new (context, method);
81
82         ret->request.owner = req_owner;
83         ret->request.body = req_body;
84         ret->request.length = req_length;
85
86         return ret;
87 }
88
89 /**
90  * soup_message_cleanup:
91  * @req: a %SoupMessage.
92  * 
93  * Frees any temporary resources created in the processing of @req. Request and
94  * response data buffers are left intact.
95  */
96 void 
97 soup_message_cleanup (SoupMessage *req)
98 {
99         g_return_if_fail (req != NULL);
100
101         if (req->priv->read_tag) {
102                 soup_transfer_read_cancel (req->priv->read_tag);
103                 req->priv->read_tag = 0;
104         }
105
106         if (req->priv->write_tag) {
107                 soup_transfer_write_cancel (req->priv->write_tag);
108                 req->priv->write_tag = 0;
109         }
110
111         if (req->priv->connect_tag) {
112                 soup_context_cancel_connect (req->priv->connect_tag);
113                 req->priv->connect_tag = NULL;
114         }
115         if (req->connection) {
116                 soup_connection_release (req->connection);
117                 req->connection = NULL;
118         }
119
120         soup_active_requests = g_slist_remove (soup_active_requests, req);
121 }
122
123 static void
124 finalize_message (SoupMessage *req)
125 {
126         soup_context_unref (req->context);
127
128         if (req->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
129                 g_free (req->request.body);
130         if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
131                 g_free (req->response.body);
132
133         if (req->priv->req_header) 
134                 g_string_free (req->priv->req_header, TRUE);
135
136         soup_message_clear_headers (req->request_headers);
137         g_hash_table_destroy (req->request_headers);
138
139         soup_message_clear_headers (req->response_headers);
140         g_hash_table_destroy (req->response_headers);
141
142         g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
143         g_slist_free (req->priv->content_handlers);
144
145         g_free ((gchar *) req->errorphrase);
146         g_free (req->priv);
147         g_free (req);
148 }
149
150 /**
151  * soup_message_free:
152  * @req: a %SoupMessage to destroy.
153  * 
154  * Destroys the %SoupMessage pointed to by @req. Request and response headers
155  * are freed. Request and response data buffers are also freed if their
156  * ownership is %SOUP_BUFFER_SYSTEM_OWNED. The message's destination context
157  * will be de-referenced.
158  */
159 void 
160 soup_message_free (SoupMessage *req)
161 {
162         g_return_if_fail (req != NULL);
163
164         soup_message_cleanup (req);
165
166         finalize_message (req);
167 }
168
169 /**
170  * soup_message_issue_callback:
171  * @req: a %SoupMessage currently being processed.
172  * @error: a %SoupErrorCode to be passed to %req's completion callback.
173  * 
174  * Finalizes the message request, by first freeing any temporary resources, then
175  * issuing the callback function pointer passed in %soup_message_new or
176  * %soup_message_new_full. If, after returning from the callback, the message
177  * has not been requeued, @msg is destroyed using %soup_message_free.
178  */
179 void
180 soup_message_issue_callback (SoupMessage *req)
181 {
182         g_return_if_fail (req != NULL);
183
184         /* 
185          * Make sure we don't have some icky recursion if the callback 
186          * runs the main loop, and the connection has some data or error 
187          * which causes the callback to be run again.
188          */
189         soup_message_cleanup (req);
190
191         if (req->priv->callback) {
192                 (*req->priv->callback) (req, req->priv->user_data);
193
194                 if (req->status != SOUP_STATUS_QUEUED)
195                         finalize_message (req);
196         }
197 }
198
199 /**
200  * soup_message_cancel:
201  * @req: a %SoupMessage currently being processed.
202  * 
203  * Cancel a running message, and issue completion callback with a
204  * %SoupTransferStatus of %SOUP_ERROR_CANCELLED. If not requeued by the
205  * completion callback, the @msg will be destroyed.
206  */
207 void 
208 soup_message_cancel (SoupMessage *msg) 
209 {
210         soup_message_set_error (msg, SOUP_ERROR_CANCELLED);
211         soup_message_issue_callback (msg);
212 }
213
214 static gboolean 
215 foreach_free_header_list (gchar *name, GSList *vals, gpointer notused)
216 {
217         g_free (name);
218         g_slist_foreach (vals, (GFunc) g_free, NULL);
219         g_slist_free (vals);
220
221         return TRUE;
222 }
223
224 void
225 soup_message_clear_headers       (GHashTable        *hash)
226 {
227         g_return_if_fail (hash != NULL);
228
229         g_hash_table_foreach_remove (hash, 
230                                      (GHRFunc) foreach_free_header_list, 
231                                      NULL);
232 }
233
234 void 
235 soup_message_add_header (GHashTable  *hash,
236                          const gchar *name,
237                          const gchar *value) 
238 {
239         gboolean exists = FALSE;
240         gpointer old_name;
241         GSList *old_value;
242
243         g_return_if_fail (hash != NULL);
244         g_return_if_fail (name != NULL || name [0] != '\0');    
245
246         exists = g_hash_table_lookup_extended (hash, 
247                                                name, 
248                                                &old_name, 
249                                                (gpointer *) &old_value);
250
251         if (value && exists)
252                 old_value = g_slist_append (old_value,
253                                             g_strdup (value));
254         else if (value && !exists)
255                 g_hash_table_insert (hash, 
256                                      g_strdup (name), 
257                                      g_slist_append (NULL, 
258                                                      g_strdup (value)));
259         else if (!value && exists) {
260                 g_hash_table_remove (hash, name);
261                 foreach_free_header_list (old_name, old_value, NULL);
262         } 
263 }
264
265 /**
266  * soup_message_get_header:
267  * @req: a %SoupMessage.
268  * @name: header name.
269  * 
270  * Lookup the first transport header with a key equal to @name.
271  *
272  * Return value: the header's value or NULL if not found.
273  */
274 const gchar *
275 soup_message_get_header (GHashTable *hash,
276                          const gchar *name)
277 {
278         GSList *vals;
279
280         g_return_val_if_fail (hash != NULL, NULL);
281         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);  
282
283         vals = g_hash_table_lookup (hash, name);
284         if (vals) 
285                 return vals->data;
286
287         return NULL;
288 }
289
290 /**
291  * soup_message_get_header_list:
292  * @req: a %SoupMessage.
293  * @name: header name.
294  * 
295  * Lookup the all transport request headers with a key equal to @name.
296  *
297  * Return value: a const pointer to a GSList of header values or NULL if not
298  * found.  
299  */
300 const GSList *
301 soup_message_get_header_list (GHashTable  *hash,
302                               const gchar *name)
303 {
304         g_return_val_if_fail (hash != NULL, NULL);
305         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);  
306
307         return g_hash_table_lookup (hash, name);
308 }
309
310 typedef struct {
311         GHFunc   func;
312         gpointer user_data;
313 } ForeachData;
314
315 static void 
316 foreach_value_in_list (gchar *name, GSList *vals, ForeachData *data)
317 {
318         while (vals) {
319                 gchar *v = vals->data;
320
321                 (*data->func) (name, v, data->user_data);
322
323                 vals = vals->next;
324         }
325 }
326
327 void
328 soup_message_foreach_header      (GHashTable        *hash,
329                                   GHFunc             func,
330                                   gpointer           user_data)
331 {
332         ForeachData data = { func, user_data };
333
334         g_return_if_fail (hash != NULL);
335         g_return_if_fail (func != NULL);
336
337         g_hash_table_foreach (hash, (GHFunc) foreach_value_in_list, &data);
338 }
339
340 /**
341  * soup_message_set_request_header:
342  * @req: a %SoupMessage.
343  * @name: header name.
344  * @value: header value.
345  *
346  * ** DEPRECATED **
347  * 
348  * Adds a new transport header to be sent on an outgoing request. Passing a NULL
349  * @value will remove all headers with a name equal to @name.
350  */
351 void
352 soup_message_set_request_header (SoupMessage *req,
353                                  const gchar *name,
354                                  const gchar *value) 
355 {
356         g_return_if_fail (req != NULL);
357         g_return_if_fail (name != NULL || name [0] != '\0');
358
359         g_warning ("soup_message_set_request_header is DEPRECATED. Use "
360                    "soup_message_add_header, with msg->request_headers as "
361                    "the first argument.\n");
362
363         soup_message_add_header (req->request_headers, name, value);
364 }
365
366 /**
367  * soup_message_get_request_header:
368  * @req: a %SoupMessage.
369  * @name: header name.
370  * 
371  * ** DEPRECATED **
372  * 
373  * Lookup the first transport request header with a key equal to @name.
374  *
375  * Return value: the header's value or NULL if not found.
376  */
377 const gchar *
378 soup_message_get_request_header (SoupMessage *req,
379                                  const gchar *name) 
380 {
381         GSList *vals;
382         g_return_val_if_fail (req != NULL, NULL);
383         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
384
385         g_warning ("soup_message_get_request_header is DEPRECATED. Use "
386                    "soup_message_get_header, with msg->request_headers as "
387                    "the first argument.\n");
388
389         if (req->request_headers) {
390                 vals = g_hash_table_lookup (req->request_headers, name);
391                 if (vals) 
392                         return vals->data;
393         }
394
395         return NULL;
396 }
397
398 /**
399  * soup_message_set_response_header:
400  * @req: a %SoupMessage.
401  * @name: header name.
402  * @value: header value.
403  * 
404  * ** DEPRECATED **
405  * 
406  * Adds a new transport header to be sent on an outgoing response. Passing a
407  * NULL @value will remove all headers with a name equal to @name.
408  */
409 void
410 soup_message_set_response_header (SoupMessage *req,
411                                   const gchar *name,
412                                   const gchar *value) 
413 {
414         g_return_if_fail (req != NULL);
415         g_return_if_fail (name != NULL || name [0] != '\0');
416
417         g_warning ("soup_message_set_response_header is DEPRECATED. Use "
418                    "soup_message_add_header, with msg->response_headers as "
419                    "the first argument.\n");
420
421         soup_message_add_header (req->response_headers, name, value);
422 }
423
424 /**
425  * soup_message_get_response_header:
426  * @req: a %SoupMessage.
427  * @name: header name.
428  * 
429  * ** DEPRECATED **
430  * 
431  * Lookup the transport response header with a key equal to @name.
432  *
433  * Return value: the header's value or NULL if not found.
434  */
435 const gchar *
436 soup_message_get_response_header (SoupMessage *req,
437                                   const gchar *name) 
438 {
439         GSList *vals;
440         g_return_val_if_fail (req != NULL, NULL);
441         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
442
443         g_warning ("soup_message_get_response_header is DEPRECATED. Use "
444                    "soup_message_get_header, with msg->response_headers as "
445                    "the first argument.\n");
446
447         if (req->response_headers) {
448                 vals = g_hash_table_lookup (req->response_headers, name);
449                 if (vals) 
450                         return vals->data;
451         }
452
453         return NULL;
454 }
455
456 /**
457  * soup_message_queue:
458  * @req: a %SoupMessage.
459  * @callback: a %SoupCallbackFn which will be called after the message completes
460  * or when an unrecoverable error occurs.
461  * @user_data: a pointer passed to @callback.
462  * 
463  * Queues the message @req for sending. All messages are processed while the
464  * glib main loop runs. If this %SoupMessage has been processed before, any
465  * resources related to the time it was last sent are freed.
466  *
467  * If the response %SoupDataBuffer has an owner of %SOUP_BUFFER_USER_OWNED, the
468  * message will not be queued, and @callback will be called with a
469  * %SoupErrorCode of %SOUP_ERROR_CANCELLED.
470  *
471  * Upon message completetion, the callback specified in @callback will be
472  * invoked. If after returning from this callback the message has not been
473  * requeued using %soup_message_queue, %soup_message_free will be called on
474  * @req.
475  */
476 void 
477 soup_message_queue (SoupMessage    *req,
478                     SoupCallbackFn  callback, 
479                     gpointer        user_data)
480 {
481         soup_queue_message (req, callback, user_data);
482 }
483
484 /**
485  * soup_message_send:
486  * @msg: a %SoupMessage.
487  * 
488  * Syncronously send @msg. This call will not return until the transfer is
489  * finished successfully or there is an unrecoverable error. 
490  *
491  * @msg is not free'd upon return.
492  *
493  * Return value: the %SoupErrorClass of the error encountered while sending or
494  * reading the response.
495  */
496 SoupErrorClass
497 soup_message_send (SoupMessage *msg)
498 {
499         soup_message_queue (msg, NULL, NULL);
500
501         while (1) {
502                 g_main_iteration (TRUE); 
503                 if (msg->status == SOUP_STATUS_FINISHED || 
504                     SOUP_ERROR_IS_TRANSPORT (msg->errorcode))
505                         break;
506         }
507
508         return msg->errorclass;
509 }
510
511 static void 
512 authorize_handler (SoupMessage *msg, gboolean proxy)
513 {
514         const GSList *vals;
515         SoupAuth *auth, *old_auth;
516         SoupContext *ctx;
517         const SoupUri *uri;
518
519         ctx = proxy ? soup_get_proxy () : msg->context;
520         uri = soup_context_get_uri (ctx);
521
522         if (!uri->user) 
523                 goto THROW_CANT_AUTHENTICATE;
524
525         vals = soup_message_get_header_list (msg->response_headers, 
526                                              proxy ? 
527                                                      "Proxy-Authenticate" : 
528                                                      "WWW-Authenticate");
529         if (!vals) goto THROW_CANT_AUTHENTICATE;
530
531         auth = soup_auth_new_from_header_list (uri, vals);
532         if (!auth) {
533                 soup_message_set_error_full (
534                         msg, 
535                         proxy ? 
536                                 SOUP_ERROR_CANT_AUTHENTICATE_PROXY : 
537                                 SOUP_ERROR_CANT_AUTHENTICATE,
538                         proxy ? 
539                                 "Unknown authentication scheme required by "
540                                 "proxy" :
541                                 "Unknown authentication scheme required");
542                 return;
543         }
544
545         old_auth = soup_auth_lookup (ctx);
546         if (old_auth) {
547                 if (!soup_auth_invalidates_prior (auth, old_auth)) {
548                         soup_auth_free (auth);
549                         goto THROW_CANT_AUTHENTICATE;
550                 }
551         }
552
553         soup_auth_set_context (auth, ctx);
554
555         soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
556
557         return;
558
559  THROW_CANT_AUTHENTICATE:
560         soup_message_set_error (msg, 
561                                 proxy ? 
562                                         SOUP_ERROR_CANT_AUTHENTICATE_PROXY : 
563                                         SOUP_ERROR_CANT_AUTHENTICATE);
564 }
565
566 static void 
567 redirect_handler (SoupMessage *msg, gpointer user_data)
568 {
569         const gchar *new_loc;
570
571         if (msg->errorclass != SOUP_ERROR_CLASS_REDIRECT || 
572             msg->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT) return;
573
574         new_loc = soup_message_get_header (msg->response_headers, "Location");
575
576         if (new_loc) {
577                 const SoupUri *old_uri;
578                 SoupUri *new_uri;
579                 SoupContext *new_ctx, *old_ctx;
580
581                 old_uri = soup_context_get_uri (msg->context);
582
583                 new_uri = soup_uri_new (new_loc);
584                 if (!new_uri) 
585                         goto INVALID_REDIRECT;
586
587                 /* 
588                  * Copy auth info from original URI.
589                  */
590                 if (old_uri->user && !new_uri->user) {
591                         new_uri->user = g_strdup (old_uri->user);
592                         new_uri->passwd = g_strdup (old_uri->passwd);
593                         new_uri->authmech = g_strdup (old_uri->authmech);
594                 }
595
596                 new_ctx = soup_context_from_uri (new_uri);
597
598                 soup_uri_free (new_uri);
599
600                 if (!new_ctx)
601                         goto INVALID_REDIRECT;
602
603                 old_ctx = msg->context;
604                 msg->context = new_ctx;
605
606                 soup_context_unref (old_ctx);
607
608                 soup_message_queue (msg,
609                                     msg->priv->callback, 
610                                     msg->priv->user_data);
611         }
612
613         return;
614
615  INVALID_REDIRECT:
616         soup_message_set_error_full (msg, 
617                                      SOUP_ERROR_MALFORMED,
618                                      "Invalid Redirect URL");
619 }
620
621 typedef enum {
622         RESPONSE_HEADER_HANDLER = 1,
623         RESPONSE_ERROR_CODE_HANDLER,
624         RESPONSE_ERROR_CLASS_HANDLER
625 } SoupHandlerKind;
626
627 typedef struct {
628         SoupHandlerType   type;
629         SoupCallbackFn    handler_cb;
630         gpointer          user_data;
631
632         SoupHandlerKind   kind;
633         union {
634                 guint             errorcode;
635                 SoupErrorClass    errorclass;
636                 const gchar      *header;
637         } data;
638 } SoupHandlerData;
639
640 static SoupHandlerData global_handlers [] = {
641         /* 
642          * Handle redirect response codes 300, 301, 302, 303, and 305.
643          */
644         {
645                 SOUP_HANDLER_PRE_BODY,
646                 redirect_handler, 
647                 NULL, 
648                 RESPONSE_HEADER_HANDLER, 
649                 { (guint) "Location" }
650         },
651         /* 
652          * Handle authorization.
653          */
654         {
655                 SOUP_HANDLER_PRE_BODY,
656                 (SoupCallbackFn) authorize_handler, 
657                 GINT_TO_POINTER (FALSE), 
658                 RESPONSE_ERROR_CODE_HANDLER, 
659                 { 401 }
660         },
661         /* 
662          * Handle proxy authorization.
663          */
664         {
665                 SOUP_HANDLER_PRE_BODY,
666                 (SoupCallbackFn) authorize_handler, 
667                 GINT_TO_POINTER (TRUE), 
668                 RESPONSE_ERROR_CODE_HANDLER, 
669                 { 407 }
670         },
671         { 0 }
672 };
673
674 static inline void 
675 run_handler (SoupMessage     *msg, 
676              SoupHandlerType  invoke_type, 
677              SoupHandlerData *data)
678 {
679         if (data->type != invoke_type) return;
680
681         switch (data->kind) {
682         case RESPONSE_HEADER_HANDLER:
683                 if (!soup_message_get_header (msg->response_headers,
684                                               data->data.header))
685                         return;
686                 break;
687         case RESPONSE_ERROR_CODE_HANDLER:
688                 if (msg->errorcode != data->data.errorcode) return;
689                 break;
690         case RESPONSE_ERROR_CLASS_HANDLER:
691                 if (msg->errorclass != data->data.errorclass) return;
692                 break;
693         default:
694                 break;
695         }
696
697         (*data->handler_cb) (msg, data->user_data);
698 }
699
700 /*
701  * Run each handler with matching criteria (first per-message then global
702  * handlers). If a handler requeues a message, we stop processing and terminate
703  * the current request. 
704  *
705  * After running all handlers, if there is an error set or the invoke type was
706  * post_body, issue the final callback.  
707  *
708  * FIXME: If the errorcode is changed by a handler, we should restart the
709  * processing.  
710  */
711 gboolean
712 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
713 {
714         GSList *list;
715         SoupHandlerData *data;
716
717         g_return_val_if_fail (msg != NULL, FALSE);
718
719         for (list = msg->priv->content_handlers; list; list = list->next) {
720                 data = list->data;
721
722                 run_handler (msg, invoke_type, data);
723
724                 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
725         }
726
727         for (data = global_handlers; data->type; data++) {
728                 run_handler (msg, invoke_type, data);
729
730                 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
731         }
732
733         /*
734          * Issue final callback if the invoke_type is POST_BODY and the error
735          * class is not INFORMATIONAL. 
736          */
737         if (invoke_type == SOUP_HANDLER_POST_BODY && 
738             msg->errorclass != SOUP_ERROR_CLASS_INFORMATIONAL) {
739                 soup_message_issue_callback (msg);
740                 return TRUE;
741         }
742
743         return FALSE;
744 }
745
746 static void 
747 add_handler (SoupMessage      *msg,
748              SoupHandlerType   type,
749              SoupCallbackFn    handler_cb,
750              gpointer          user_data,
751              SoupHandlerKind   kind,
752              const gchar      *header,
753              guint             errorcode,
754              guint             errorclass)
755 {
756         SoupHandlerData *data;
757
758         data = g_new0 (SoupHandlerData, 1);
759         data->type = type;
760         data->handler_cb = handler_cb;
761         data->user_data = user_data;
762         data->kind = kind;
763
764         switch (kind) {
765         case RESPONSE_HEADER_HANDLER:
766                 data->data.header = header;
767                 break;
768         case RESPONSE_ERROR_CODE_HANDLER:
769                 data->data.errorcode = errorcode;
770                 break;
771         case RESPONSE_ERROR_CLASS_HANDLER:
772                 data->data.errorclass = errorclass;
773                 break;
774         default:
775                 break;
776         }
777
778         msg->priv->content_handlers = 
779                 g_slist_append (msg->priv->content_handlers, data);
780 }
781
782 void 
783 soup_message_add_header_handler (SoupMessage      *msg,
784                                  const gchar      *header,
785                                  SoupHandlerType   type,
786                                  SoupCallbackFn    handler_cb,
787                                  gpointer          user_data)
788 {
789         g_return_if_fail (msg != NULL);
790         g_return_if_fail (header != NULL);
791         g_return_if_fail (handler_cb != NULL);
792
793         add_handler (msg, 
794                      type, 
795                      handler_cb, 
796                      user_data, 
797                      RESPONSE_HEADER_HANDLER, 
798                      header, 
799                      0,
800                      0);
801 }
802
803 void 
804 soup_message_add_error_code_handler (SoupMessage      *msg,
805                                      guint             errorcode,
806                                      SoupHandlerType   type,
807                                      SoupCallbackFn    handler_cb,
808                                      gpointer          user_data)
809 {
810         g_return_if_fail (msg != NULL);
811         g_return_if_fail (errorcode != 0);
812         g_return_if_fail (handler_cb != NULL);
813
814         add_handler (msg, 
815                      type, 
816                      handler_cb, 
817                      user_data, 
818                      RESPONSE_ERROR_CODE_HANDLER, 
819                      NULL, 
820                      errorcode,
821                      0);
822 }
823
824 void 
825 soup_message_add_error_class_handler (SoupMessage      *msg,
826                                       SoupErrorClass    errorclass,
827                                       SoupHandlerType   type,
828                                       SoupCallbackFn    handler_cb,
829                                       gpointer          user_data)
830 {
831         g_return_if_fail (msg != NULL);
832         g_return_if_fail (errorclass != 0);
833         g_return_if_fail (handler_cb != NULL);
834
835         add_handler (msg, 
836                      type, 
837                      handler_cb, 
838                      user_data, 
839                      RESPONSE_ERROR_CLASS_HANDLER, 
840                      NULL, 
841                      0,
842                      errorclass);
843 }
844
845 void 
846 soup_message_add_handler (SoupMessage      *msg,
847                           SoupHandlerType   type,
848                           SoupCallbackFn    handler_cb,
849                           gpointer          user_data)
850 {
851         g_return_if_fail (msg != NULL);
852         g_return_if_fail (handler_cb != NULL);
853
854         add_handler (msg, 
855                      type, 
856                      handler_cb, 
857                      user_data, 
858                      0, 
859                      NULL, 
860                      0,
861                      0);
862 }
863
864 void
865 soup_message_remove_handler (SoupMessage     *msg, 
866                              SoupHandlerType  type,
867                              SoupCallbackFn   handler_cb,
868                              gpointer         user_data)
869 {
870         GSList *iter = msg->priv->content_handlers;
871
872         while (iter) {
873                 SoupHandlerData *data = iter->data;
874
875                 if (data->handler_cb == handler_cb &&
876                     data->user_data == user_data &&
877                     data->type == type) {
878                         msg->priv->content_handlers = 
879                                 g_slist_remove_link (
880                                         msg->priv->content_handlers,
881                                         iter);
882                         g_free (data);
883                         break;
884                 }
885                 
886                 iter = iter->next;
887         }
888 }
889
890 static inline gboolean
891 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
892 {
893         return ((newflags & find) && !(msg->priv->msg_flags & find));
894 }
895
896 static inline gboolean
897 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
898 {
899         return (!(newflags & find) && (msg->priv->msg_flags & find));
900 }
901
902 void
903 soup_message_set_flags (SoupMessage *msg, guint flags)
904 {
905         g_return_if_fail (msg != NULL);
906
907         msg->priv->msg_flags = flags;
908 }
909
910 guint
911 soup_message_get_flags (SoupMessage *msg)
912 {
913         g_return_val_if_fail (msg != NULL, 0);
914
915         return msg->priv->msg_flags;
916 }
917
918 void 
919 soup_message_set_http_version  (SoupMessage *msg, SoupHttpVersion version)
920 {
921         g_return_if_fail (msg != NULL);
922
923         msg->priv->http_version = version;
924 }
925
926 void
927 soup_message_set_error (SoupMessage *msg, SoupKnownErrorCode errcode)
928 {
929         g_return_if_fail (msg != NULL);
930         g_return_if_fail (errcode != 0);
931
932         g_free ((gchar *) msg->errorphrase);
933
934         msg->errorcode = errcode;
935         msg->errorclass = soup_error_get_class (errcode);
936         msg->errorphrase = g_strdup (soup_error_get_phrase (errcode));
937 }
938
939 void
940 soup_message_set_error_full (SoupMessage *msg, 
941                              guint        errcode, 
942                              const gchar *errphrase)
943 {
944         g_return_if_fail (msg != NULL);
945         g_return_if_fail (errcode != 0);
946         g_return_if_fail (errphrase != NULL);
947
948         g_free ((gchar *) msg->errorphrase);
949
950         msg->errorcode = errcode;
951         msg->errorclass = soup_error_get_class (errcode);
952         msg->errorphrase = g_strdup (errphrase);
953 }
954
955 void
956 soup_message_set_handler_error (SoupMessage *msg, 
957                                 guint        errcode, 
958                                 const gchar *errphrase)
959 {
960         g_return_if_fail (msg != NULL);
961         g_return_if_fail (errcode != 0);
962         g_return_if_fail (errphrase != NULL);
963
964         g_free ((gchar *) msg->errorphrase);
965
966         msg->errorcode = errcode;
967         msg->errorclass = SOUP_ERROR_CLASS_HANDLER;
968         msg->errorphrase = g_strdup (errphrase);
969 }