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-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"
22 * @context: a %SoupContext for the destination endpoint.
23 * @method: a string which will be used as the HTTP method for the created
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.
29 * Return value: the new %SoupMessage.
32 soup_message_new (SoupContext *context, const gchar *method)
36 g_return_val_if_fail (context, NULL);
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;
44 ret->request_headers = g_hash_table_new (soup_str_case_hash,
47 ret->response_headers = g_hash_table_new (soup_str_case_hash,
50 ret->priv->http_version = SOUP_HTTP_1_1;
52 soup_context_ref (context);
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
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.
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
71 * Return value: the new %SoupMessage.
74 soup_message_new_full (SoupContext *context,
76 SoupOwnership req_owner,
80 SoupMessage *ret = soup_message_new (context, method);
82 ret->request.owner = req_owner;
83 ret->request.body = req_body;
84 ret->request.length = req_length;
90 * soup_message_cleanup:
91 * @req: a %SoupMessage.
93 * Frees any temporary resources created in the processing of @req. Request and
94 * response data buffers are left intact.
97 soup_message_cleanup (SoupMessage *req)
99 g_return_if_fail (req != NULL);
101 if (req->priv->read_tag) {
102 soup_transfer_read_cancel (req->priv->read_tag);
103 req->priv->read_tag = 0;
106 if (req->priv->write_tag) {
107 soup_transfer_write_cancel (req->priv->write_tag);
108 req->priv->write_tag = 0;
111 if (req->priv->connect_tag) {
112 soup_context_cancel_connect (req->priv->connect_tag);
113 req->priv->connect_tag = NULL;
115 if (req->connection) {
116 soup_connection_release (req->connection);
117 req->connection = NULL;
120 soup_active_requests = g_slist_remove (soup_active_requests, req);
124 finalize_message (SoupMessage *req)
126 soup_context_unref (req->context);
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);
133 if (req->priv->req_header)
134 g_string_free (req->priv->req_header, TRUE);
136 soup_message_clear_headers (req->request_headers);
137 g_hash_table_destroy (req->request_headers);
139 soup_message_clear_headers (req->response_headers);
140 g_hash_table_destroy (req->response_headers);
142 g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
143 g_slist_free (req->priv->content_handlers);
145 g_free ((gchar *) req->errorphrase);
152 * @req: a %SoupMessage to destroy.
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.
160 soup_message_free (SoupMessage *req)
162 g_return_if_fail (req != NULL);
164 soup_message_cleanup (req);
166 finalize_message (req);
170 * soup_message_issue_callback:
171 * @req: a %SoupMessage currently being processed.
172 * @error: a %SoupErrorCode to be passed to %req's completion callback.
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.
180 soup_message_issue_callback (SoupMessage *req)
182 g_return_if_fail (req != NULL);
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.
189 soup_message_cleanup (req);
191 if (req->priv->callback) {
192 (*req->priv->callback) (req, req->priv->user_data);
194 if (req->status != SOUP_STATUS_QUEUED)
195 finalize_message (req);
200 * soup_message_cancel:
201 * @req: a %SoupMessage currently being processed.
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.
208 soup_message_cancel (SoupMessage *msg)
210 soup_message_set_error (msg, SOUP_ERROR_CANCELLED);
211 soup_message_issue_callback (msg);
215 foreach_free_header_list (gchar *name, GSList *vals, gpointer notused)
218 g_slist_foreach (vals, (GFunc) g_free, NULL);
225 soup_message_clear_headers (GHashTable *hash)
227 g_return_if_fail (hash != NULL);
229 g_hash_table_foreach_remove (hash,
230 (GHRFunc) foreach_free_header_list,
235 soup_message_add_header (GHashTable *hash,
239 gboolean exists = FALSE;
243 g_return_if_fail (hash != NULL);
244 g_return_if_fail (name != NULL || name [0] != '\0');
246 exists = g_hash_table_lookup_extended (hash,
249 (gpointer *) &old_value);
252 old_value = g_slist_append (old_value,
254 else if (value && !exists)
255 g_hash_table_insert (hash,
257 g_slist_append (NULL,
259 else if (!value && exists) {
260 g_hash_table_remove (hash, name);
261 foreach_free_header_list (old_name, old_value, NULL);
266 * soup_message_get_header:
267 * @req: a %SoupMessage.
268 * @name: header name.
270 * Lookup the first transport header with a key equal to @name.
272 * Return value: the header's value or NULL if not found.
275 soup_message_get_header (GHashTable *hash,
280 g_return_val_if_fail (hash != NULL, NULL);
281 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
283 vals = g_hash_table_lookup (hash, name);
291 * soup_message_get_header_list:
292 * @req: a %SoupMessage.
293 * @name: header name.
295 * Lookup the all transport request headers with a key equal to @name.
297 * Return value: a const pointer to a GSList of header values or NULL if not
301 soup_message_get_header_list (GHashTable *hash,
304 g_return_val_if_fail (hash != NULL, NULL);
305 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
307 return g_hash_table_lookup (hash, name);
316 foreach_value_in_list (gchar *name, GSList *vals, ForeachData *data)
319 gchar *v = vals->data;
321 (*data->func) (name, v, data->user_data);
328 soup_message_foreach_header (GHashTable *hash,
332 ForeachData data = { func, user_data };
334 g_return_if_fail (hash != NULL);
335 g_return_if_fail (func != NULL);
337 g_hash_table_foreach (hash, (GHFunc) foreach_value_in_list, &data);
341 * soup_message_set_request_header:
342 * @req: a %SoupMessage.
343 * @name: header name.
344 * @value: header value.
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.
352 soup_message_set_request_header (SoupMessage *req,
356 g_return_if_fail (req != NULL);
357 g_return_if_fail (name != NULL || name [0] != '\0');
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");
363 soup_message_add_header (req->request_headers, name, value);
367 * soup_message_get_request_header:
368 * @req: a %SoupMessage.
369 * @name: header name.
373 * Lookup the first transport request header with a key equal to @name.
375 * Return value: the header's value or NULL if not found.
378 soup_message_get_request_header (SoupMessage *req,
382 g_return_val_if_fail (req != NULL, NULL);
383 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
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");
389 if (req->request_headers) {
390 vals = g_hash_table_lookup (req->request_headers, name);
399 * soup_message_set_response_header:
400 * @req: a %SoupMessage.
401 * @name: header name.
402 * @value: header value.
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.
410 soup_message_set_response_header (SoupMessage *req,
414 g_return_if_fail (req != NULL);
415 g_return_if_fail (name != NULL || name [0] != '\0');
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");
421 soup_message_add_header (req->response_headers, name, value);
425 * soup_message_get_response_header:
426 * @req: a %SoupMessage.
427 * @name: header name.
431 * Lookup the transport response header with a key equal to @name.
433 * Return value: the header's value or NULL if not found.
436 soup_message_get_response_header (SoupMessage *req,
440 g_return_val_if_fail (req != NULL, NULL);
441 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
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");
447 if (req->response_headers) {
448 vals = g_hash_table_lookup (req->response_headers, name);
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.
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.
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.
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
477 soup_message_queue (SoupMessage *req,
478 SoupCallbackFn callback,
481 soup_queue_message (req, callback, user_data);
486 * @msg: a %SoupMessage.
488 * Syncronously send @msg. This call will not return until the transfer is
489 * finished successfully or there is an unrecoverable error.
491 * @msg is not free'd upon return.
493 * Return value: the %SoupErrorClass of the error encountered while sending or
494 * reading the response.
497 soup_message_send (SoupMessage *msg)
499 soup_message_queue (msg, NULL, NULL);
502 g_main_iteration (TRUE);
503 if (msg->status == SOUP_STATUS_FINISHED ||
504 SOUP_ERROR_IS_TRANSPORT (msg->errorcode))
508 return msg->errorclass;
512 authorize_handler (SoupMessage *msg, gboolean proxy)
515 SoupAuth *auth, *old_auth;
519 ctx = proxy ? soup_get_proxy () : msg->context;
520 uri = soup_context_get_uri (ctx);
523 goto THROW_CANT_AUTHENTICATE;
525 vals = soup_message_get_header_list (msg->response_headers,
527 "Proxy-Authenticate" :
529 if (!vals) goto THROW_CANT_AUTHENTICATE;
531 auth = soup_auth_new_from_header_list (uri, vals);
533 soup_message_set_error_full (
536 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
537 SOUP_ERROR_CANT_AUTHENTICATE,
539 "Unknown authentication scheme required by "
541 "Unknown authentication scheme required");
545 old_auth = soup_auth_lookup (ctx);
547 if (!soup_auth_invalidates_prior (auth, old_auth)) {
548 soup_auth_free (auth);
549 goto THROW_CANT_AUTHENTICATE;
553 soup_auth_set_context (auth, ctx);
555 soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
559 THROW_CANT_AUTHENTICATE:
560 soup_message_set_error (msg,
562 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
563 SOUP_ERROR_CANT_AUTHENTICATE);
567 redirect_handler (SoupMessage *msg, gpointer user_data)
569 const gchar *new_loc;
571 if (msg->errorclass != SOUP_ERROR_CLASS_REDIRECT ||
572 msg->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT) return;
574 new_loc = soup_message_get_header (msg->response_headers, "Location");
577 const SoupUri *old_uri;
579 SoupContext *new_ctx, *old_ctx;
581 old_uri = soup_context_get_uri (msg->context);
583 new_uri = soup_uri_new (new_loc);
585 goto INVALID_REDIRECT;
588 * Copy auth info from original URI.
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);
596 new_ctx = soup_context_from_uri (new_uri);
598 soup_uri_free (new_uri);
601 goto INVALID_REDIRECT;
603 old_ctx = msg->context;
604 msg->context = new_ctx;
606 soup_context_unref (old_ctx);
608 soup_message_queue (msg,
610 msg->priv->user_data);
616 soup_message_set_error_full (msg,
617 SOUP_ERROR_MALFORMED,
618 "Invalid Redirect URL");
622 RESPONSE_HEADER_HANDLER = 1,
623 RESPONSE_ERROR_CODE_HANDLER,
624 RESPONSE_ERROR_CLASS_HANDLER
628 SoupHandlerType type;
629 SoupCallbackFn handler_cb;
632 SoupHandlerKind kind;
635 SoupErrorClass errorclass;
640 static SoupHandlerData global_handlers [] = {
642 * Handle redirect response codes 300, 301, 302, 303, and 305.
645 SOUP_HANDLER_PRE_BODY,
648 RESPONSE_HEADER_HANDLER,
649 { (guint) "Location" }
652 * Handle authorization.
655 SOUP_HANDLER_PRE_BODY,
656 (SoupCallbackFn) authorize_handler,
657 GINT_TO_POINTER (FALSE),
658 RESPONSE_ERROR_CODE_HANDLER,
662 * Handle proxy authorization.
665 SOUP_HANDLER_PRE_BODY,
666 (SoupCallbackFn) authorize_handler,
667 GINT_TO_POINTER (TRUE),
668 RESPONSE_ERROR_CODE_HANDLER,
675 run_handler (SoupMessage *msg,
676 SoupHandlerType invoke_type,
677 SoupHandlerData *data)
679 if (data->type != invoke_type) return;
681 switch (data->kind) {
682 case RESPONSE_HEADER_HANDLER:
683 if (!soup_message_get_header (msg->response_headers,
687 case RESPONSE_ERROR_CODE_HANDLER:
688 if (msg->errorcode != data->data.errorcode) return;
690 case RESPONSE_ERROR_CLASS_HANDLER:
691 if (msg->errorclass != data->data.errorclass) return;
697 (*data->handler_cb) (msg, data->user_data);
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.
705 * After running all handlers, if there is an error set or the invoke type was
706 * post_body, issue the final callback.
708 * FIXME: If the errorcode is changed by a handler, we should restart the
712 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
715 SoupHandlerData *data;
717 g_return_val_if_fail (msg != NULL, FALSE);
719 for (list = msg->priv->content_handlers; list; list = list->next) {
722 run_handler (msg, invoke_type, data);
724 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
727 for (data = global_handlers; data->type; data++) {
728 run_handler (msg, invoke_type, data);
730 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
734 * Issue final callback if the invoke_type is POST_BODY and the error
735 * class is not INFORMATIONAL.
737 if (invoke_type == SOUP_HANDLER_POST_BODY &&
738 msg->errorclass != SOUP_ERROR_CLASS_INFORMATIONAL) {
739 soup_message_issue_callback (msg);
747 add_handler (SoupMessage *msg,
748 SoupHandlerType type,
749 SoupCallbackFn handler_cb,
751 SoupHandlerKind kind,
756 SoupHandlerData *data;
758 data = g_new0 (SoupHandlerData, 1);
760 data->handler_cb = handler_cb;
761 data->user_data = user_data;
765 case RESPONSE_HEADER_HANDLER:
766 data->data.header = header;
768 case RESPONSE_ERROR_CODE_HANDLER:
769 data->data.errorcode = errorcode;
771 case RESPONSE_ERROR_CLASS_HANDLER:
772 data->data.errorclass = errorclass;
778 msg->priv->content_handlers =
779 g_slist_append (msg->priv->content_handlers, data);
783 soup_message_add_header_handler (SoupMessage *msg,
785 SoupHandlerType type,
786 SoupCallbackFn handler_cb,
789 g_return_if_fail (msg != NULL);
790 g_return_if_fail (header != NULL);
791 g_return_if_fail (handler_cb != NULL);
797 RESPONSE_HEADER_HANDLER,
804 soup_message_add_error_code_handler (SoupMessage *msg,
806 SoupHandlerType type,
807 SoupCallbackFn handler_cb,
810 g_return_if_fail (msg != NULL);
811 g_return_if_fail (errorcode != 0);
812 g_return_if_fail (handler_cb != NULL);
818 RESPONSE_ERROR_CODE_HANDLER,
825 soup_message_add_error_class_handler (SoupMessage *msg,
826 SoupErrorClass errorclass,
827 SoupHandlerType type,
828 SoupCallbackFn handler_cb,
831 g_return_if_fail (msg != NULL);
832 g_return_if_fail (errorclass != 0);
833 g_return_if_fail (handler_cb != NULL);
839 RESPONSE_ERROR_CLASS_HANDLER,
846 soup_message_add_handler (SoupMessage *msg,
847 SoupHandlerType type,
848 SoupCallbackFn handler_cb,
851 g_return_if_fail (msg != NULL);
852 g_return_if_fail (handler_cb != NULL);
865 soup_message_remove_handler (SoupMessage *msg,
866 SoupHandlerType type,
867 SoupCallbackFn handler_cb,
870 GSList *iter = msg->priv->content_handlers;
873 SoupHandlerData *data = iter->data;
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,
890 static inline gboolean
891 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
893 return ((newflags & find) && !(msg->priv->msg_flags & find));
896 static inline gboolean
897 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
899 return (!(newflags & find) && (msg->priv->msg_flags & find));
903 soup_message_set_flags (SoupMessage *msg, guint flags)
905 g_return_if_fail (msg != NULL);
907 msg->priv->msg_flags = flags;
911 soup_message_get_flags (SoupMessage *msg)
913 g_return_val_if_fail (msg != NULL, 0);
915 return msg->priv->msg_flags;
919 soup_message_set_http_version (SoupMessage *msg, SoupHttpVersion version)
921 g_return_if_fail (msg != NULL);
923 msg->priv->http_version = version;
927 soup_message_set_error (SoupMessage *msg, SoupKnownErrorCode errcode)
929 g_return_if_fail (msg != NULL);
930 g_return_if_fail (errcode != 0);
932 g_free ((gchar *) msg->errorphrase);
934 msg->errorcode = errcode;
935 msg->errorclass = soup_error_get_class (errcode);
936 msg->errorphrase = g_strdup (soup_error_get_phrase (errcode));
940 soup_message_set_error_full (SoupMessage *msg,
942 const gchar *errphrase)
944 g_return_if_fail (msg != NULL);
945 g_return_if_fail (errcode != 0);
946 g_return_if_fail (errphrase != NULL);
948 g_free ((gchar *) msg->errorphrase);
950 msg->errorcode = errcode;
951 msg->errorclass = soup_error_get_class (errcode);
952 msg->errorphrase = g_strdup (errphrase);
956 soup_message_set_handler_error (SoupMessage *msg,
958 const gchar *errphrase)
960 g_return_if_fail (msg != NULL);
961 g_return_if_fail (errcode != 0);
962 g_return_if_fail (errphrase != NULL);
964 g_free ((gchar *) msg->errorphrase);
966 msg->errorcode = errcode;
967 msg->errorclass = SOUP_ERROR_CLASS_HANDLER;
968 msg->errorphrase = g_strdup (errphrase);