Stop processing if a handler requeues the message.
[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
114         soup_active_requests = g_slist_remove (soup_active_requests, req);
115 }
116
117 static void
118 soup_message_remove_header (gchar *name, gchar *value, gpointer unused)
119 {
120         g_free (name);
121         g_free (value);
122 }
123
124 /**
125  * soup_message_free:
126  * @req: a %SoupMessage to destroy.
127  * 
128  * Destroys the %SoupMessage pointed to by @req. Request and response headers
129  * are freed. Request and response data buffers are also freed if their
130  * ownership is %SOUP_BUFFER_SYSTEM_OWNED. The message's destination context
131  * will be de-referenced.
132  */
133 void 
134 soup_message_free (SoupMessage *req)
135 {
136         g_return_if_fail (req != NULL);
137
138         soup_message_cleanup (req);
139
140         soup_context_unref (req->context);
141
142         if (req->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
143                 g_free (req->request.body);
144         if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
145                 g_free (req->response.body);
146
147         if (req->priv->req_header) 
148                 g_string_free (req->priv->req_header, TRUE);
149
150         if (req->request_headers) {
151                 g_hash_table_foreach (req->request_headers,
152                                       (GHFunc) soup_message_remove_header,
153                                       NULL);
154                 g_hash_table_destroy (req->request_headers);
155         }
156
157         if (req->response_headers) {
158                 g_hash_table_foreach (req->response_headers,
159                                       (GHFunc) soup_message_remove_header,
160                                       NULL);
161                 g_hash_table_destroy (req->response_headers);
162         }
163
164         g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
165         g_slist_free (req->priv->content_handlers);
166
167         g_free (req->priv);
168         g_free (req->action);
169         g_free (req);
170 }
171
172 /**
173  * soup_message_issue_callback:
174  * @req: a %SoupMessage currently being processed.
175  * @error: a %SoupErrorCode to be passed to %req's completion callback.
176  * 
177  * Finalizes the message request, by first freeing any temporary resources, then
178  * issuing the callback function pointer passed in %soup_message_new or
179  * %soup_message_new_full. If, after returning from the callback, the message
180  * has not been requeued, @msg is destroyed using %soup_message_free.
181  */
182 void
183 soup_message_issue_callback (SoupMessage *req, SoupErrorCode error)
184 {
185         g_return_if_fail (req != NULL);
186
187         /* make sure we don't have some icky recursion if the callback 
188            runs the main loop, and the connection has some data or error 
189            which causes the callback to be run again */
190         soup_message_cleanup (req);
191
192         req->priv->errorcode = error;
193
194         if (req->priv->callback) {
195                 (*req->priv->callback) (req, error, req->priv->user_data);
196
197                 /* Free it only if callback exist, its probably a sync call */
198                 if (req->status != SOUP_STATUS_QUEUED)
199                         soup_message_free (req);
200         }
201 }
202
203 /**
204  * soup_message_cancel:
205  * @req: a %SoupMessage currently being processed.
206  * 
207  * Cancel a running message, and issue completion callback with a
208  * %SoupTransferStatus of %SOUP_ERROR_CANCELLED. If not requeued by the
209  * completion callback, the @msg will be destroyed.
210  */
211 void 
212 soup_message_cancel (SoupMessage *req) 
213 {
214         soup_message_issue_callback (req, SOUP_ERROR_CANCELLED);
215 }
216
217 static void 
218 soup_message_set_header (GHashTable  **hash,
219                          const gchar  *name,
220                          const gchar  *value) 
221 {
222         gpointer old_name, old_value;
223
224         if (!*hash) 
225                 *hash = g_hash_table_new (soup_str_case_hash, 
226                                           soup_str_case_equal);
227         else if (g_hash_table_lookup_extended (*hash, 
228                                                name, 
229                                                &old_name, 
230                                                &old_value)) {
231                 g_hash_table_remove (*hash, name);
232                 g_free (old_name);
233                 g_free (old_value);
234         }
235
236         g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
237 }
238
239 /**
240  * soup_message_set_request_header:
241  * @req: a %SoupMessage.
242  * @name: header name.
243  * @value: header value.
244  * 
245  * Adds a new transport header to be sent on an outgoing request.
246  */
247 void
248 soup_message_set_request_header (SoupMessage *req,
249                                  const gchar *name,
250                                  const gchar *value) 
251 {
252         g_return_if_fail (req != NULL);
253         g_return_if_fail (name != NULL || name [0] != '\0');
254
255         if (req->priv->req_header) {
256                 g_string_free (req->priv->req_header, TRUE);
257                 req->priv->req_header = NULL;
258         }
259
260         soup_message_set_header (&req->request_headers, name, value);
261 }
262
263 /**
264  * soup_message_get_request_header:
265  * @req: a %SoupMessage.
266  * @name: header name.
267  * 
268  * Lookup the transport request header with a key equal to @name.
269  *
270  * Return value: the header's value or NULL if not found.
271  */
272 const gchar *
273 soup_message_get_request_header (SoupMessage *req,
274                                  const gchar *name) 
275 {
276         g_return_val_if_fail (req != NULL, NULL);
277         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
278
279         return req->request_headers ? 
280                 g_hash_table_lookup (req->request_headers, name) : NULL;
281 }
282
283 /**
284  * soup_message_set_response_header:
285  * @req: a %SoupMessage.
286  * @name: header name.
287  * @value: header value.
288  * 
289  * Adds a new transport header to be sent on an outgoing response.
290  */
291 void
292 soup_message_set_response_header (SoupMessage *req,
293                                   const gchar *name,
294                                   const gchar *value) 
295 {
296         g_return_if_fail (req != NULL);
297         g_return_if_fail (name != NULL || name [0] != '\0');
298
299         soup_message_set_header (&req->response_headers, name, value);
300 }
301
302 /**
303  * soup_message_get_response_header:
304  * @req: a %SoupMessage.
305  * @name: header name.
306  * 
307  * Lookup the transport response header with a key equal to @name.
308  *
309  * Return value: the header's value or NULL if not found.
310  */
311 const gchar *
312 soup_message_get_response_header (SoupMessage *req,
313                                   const gchar *name) 
314 {
315         g_return_val_if_fail (req != NULL, NULL);
316         g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
317
318         return req->response_headers ? 
319                 g_hash_table_lookup (req->response_headers, name) : NULL;
320 }
321
322 /**
323  * soup_message_send:
324  * @msg: a %SoupMessage.
325  * 
326  * Syncronously send @msg. This call will not return until the transfer is
327  * finished successfully or there is an unrecoverable error. 
328  *
329  * @msg is not free'd upon return.
330  *
331  * Return value: the %SoupErrorCode of the error encountered while sending, or
332  * SOUP_ERROR_NONE.
333  */
334 SoupErrorCode 
335 soup_message_send (SoupMessage *msg)
336 {
337         soup_message_queue (msg, NULL, NULL);
338
339         while (1) {
340                 g_main_iteration (TRUE); 
341                 if (msg->status == SOUP_STATUS_FINISHED ||
342                     msg->priv->errorcode != SOUP_ERROR_NONE)
343                         return msg->priv->errorcode;
344         }
345
346         return SOUP_ERROR_NONE;
347 }
348
349 void
350 soup_message_set_method (SoupMessage *msg, const gchar *method)
351 {
352         g_return_if_fail (msg != NULL);
353         g_return_if_fail (method != NULL);
354
355         msg->method = method;
356 }
357
358 const gchar *
359 soup_message_get_method (SoupMessage *msg)
360 {
361         g_return_val_if_fail (msg != NULL, NULL);
362
363         return msg->method;
364 }
365
366 typedef enum {
367         RESPONSE_HEADER_HANDLER,
368         RESPONSE_CODE_HANDLER,
369         RESPONSE_BODY_HANDLER
370 } SoupHandlerKind;
371
372 typedef struct {
373         SoupHandlerType   type;
374         SoupHandlerFn     handler_cb;
375         gpointer          user_data;
376
377         SoupHandlerKind   kind;
378         const gchar      *header;
379         guint             code;
380 } SoupHandlerData;
381
382 static void 
383 soup_message_add_handler (SoupMessage      *msg,
384                           SoupHandlerType   type,
385                           SoupHandlerFn     handler_cb,
386                           gpointer          user_data,
387                           SoupHandlerKind   kind,
388                           const gchar      *header,
389                           guint             code)
390 {
391         SoupHandlerData *data;
392
393         data = g_new0 (SoupHandlerData, 1);
394         data->type = type;
395         data->handler_cb = handler_cb;
396         data->user_data = user_data;
397         data->kind = kind;
398         data->header = header;
399         data->code = code;
400
401         msg->priv->content_handlers = 
402                 g_slist_append (msg->priv->content_handlers, data);
403 }
404
405 void 
406 soup_message_add_header_handler (SoupMessage      *msg,
407                                  const gchar      *header,
408                                  SoupHandlerType   type,
409                                  SoupHandlerFn     handler_cb,
410                                  gpointer          user_data)
411 {
412         g_return_if_fail (msg != NULL);
413         g_return_if_fail (header != NULL);
414         g_return_if_fail (handler_cb != NULL);
415
416         soup_message_add_handler (msg, 
417                                   type, 
418                                   handler_cb, 
419                                   user_data, 
420                                   RESPONSE_HEADER_HANDLER, 
421                                   header, 
422                                   0);
423 }
424
425 void 
426 soup_message_add_response_code_handler (SoupMessage      *msg,
427                                         guint             code,
428                                         SoupHandlerType   type,
429                                         SoupHandlerFn     handler_cb,
430                                         gpointer          user_data)
431 {
432         g_return_if_fail (msg != NULL);
433         g_return_if_fail (code != 0);
434         g_return_if_fail (handler_cb != NULL);
435
436         soup_message_add_handler (msg, 
437                                   type, 
438                                   handler_cb, 
439                                   user_data, 
440                                   RESPONSE_CODE_HANDLER, 
441                                   NULL, 
442                                   code);
443 }
444
445 void 
446 soup_message_add_body_handler (SoupMessage      *msg,
447                                SoupHandlerType   type,
448                                SoupHandlerFn     handler_cb,
449                                gpointer          user_data)
450 {
451         g_return_if_fail (msg != NULL);
452         g_return_if_fail (handler_cb != NULL);
453
454         soup_message_add_handler (msg, 
455                                   type, 
456                                   handler_cb, 
457                                   user_data, 
458                                   RESPONSE_BODY_HANDLER, 
459                                   NULL, 
460                                   0);
461 }
462
463 SoupErrorCode 
464 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
465 {
466         GSList *list;
467         SoupErrorCode retval = SOUP_ERROR_NONE;
468
469         g_return_val_if_fail (msg != NULL, retval);
470         
471         for (list = msg->priv->content_handlers; list; list = list->next) {
472                 SoupHandlerData *data = list->data;
473                 
474                 if (data->type != invoke_type) continue;
475
476                 switch (data->kind) {
477                 case RESPONSE_HEADER_HANDLER:
478                         if (!soup_message_get_response_header (msg,
479                                                                data->header))
480                                 continue;
481                         break;
482                 case RESPONSE_CODE_HANDLER:
483                         if (msg->response_code != data->code) continue;
484                         break;
485                 }
486
487                 retval = (*data->handler_cb) (msg, data->user_data);
488
489                 if (retval != SOUP_ERROR_NONE) break;
490                 if (msg->status == SOUP_STATUS_QUEUED) break;
491         }
492
493         return retval;
494 }
495
496 static SoupErrorCode 
497 soup_message_redirect (SoupMessage *msg, gpointer user_data)
498 {
499         const gchar *new_url;
500
501         switch (msg->response_code) {
502         case 300: /* Multiple Choices */
503         case 301: /* Moved Permanently */
504         case 302: /* Moved Temporarily */
505         case 303: /* See Other */
506         case 305: /* Use Proxy */
507                 break;
508         default:
509                 return SOUP_ERROR_NONE;
510         }
511
512         if (!(msg->priv->flags & SOUP_MESSAGE_FOLLOW_REDIRECT)) 
513                 return SOUP_ERROR_NONE;
514
515         new_url = soup_message_get_response_header (msg, "Location");
516         if (new_url) {
517                 soup_context_unref (msg->context);
518                 msg->context = soup_context_get (new_url);
519
520                 if (!msg->context) return SOUP_ERROR_MALFORMED_HEADER;
521
522                 soup_message_queue (msg,
523                                     msg->priv->callback, 
524                                     msg->priv->user_data);
525         }
526
527         return SOUP_ERROR_NONE;
528 }
529
530 void
531 soup_message_set_flags (SoupMessage *msg, guint flags)
532 {
533         g_return_if_fail (msg != NULL);
534
535         if ((flags & SOUP_MESSAGE_FOLLOW_REDIRECT) &&
536             !(msg->priv->msg_flags & SOUP_MESSAGE_FOLLOW_REDIRECT))
537                 soup_message_add_header_handler (msg,
538                                                  "Location",
539                                                  SOUP_HANDLER_PRE_BODY,
540                                                  soup_message_redirect,
541                                                  NULL);
542
543         msg->priv->msg_flags = flags;
544 }
545
546 guint
547 soup_message_get_flags (SoupMessage *msg)
548 {
549         return msg->priv->msg_flags;
550 }