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