1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-message.c: Asyncronous Callback-based SOAP Request Queue.
6 * Alex Graveley (alex@helixcode.com)
8 * Copyright (C) 2000, Helix Code, Inc.
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"
18 authorize_handler (SoupMessage *msg, gboolean proxy)
20 const char *auth_header;
24 ctx = proxy ? soup_get_proxy () : msg->context;
27 soup_message_get_response_header (
29 proxy ? "Proxy-Authenticate" : "WWW-Authenticate");
30 if (!auth_header) return SOUP_ERROR_CANT_AUTHENTICATE;
32 auth = soup_auth_new_from_header (ctx, auth_header);
33 if (!auth) return SOUP_ERROR_MALFORMED_HEADER;
36 if (soup_auth_invalidates_prior (auth))
37 soup_auth_free (ctx->auth);
39 soup_auth_free (auth);
40 return SOUP_ERROR_CANT_AUTHENTICATE;
46 if (msg->priv->req_header) {
47 g_string_free (msg->priv->req_header, TRUE);
48 msg->priv->req_header = NULL;
51 soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
53 return SOUP_ERROR_NONE;
58 * @context: a %SoupContext for the destination endpoint.
59 * @action: a string which will be used as the SOAPAction header for the created
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.
65 * Return value: the new %SoupMessage.
68 soup_message_new (SoupContext *context, SoupAction action)
72 g_return_val_if_fail (context, NULL);
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;
81 soup_context_ref (context);
84 * Add a 401 (Authorization Required) response code handler if the
85 * context URI has a login user name.
87 if (soup_context_get_uri (context)->user)
88 soup_message_add_response_code_handler (
91 SOUP_HANDLER_POST_BODY,
92 (SoupHandlerFn) authorize_handler,
93 GINT_TO_POINTER (FALSE));
96 * Always add a 407 (Proxy-Authorization Required) handler, in case the
97 * proxy is reset after message creation.
99 soup_message_add_response_code_handler (
102 SOUP_HANDLER_POST_BODY,
103 (SoupHandlerFn) authorize_handler,
104 GINT_TO_POINTER (TRUE));
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
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.
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
123 * Return value: the new %SoupMessage.
126 soup_message_new_full (SoupContext *context,
128 SoupOwnership req_owner,
132 SoupMessage *ret = soup_message_new (context, action);
134 ret->request.owner = req_owner;
135 ret->request.body = req_body;
136 ret->request.length = req_length;
142 * soup_message_cleanup:
143 * @req: a %SoupMessage.
144 * @action: a string which will be used as the SOAPAction header for the created
147 * Frees any temporary resources created in the processing of @req. Request and
148 * response data buffers are left intact.
151 soup_message_cleanup (SoupMessage *req)
153 g_return_if_fail (req != NULL);
155 if (req->priv->read_tag) {
156 soup_transfer_read_cancel (req->priv->read_tag);
157 req->priv->read_tag = 0;
160 if (req->priv->write_tag) {
161 soup_transfer_write_cancel (req->priv->write_tag);
162 req->priv->write_tag = 0;
165 if (req->priv->connect_tag) {
166 soup_context_cancel_connect (req->priv->connect_tag);
167 req->priv->connect_tag = NULL;
169 if (req->priv->conn) {
170 soup_connection_release (req->priv->conn);
171 req->priv->conn = NULL;
174 soup_active_requests = g_slist_remove (soup_active_requests, req);
178 soup_message_remove_header (gchar *name, gchar *value, gpointer unused)
186 * @req: a %SoupMessage to destroy.
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.
194 soup_message_free (SoupMessage *req)
196 g_return_if_fail (req != NULL);
198 soup_message_cleanup (req);
200 soup_context_unref (req->context);
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);
207 if (req->priv->req_header)
208 g_string_free (req->priv->req_header, TRUE);
210 if (req->request_headers) {
211 g_hash_table_foreach (req->request_headers,
212 (GHFunc) soup_message_remove_header,
214 g_hash_table_destroy (req->request_headers);
217 if (req->response_headers) {
218 g_hash_table_foreach (req->response_headers,
219 (GHFunc) soup_message_remove_header,
221 g_hash_table_destroy (req->response_headers);
224 g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
225 g_slist_free (req->priv->content_handlers);
228 g_free (req->action);
233 * soup_message_issue_callback:
234 * @req: a %SoupMessage currently being processed.
235 * @error: a %SoupErrorCode to be passed to %req's completion callback.
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.
243 soup_message_issue_callback (SoupMessage *req, SoupErrorCode error)
245 g_return_if_fail (req != NULL);
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);
252 req->priv->errorcode = error;
254 if (req->priv->callback) {
255 (*req->priv->callback) (req, error, req->priv->user_data);
257 /* Free it only if callback exist, its probably a sync call */
258 if (req->status != SOUP_STATUS_QUEUED)
259 soup_message_free (req);
264 * soup_message_cancel:
265 * @req: a %SoupMessage currently being processed.
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.
272 soup_message_cancel (SoupMessage *req)
274 soup_message_issue_callback (req, SOUP_ERROR_CANCELLED);
278 soup_message_set_header (GHashTable **hash,
282 gpointer old_name, old_value;
285 *hash = g_hash_table_new (soup_str_case_hash,
286 soup_str_case_equal);
287 else if (g_hash_table_lookup_extended (*hash,
291 g_hash_table_remove (*hash, name);
297 g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
301 * soup_message_set_request_header:
302 * @req: a %SoupMessage.
303 * @name: header name.
304 * @value: header value.
306 * Adds a new transport header to be sent on an outgoing request. Passing a NULL
307 * @value will remove the header name supplied.
310 soup_message_set_request_header (SoupMessage *req,
314 g_return_if_fail (req != NULL);
315 g_return_if_fail (name != NULL || name [0] != '\0');
317 if (req->priv->req_header) {
318 g_string_free (req->priv->req_header, TRUE);
319 req->priv->req_header = NULL;
322 soup_message_set_header (&req->request_headers, name, value);
326 * soup_message_get_request_header:
327 * @req: a %SoupMessage.
328 * @name: header name.
330 * Lookup the transport request header with a key equal to @name.
332 * Return value: the header's value or NULL if not found.
335 soup_message_get_request_header (SoupMessage *req,
338 g_return_val_if_fail (req != NULL, NULL);
339 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
341 return req->request_headers ?
342 g_hash_table_lookup (req->request_headers, name) : NULL;
346 * soup_message_set_response_header:
347 * @req: a %SoupMessage.
348 * @name: header name.
349 * @value: header value.
351 * Adds a new transport header to be sent on an outgoing response. Passing a
352 * NULL @value will remove the header name supplied.
355 soup_message_set_response_header (SoupMessage *req,
359 g_return_if_fail (req != NULL);
360 g_return_if_fail (name != NULL || name [0] != '\0');
362 soup_message_set_header (&req->response_headers, name, value);
366 * soup_message_get_response_header:
367 * @req: a %SoupMessage.
368 * @name: header name.
370 * Lookup the transport response header with a key equal to @name.
372 * Return value: the header's value or NULL if not found.
375 soup_message_get_response_header (SoupMessage *req,
378 g_return_val_if_fail (req != NULL, NULL);
379 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
381 return req->response_headers ?
382 g_hash_table_lookup (req->response_headers, name) : NULL;
387 * @msg: a %SoupMessage.
389 * Syncronously send @msg. This call will not return until the transfer is
390 * finished successfully or there is an unrecoverable error.
392 * @msg is not free'd upon return.
394 * Return value: the %SoupErrorCode of the error encountered while sending, or
398 soup_message_send (SoupMessage *msg)
400 soup_message_queue (msg, NULL, NULL);
403 g_main_iteration (TRUE);
404 if (msg->status == SOUP_STATUS_FINISHED ||
405 msg->priv->errorcode != SOUP_ERROR_NONE)
406 return msg->priv->errorcode;
409 return SOUP_ERROR_NONE;
413 soup_message_set_method (SoupMessage *msg, const gchar *method)
415 g_return_if_fail (msg != NULL);
416 g_return_if_fail (method != NULL);
418 msg->method = method;
422 soup_message_get_method (SoupMessage *msg)
424 g_return_val_if_fail (msg != NULL, NULL);
430 RESPONSE_HEADER_HANDLER,
431 RESPONSE_CODE_HANDLER,
432 RESPONSE_BODY_HANDLER
436 SoupHandlerType type;
437 SoupHandlerFn handler_cb;
440 SoupHandlerKind kind;
446 soup_message_add_handler (SoupMessage *msg,
447 SoupHandlerType type,
448 SoupHandlerFn handler_cb,
450 SoupHandlerKind kind,
454 SoupHandlerData *data;
456 data = g_new0 (SoupHandlerData, 1);
458 data->handler_cb = handler_cb;
459 data->user_data = user_data;
461 data->header = header;
464 msg->priv->content_handlers =
465 g_slist_append (msg->priv->content_handlers, data);
469 soup_message_add_header_handler (SoupMessage *msg,
471 SoupHandlerType type,
472 SoupHandlerFn handler_cb,
475 g_return_if_fail (msg != NULL);
476 g_return_if_fail (header != NULL);
477 g_return_if_fail (handler_cb != NULL);
479 soup_message_add_handler (msg,
483 RESPONSE_HEADER_HANDLER,
489 soup_message_add_response_code_handler (SoupMessage *msg,
491 SoupHandlerType type,
492 SoupHandlerFn handler_cb,
495 g_return_if_fail (msg != NULL);
496 g_return_if_fail (code != 0);
497 g_return_if_fail (handler_cb != NULL);
499 soup_message_add_handler (msg,
503 RESPONSE_CODE_HANDLER,
509 soup_message_add_body_handler (SoupMessage *msg,
510 SoupHandlerType type,
511 SoupHandlerFn handler_cb,
514 g_return_if_fail (msg != NULL);
515 g_return_if_fail (handler_cb != NULL);
517 soup_message_add_handler (msg,
521 RESPONSE_BODY_HANDLER,
527 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
530 SoupErrorCode retval = SOUP_ERROR_NONE;
532 g_return_val_if_fail (msg != NULL, retval);
534 for (list = msg->priv->content_handlers; list; list = list->next) {
535 SoupHandlerData *data = list->data;
537 if (data->type != invoke_type) continue;
539 switch (data->kind) {
540 case RESPONSE_HEADER_HANDLER:
541 if (!soup_message_get_response_header (msg,
545 case RESPONSE_CODE_HANDLER:
546 if (msg->response_code != data->code) continue;
548 case RESPONSE_BODY_HANDLER:
552 retval = (*data->handler_cb) (msg, data->user_data);
554 if (retval != SOUP_ERROR_NONE) break;
555 if (msg->status == SOUP_STATUS_QUEUED) break;
562 soup_message_remove_handler (SoupMessage *msg,
563 SoupHandlerFn handler_cb,
566 GSList *iter = msg->priv->content_handlers;
569 SoupHandlerData *data = iter->data;
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,
586 redirect_handler (SoupMessage *msg, gpointer user_data)
588 const gchar *new_url;
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 */
598 return SOUP_ERROR_NONE;
601 if (!(msg->priv->msg_flags & SOUP_MESSAGE_FOLLOW_REDIRECT))
602 return SOUP_ERROR_NONE;
604 new_url = soup_message_get_response_header (msg, "Location");
607 SoupContext *new_ctx = soup_context_get (new_url);
608 if (!new_ctx) return SOUP_ERROR_MALFORMED_HEADER;
610 soup_context_unref (msg->context);
611 msg->context = new_ctx;
613 soup_message_queue (msg,
615 msg->priv->user_data);
618 return SOUP_ERROR_NONE;
621 static inline gboolean
622 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
624 return ((newflags & find) && !(msg->priv->msg_flags & find));
627 static inline gboolean
628 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
630 return (!(newflags & find) && (msg->priv->msg_flags & find));
634 soup_message_set_flags (SoupMessage *msg, guint flags)
636 g_return_if_fail (msg != NULL);
638 if (ADDED_FLAG (msg, flags, SOUP_MESSAGE_FOLLOW_REDIRECT))
639 soup_message_add_header_handler (msg,
641 SOUP_HANDLER_PRE_BODY,
644 else if (REMOVED_FLAG (msg, flags, SOUP_MESSAGE_FOLLOW_REDIRECT))
645 soup_message_remove_handler (msg,
649 msg->priv->msg_flags = flags;
653 soup_message_get_flags (SoupMessage *msg)
655 return msg->priv->msg_flags;