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
24 * request, if NULL a GET request will be made.
26 * Creates a new empty %SoupMessage, which will connect to the URL represented
27 * by @context. A reference will be added to @context.
29 * The new message has a status of @SOUP_STATUS_IDLE.
31 * Return value: the new %SoupMessage.
34 soup_message_new (SoupContext *context, const gchar *method)
38 g_return_val_if_fail (context, NULL);
40 ret = g_new0 (SoupMessage, 1);
41 ret->priv = g_new0 (SoupMessagePrivate, 1);
42 ret->status = SOUP_STATUS_IDLE;
43 ret->context = context;
44 ret->method = method ? method : SOUP_METHOD_GET;
46 ret->request_headers = g_hash_table_new (soup_str_case_hash,
49 ret->response_headers = g_hash_table_new (soup_str_case_hash,
52 ret->priv->http_version = SOUP_HTTP_1_1;
54 soup_context_ref (context);
60 * soup_message_new_full:
61 * @context: a %SoupContext for the destination endpoint.
62 * @method: a string which will be used as the HTTP method for the created
63 * request, if NULL a GET request will be made..
64 * @req_owner: the %SoupOwnership of the passed data buffer.
65 * @req_body: a data buffer containing the body of the message request.
66 * @req_length: the byte length of @req_body.
68 * Creates a new %SoupMessage, which will connect to the URL represented by
69 * @context. A reference is added to @context. The request data
70 * buffer will be filled from @req_owner, @req_body, and @req_length
73 * The new message has a status of @SOUP_STATUS_IDLE.
75 * Return value: the new %SoupMessage.
78 soup_message_new_full (SoupContext *context,
80 SoupOwnership req_owner,
84 SoupMessage *ret = soup_message_new (context, method);
86 ret->request.owner = req_owner;
87 ret->request.body = req_body;
88 ret->request.length = req_length;
94 * soup_message_cleanup:
95 * @req: a %SoupMessage.
97 * Frees any temporary resources created in the processing of @req. Also
98 * releases the active connection, if one exists. Request and response data
99 * buffers are left intact.
102 soup_message_cleanup (SoupMessage *req)
104 g_return_if_fail (req != NULL);
106 if (req->priv->read_tag) {
107 soup_transfer_read_cancel (req->priv->read_tag);
108 req->priv->read_tag = 0;
111 if (req->priv->write_tag) {
112 soup_transfer_write_cancel (req->priv->write_tag);
113 req->priv->write_tag = 0;
116 if (req->priv->connect_tag) {
117 soup_context_cancel_connect (req->priv->connect_tag);
118 req->priv->connect_tag = NULL;
120 if (req->connection) {
121 soup_connection_release (req->connection);
122 req->connection = NULL;
125 soup_active_requests = g_slist_remove (soup_active_requests, req);
129 finalize_message (SoupMessage *req)
131 soup_context_unref (req->context);
133 if (req->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
134 g_free (req->request.body);
135 if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
136 g_free (req->response.body);
138 if (req->priv->req_header)
139 g_string_free (req->priv->req_header, TRUE);
141 soup_message_clear_headers (req->request_headers);
142 g_hash_table_destroy (req->request_headers);
144 soup_message_clear_headers (req->response_headers);
145 g_hash_table_destroy (req->response_headers);
147 g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
148 g_slist_free (req->priv->content_handlers);
150 g_free ((gchar *) req->errorphrase);
157 * @req: a %SoupMessage to destroy.
159 * Destroys the %SoupMessage pointed to by @req. Request and response headers
160 * are freed. Request and response data buffers are also freed if their
161 * ownership is %SOUP_BUFFER_SYSTEM_OWNED. The message's destination context
162 * will be de-referenced.
165 soup_message_free (SoupMessage *req)
167 g_return_if_fail (req != NULL);
169 soup_message_cleanup (req);
171 finalize_message (req);
175 * soup_message_issue_callback:
176 * @req: a %SoupMessage currently being processed.
177 * @error: a %SoupErrorCode to be passed to %req's completion callback.
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.
185 soup_message_issue_callback (SoupMessage *req)
187 g_return_if_fail (req != NULL);
190 * Make sure we don't have some icky recursion if the callback
191 * runs the main loop, and the connection has some data or error
192 * which causes the callback to be run again.
194 soup_message_cleanup (req);
196 if (req->priv->callback) {
197 (*req->priv->callback) (req, req->priv->user_data);
199 if (req->status != SOUP_STATUS_QUEUED)
200 finalize_message (req);
205 * soup_message_cancel:
206 * @req: a %SoupMessage currently being processed.
208 * Cancel a running message, and issue completion callback with a
209 * %SoupTransferStatus of %SOUP_ERROR_CANCELLED. If not requeued by the
210 * completion callback, the @msg will be destroyed.
213 soup_message_cancel (SoupMessage *msg)
215 soup_message_set_error (msg, SOUP_ERROR_CANCELLED);
216 soup_message_issue_callback (msg);
220 foreach_free_header_list (gchar *name, GSList *vals, gpointer notused)
223 g_slist_foreach (vals, (GFunc) g_free, NULL);
230 soup_message_clear_headers (GHashTable *hash)
232 g_return_if_fail (hash != NULL);
234 g_hash_table_foreach_remove (hash,
235 (GHRFunc) foreach_free_header_list,
240 soup_message_remove_header (GHashTable *hash,
246 g_return_if_fail (hash != NULL);
247 g_return_if_fail (name != NULL || name [0] != '\0');
249 if (g_hash_table_lookup_extended (hash,
251 (gpointer *) &stored_key,
252 (gpointer *) &vals)) {
253 g_hash_table_remove (hash, name);
254 foreach_free_header_list (stored_key, vals, NULL);
259 soup_message_add_header (GHashTable *hash,
265 g_return_if_fail (hash != NULL);
266 g_return_if_fail (name != NULL || name [0] != '\0');
267 g_return_if_fail (value != NULL);
269 old_value = g_hash_table_lookup (hash, name);
272 g_slist_append (old_value, g_strdup (value));
274 g_hash_table_insert (hash,
276 g_slist_append (NULL,
281 * soup_message_get_header:
282 * @req: a %SoupMessage.
283 * @name: header name.
285 * Lookup the first transport header with a key equal to @name.
287 * Return value: the header's value or NULL if not found.
290 soup_message_get_header (GHashTable *hash,
295 g_return_val_if_fail (hash != NULL, NULL);
296 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
298 vals = g_hash_table_lookup (hash, name);
306 * soup_message_get_header_list:
307 * @req: a %SoupMessage.
308 * @name: header name.
310 * Lookup the all transport request headers with a key equal to @name.
312 * Return value: a const pointer to a GSList of header values or NULL if not
316 soup_message_get_header_list (GHashTable *hash,
319 g_return_val_if_fail (hash != NULL, NULL);
320 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
322 return g_hash_table_lookup (hash, name);
331 foreach_value_in_list (gchar *name, GSList *vals, ForeachData *data)
334 gchar *v = vals->data;
336 (*data->func) (name, v, data->user_data);
343 soup_message_foreach_header (GHashTable *hash,
347 ForeachData data = { func, user_data };
349 g_return_if_fail (hash != NULL);
350 g_return_if_fail (func != NULL);
352 g_hash_table_foreach (hash, (GHFunc) foreach_value_in_list, &data);
361 foreach_remove_value_in_list (gchar *name,
363 ForeachRemoveData *data)
368 gchar *v = iter->data;
369 gboolean ret = FALSE;
371 ret = (*data->func) (name, v, data->user_data);
373 GSList *next = iter->next;
375 vals = g_slist_remove (vals, v);
392 soup_message_foreach_remove_header (GHashTable *hash,
396 ForeachRemoveData data = { func, user_data };
398 g_return_if_fail (hash != NULL);
399 g_return_if_fail (func != NULL);
401 g_hash_table_foreach_remove (hash,
402 (GHRFunc) foreach_remove_value_in_list,
407 * soup_message_set_request_header:
408 * @req: a %SoupMessage.
409 * @name: header name.
410 * @value: header value.
414 * Adds a new transport header to be sent on an outgoing request. Passing a NULL
415 * @value will remove all headers with a name equal to @name.
418 soup_message_set_request_header (SoupMessage *req,
422 g_return_if_fail (req != NULL);
423 g_return_if_fail (name != NULL || name [0] != '\0');
425 g_warning ("soup_message_set_request_header is DEPRECATED. Use "
426 "soup_message_add_header, with msg->request_headers as "
427 "the first argument.\n");
429 soup_message_add_header (req->request_headers, name, value);
433 * soup_message_get_request_header:
434 * @req: a %SoupMessage.
435 * @name: header name.
439 * Lookup the first transport request header with a key equal to @name.
441 * Return value: the header's value or NULL if not found.
444 soup_message_get_request_header (SoupMessage *req,
448 g_return_val_if_fail (req != NULL, NULL);
449 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
451 g_warning ("soup_message_get_request_header is DEPRECATED. Use "
452 "soup_message_get_header, with msg->request_headers as "
453 "the first argument.\n");
455 if (req->request_headers) {
456 vals = g_hash_table_lookup (req->request_headers, name);
465 * soup_message_set_response_header:
466 * @req: a %SoupMessage.
467 * @name: header name.
468 * @value: header value.
472 * Adds a new transport header to be sent on an outgoing response. Passing a
473 * NULL @value will remove all headers with a name equal to @name.
476 soup_message_set_response_header (SoupMessage *req,
480 g_return_if_fail (req != NULL);
481 g_return_if_fail (name != NULL || name [0] != '\0');
483 g_warning ("soup_message_set_response_header is DEPRECATED. Use "
484 "soup_message_add_header, with msg->response_headers as "
485 "the first argument.\n");
487 soup_message_add_header (req->response_headers, name, value);
491 * soup_message_get_response_header:
492 * @req: a %SoupMessage.
493 * @name: header name.
497 * Lookup the transport response header with a key equal to @name.
499 * Return value: the header's value or NULL if not found.
502 soup_message_get_response_header (SoupMessage *req,
506 g_return_val_if_fail (req != NULL, NULL);
507 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
509 g_warning ("soup_message_get_response_header is DEPRECATED. Use "
510 "soup_message_get_header, with msg->response_headers as "
511 "the first argument.\n");
513 if (req->response_headers) {
514 vals = g_hash_table_lookup (req->response_headers, name);
523 * soup_message_queue:
524 * @req: a %SoupMessage.
525 * @callback: a %SoupCallbackFn which will be called after the message completes
526 * or when an unrecoverable error occurs.
527 * @user_data: a pointer passed to @callback.
529 * Queues the message @req for sending. All messages are processed while the
530 * glib main loop runs. If this %SoupMessage has been processed before, any
531 * resources related to the time it was last sent are freed.
533 * If the response %SoupDataBuffer has an owner of %SOUP_BUFFER_USER_OWNED, the
534 * message will not be queued, and @callback will be called with a
535 * %SoupErrorCode of %SOUP_ERROR_CANCELLED.
537 * Upon message completetion, the callback specified in @callback will be
538 * invoked. If after returning from this callback the message has not been
539 * requeued using %soup_message_queue, %soup_message_free will be called on
543 soup_message_queue (SoupMessage *req,
544 SoupCallbackFn callback,
547 soup_queue_message (req, callback, user_data);
552 * @msg: a %SoupMessage.
554 * Syncronously send @msg. This call will not return until the transfer is
555 * finished successfully or there is an unrecoverable error.
557 * @msg is not free'd upon return.
559 * Return value: the %SoupErrorClass of the error encountered while sending or
560 * reading the response.
563 soup_message_send (SoupMessage *msg)
565 soup_message_queue (msg, NULL, NULL);
568 g_main_iteration (TRUE);
569 if (msg->status == SOUP_STATUS_FINISHED ||
570 SOUP_ERROR_IS_TRANSPORT (msg->errorcode))
574 return msg->errorclass;
578 authorize_handler (SoupMessage *msg, gboolean proxy)
581 SoupAuth *auth, *old_auth;
585 ctx = proxy ? soup_get_proxy () : msg->context;
586 uri = soup_context_get_uri (ctx);
588 vals = soup_message_get_header_list (msg->response_headers,
590 "Proxy-Authenticate" :
592 if (!vals) goto THROW_CANT_AUTHENTICATE;
594 auth = soup_auth_new_from_header_list (vals);
596 soup_message_set_error_full (
599 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
600 SOUP_ERROR_CANT_AUTHENTICATE,
602 "Unknown authentication scheme required by "
604 "Unknown authentication scheme required");
609 * Call registered authenticate handler
611 if (!uri->user && soup_auth_fn)
612 (*soup_auth_fn) (auth->type,
615 soup_auth_fn_user_data);
618 soup_auth_free (auth);
619 goto THROW_CANT_AUTHENTICATE;
623 * Initialize with auth data (possibly returned from auth callback).
625 soup_auth_initialize (auth, uri);
627 old_auth = soup_auth_lookup (ctx);
629 if (!soup_auth_invalidates_prior (auth, old_auth)) {
630 soup_auth_free (auth);
631 goto THROW_CANT_AUTHENTICATE;
635 soup_auth_set_context (auth, ctx);
637 soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
641 THROW_CANT_AUTHENTICATE:
642 soup_message_set_error (msg,
644 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
645 SOUP_ERROR_CANT_AUTHENTICATE);
649 redirect_handler (SoupMessage *msg, gpointer user_data)
651 const gchar *new_loc;
653 if (msg->errorclass != SOUP_ERROR_CLASS_REDIRECT ||
654 msg->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT) return;
656 new_loc = soup_message_get_header (msg->response_headers, "Location");
659 const SoupUri *old_uri;
661 SoupContext *new_ctx;
663 old_uri = soup_context_get_uri (msg->context);
665 new_uri = soup_uri_new (new_loc);
667 goto INVALID_REDIRECT;
670 * Copy auth info from original URI.
672 if (old_uri->user && !new_uri->user)
673 soup_uri_set_auth (new_uri,
678 new_ctx = soup_context_from_uri (new_uri);
680 soup_uri_free (new_uri);
683 goto INVALID_REDIRECT;
685 soup_message_set_context (msg, new_ctx);
686 soup_context_unref (new_ctx);
688 soup_message_queue (msg,
690 msg->priv->user_data);
696 soup_message_set_error_full (msg,
697 SOUP_ERROR_MALFORMED,
698 "Invalid Redirect URL");
702 RESPONSE_HEADER_HANDLER = 1,
703 RESPONSE_ERROR_CODE_HANDLER,
704 RESPONSE_ERROR_CLASS_HANDLER
708 SoupHandlerType type;
709 SoupCallbackFn handler_cb;
712 SoupHandlerKind kind;
715 SoupErrorClass errorclass;
720 static SoupHandlerData global_handlers [] = {
722 * Handle redirect response codes 300, 301, 302, 303, and 305.
725 SOUP_HANDLER_PRE_BODY,
728 RESPONSE_HEADER_HANDLER,
729 { (guint) "Location" }
732 * Handle authorization.
735 SOUP_HANDLER_PRE_BODY,
736 (SoupCallbackFn) authorize_handler,
737 GINT_TO_POINTER (FALSE),
738 RESPONSE_ERROR_CODE_HANDLER,
742 * Handle proxy authorization.
745 SOUP_HANDLER_PRE_BODY,
746 (SoupCallbackFn) authorize_handler,
747 GINT_TO_POINTER (TRUE),
748 RESPONSE_ERROR_CODE_HANDLER,
755 run_handler (SoupMessage *msg,
756 SoupHandlerType invoke_type,
757 SoupHandlerData *data)
759 if (data->type != invoke_type) return;
761 switch (data->kind) {
762 case RESPONSE_HEADER_HANDLER:
763 if (!soup_message_get_header (msg->response_headers,
767 case RESPONSE_ERROR_CODE_HANDLER:
768 if (msg->errorcode != data->data.errorcode) return;
770 case RESPONSE_ERROR_CLASS_HANDLER:
771 if (msg->errorclass != data->data.errorclass) return;
777 (*data->handler_cb) (msg, data->user_data);
781 * Run each handler with matching criteria (first per-message then global
782 * handlers). If a handler requeues a message, we stop processing and terminate
783 * the current request.
785 * After running all handlers, if there is an error set or the invoke type was
786 * post_body, issue the final callback.
788 * FIXME: If the errorcode is changed by a handler, we should restart the
792 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
795 SoupHandlerData *data;
797 g_return_val_if_fail (msg != NULL, FALSE);
799 for (list = msg->priv->content_handlers; list; list = list->next) {
802 run_handler (msg, invoke_type, data);
804 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
807 for (data = global_handlers; data->type; data++) {
808 run_handler (msg, invoke_type, data);
810 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
814 * Issue final callback if the invoke_type is POST_BODY and the error
815 * class is not INFORMATIONAL.
817 if (invoke_type == SOUP_HANDLER_POST_BODY &&
818 msg->errorclass != SOUP_ERROR_CLASS_INFORMATIONAL) {
819 soup_message_issue_callback (msg);
827 add_handler (SoupMessage *msg,
828 SoupHandlerType type,
829 SoupCallbackFn handler_cb,
831 SoupHandlerKind kind,
836 SoupHandlerData *data;
838 data = g_new0 (SoupHandlerData, 1);
840 data->handler_cb = handler_cb;
841 data->user_data = user_data;
845 case RESPONSE_HEADER_HANDLER:
846 data->data.header = header;
848 case RESPONSE_ERROR_CODE_HANDLER:
849 data->data.errorcode = errorcode;
851 case RESPONSE_ERROR_CLASS_HANDLER:
852 data->data.errorclass = errorclass;
858 msg->priv->content_handlers =
859 g_slist_append (msg->priv->content_handlers, data);
863 soup_message_add_header_handler (SoupMessage *msg,
865 SoupHandlerType type,
866 SoupCallbackFn handler_cb,
869 g_return_if_fail (msg != NULL);
870 g_return_if_fail (header != NULL);
871 g_return_if_fail (handler_cb != NULL);
877 RESPONSE_HEADER_HANDLER,
884 soup_message_add_error_code_handler (SoupMessage *msg,
886 SoupHandlerType type,
887 SoupCallbackFn handler_cb,
890 g_return_if_fail (msg != NULL);
891 g_return_if_fail (errorcode != 0);
892 g_return_if_fail (handler_cb != NULL);
898 RESPONSE_ERROR_CODE_HANDLER,
905 soup_message_add_error_class_handler (SoupMessage *msg,
906 SoupErrorClass errorclass,
907 SoupHandlerType type,
908 SoupCallbackFn handler_cb,
911 g_return_if_fail (msg != NULL);
912 g_return_if_fail (errorclass != 0);
913 g_return_if_fail (handler_cb != NULL);
919 RESPONSE_ERROR_CLASS_HANDLER,
926 soup_message_add_handler (SoupMessage *msg,
927 SoupHandlerType type,
928 SoupCallbackFn handler_cb,
931 g_return_if_fail (msg != NULL);
932 g_return_if_fail (handler_cb != NULL);
945 soup_message_remove_handler (SoupMessage *msg,
946 SoupHandlerType type,
947 SoupCallbackFn handler_cb,
950 GSList *iter = msg->priv->content_handlers;
953 SoupHandlerData *data = iter->data;
955 if (data->handler_cb == handler_cb &&
956 data->user_data == user_data &&
957 data->type == type) {
958 msg->priv->content_handlers =
959 g_slist_remove_link (
960 msg->priv->content_handlers,
970 static inline gboolean
971 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
973 return ((newflags & find) && !(msg->priv->msg_flags & find));
976 static inline gboolean
977 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
979 return (!(newflags & find) && (msg->priv->msg_flags & find));
983 soup_message_set_flags (SoupMessage *msg, guint flags)
985 g_return_if_fail (msg != NULL);
987 msg->priv->msg_flags = flags;
991 soup_message_get_flags (SoupMessage *msg)
993 g_return_val_if_fail (msg != NULL, 0);
995 return msg->priv->msg_flags;
999 soup_message_set_http_version (SoupMessage *msg, SoupHttpVersion version)
1001 g_return_if_fail (msg != NULL);
1003 msg->priv->http_version = version;
1007 soup_message_set_context (SoupMessage *msg,
1008 SoupContext *new_ctx)
1010 g_return_if_fail (msg != NULL);
1011 g_return_if_fail (new_ctx != NULL);
1013 soup_context_unref (msg->context);
1014 soup_context_ref (new_ctx);
1016 msg->context = new_ctx;
1020 soup_message_get_context (SoupMessage *msg)
1022 g_return_val_if_fail (msg != NULL, NULL);
1024 soup_context_ref (msg->context);
1025 return msg->context;
1029 soup_message_set_error (SoupMessage *msg, SoupKnownErrorCode errcode)
1031 g_return_if_fail (msg != NULL);
1032 g_return_if_fail (errcode != 0);
1034 g_free ((gchar *) msg->errorphrase);
1036 msg->errorcode = errcode;
1037 msg->errorclass = soup_error_get_class (errcode);
1038 msg->errorphrase = g_strdup (soup_error_get_phrase (errcode));
1042 soup_message_set_error_full (SoupMessage *msg,
1044 const gchar *errphrase)
1046 g_return_if_fail (msg != NULL);
1047 g_return_if_fail (errcode != 0);
1048 g_return_if_fail (errphrase != NULL);
1050 g_free ((gchar *) msg->errorphrase);
1052 msg->errorcode = errcode;
1053 msg->errorclass = soup_error_get_class (errcode);
1054 msg->errorphrase = g_strdup (errphrase);
1058 soup_message_set_handler_error (SoupMessage *msg,
1060 const gchar *errphrase)
1062 g_return_if_fail (msg != NULL);
1063 g_return_if_fail (errcode != 0);
1064 g_return_if_fail (errphrase != NULL);
1066 g_free ((gchar *) msg->errorphrase);
1068 msg->errorcode = errcode;
1069 msg->errorclass = SOUP_ERROR_CLASS_HANDLER;
1070 msg->errorphrase = g_strdup (errphrase);