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);
589 goto THROW_CANT_AUTHENTICATE;
591 vals = soup_message_get_header_list (msg->response_headers,
593 "Proxy-Authenticate" :
595 if (!vals) goto THROW_CANT_AUTHENTICATE;
597 auth = soup_auth_new_from_header_list (uri, vals);
599 soup_message_set_error_full (
602 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
603 SOUP_ERROR_CANT_AUTHENTICATE,
605 "Unknown authentication scheme required by "
607 "Unknown authentication scheme required");
611 old_auth = soup_auth_lookup (ctx);
613 if (!soup_auth_invalidates_prior (auth, old_auth)) {
614 soup_auth_free (auth);
615 goto THROW_CANT_AUTHENTICATE;
619 soup_auth_set_context (auth, ctx);
621 soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
625 THROW_CANT_AUTHENTICATE:
626 soup_message_set_error (msg,
628 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
629 SOUP_ERROR_CANT_AUTHENTICATE);
633 redirect_handler (SoupMessage *msg, gpointer user_data)
635 const gchar *new_loc;
637 if (msg->errorclass != SOUP_ERROR_CLASS_REDIRECT ||
638 msg->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT) return;
640 new_loc = soup_message_get_header (msg->response_headers, "Location");
643 const SoupUri *old_uri;
645 SoupContext *new_ctx;
647 old_uri = soup_context_get_uri (msg->context);
649 new_uri = soup_uri_new (new_loc);
651 goto INVALID_REDIRECT;
654 * Copy auth info from original URI.
656 if (old_uri->user && !new_uri->user)
657 soup_uri_set_auth (new_uri,
662 new_ctx = soup_context_from_uri (new_uri);
664 soup_uri_free (new_uri);
667 goto INVALID_REDIRECT;
669 soup_message_set_context (msg, new_ctx);
670 soup_context_unref (new_ctx);
672 soup_message_queue (msg,
674 msg->priv->user_data);
680 soup_message_set_error_full (msg,
681 SOUP_ERROR_MALFORMED,
682 "Invalid Redirect URL");
686 RESPONSE_HEADER_HANDLER = 1,
687 RESPONSE_ERROR_CODE_HANDLER,
688 RESPONSE_ERROR_CLASS_HANDLER
692 SoupHandlerType type;
693 SoupCallbackFn handler_cb;
696 SoupHandlerKind kind;
699 SoupErrorClass errorclass;
704 static SoupHandlerData global_handlers [] = {
706 * Handle redirect response codes 300, 301, 302, 303, and 305.
709 SOUP_HANDLER_PRE_BODY,
712 RESPONSE_HEADER_HANDLER,
713 { (guint) "Location" }
716 * Handle authorization.
719 SOUP_HANDLER_PRE_BODY,
720 (SoupCallbackFn) authorize_handler,
721 GINT_TO_POINTER (FALSE),
722 RESPONSE_ERROR_CODE_HANDLER,
726 * Handle proxy authorization.
729 SOUP_HANDLER_PRE_BODY,
730 (SoupCallbackFn) authorize_handler,
731 GINT_TO_POINTER (TRUE),
732 RESPONSE_ERROR_CODE_HANDLER,
739 run_handler (SoupMessage *msg,
740 SoupHandlerType invoke_type,
741 SoupHandlerData *data)
743 if (data->type != invoke_type) return;
745 switch (data->kind) {
746 case RESPONSE_HEADER_HANDLER:
747 if (!soup_message_get_header (msg->response_headers,
751 case RESPONSE_ERROR_CODE_HANDLER:
752 if (msg->errorcode != data->data.errorcode) return;
754 case RESPONSE_ERROR_CLASS_HANDLER:
755 if (msg->errorclass != data->data.errorclass) return;
761 (*data->handler_cb) (msg, data->user_data);
765 * Run each handler with matching criteria (first per-message then global
766 * handlers). If a handler requeues a message, we stop processing and terminate
767 * the current request.
769 * After running all handlers, if there is an error set or the invoke type was
770 * post_body, issue the final callback.
772 * FIXME: If the errorcode is changed by a handler, we should restart the
776 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
779 SoupHandlerData *data;
781 g_return_val_if_fail (msg != NULL, FALSE);
783 for (list = msg->priv->content_handlers; list; list = list->next) {
786 run_handler (msg, invoke_type, data);
788 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
791 for (data = global_handlers; data->type; data++) {
792 run_handler (msg, invoke_type, data);
794 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
798 * Issue final callback if the invoke_type is POST_BODY and the error
799 * class is not INFORMATIONAL.
801 if (invoke_type == SOUP_HANDLER_POST_BODY &&
802 msg->errorclass != SOUP_ERROR_CLASS_INFORMATIONAL) {
803 soup_message_issue_callback (msg);
811 add_handler (SoupMessage *msg,
812 SoupHandlerType type,
813 SoupCallbackFn handler_cb,
815 SoupHandlerKind kind,
820 SoupHandlerData *data;
822 data = g_new0 (SoupHandlerData, 1);
824 data->handler_cb = handler_cb;
825 data->user_data = user_data;
829 case RESPONSE_HEADER_HANDLER:
830 data->data.header = header;
832 case RESPONSE_ERROR_CODE_HANDLER:
833 data->data.errorcode = errorcode;
835 case RESPONSE_ERROR_CLASS_HANDLER:
836 data->data.errorclass = errorclass;
842 msg->priv->content_handlers =
843 g_slist_append (msg->priv->content_handlers, data);
847 soup_message_add_header_handler (SoupMessage *msg,
849 SoupHandlerType type,
850 SoupCallbackFn handler_cb,
853 g_return_if_fail (msg != NULL);
854 g_return_if_fail (header != NULL);
855 g_return_if_fail (handler_cb != NULL);
861 RESPONSE_HEADER_HANDLER,
868 soup_message_add_error_code_handler (SoupMessage *msg,
870 SoupHandlerType type,
871 SoupCallbackFn handler_cb,
874 g_return_if_fail (msg != NULL);
875 g_return_if_fail (errorcode != 0);
876 g_return_if_fail (handler_cb != NULL);
882 RESPONSE_ERROR_CODE_HANDLER,
889 soup_message_add_error_class_handler (SoupMessage *msg,
890 SoupErrorClass errorclass,
891 SoupHandlerType type,
892 SoupCallbackFn handler_cb,
895 g_return_if_fail (msg != NULL);
896 g_return_if_fail (errorclass != 0);
897 g_return_if_fail (handler_cb != NULL);
903 RESPONSE_ERROR_CLASS_HANDLER,
910 soup_message_add_handler (SoupMessage *msg,
911 SoupHandlerType type,
912 SoupCallbackFn handler_cb,
915 g_return_if_fail (msg != NULL);
916 g_return_if_fail (handler_cb != NULL);
929 soup_message_remove_handler (SoupMessage *msg,
930 SoupHandlerType type,
931 SoupCallbackFn handler_cb,
934 GSList *iter = msg->priv->content_handlers;
937 SoupHandlerData *data = iter->data;
939 if (data->handler_cb == handler_cb &&
940 data->user_data == user_data &&
941 data->type == type) {
942 msg->priv->content_handlers =
943 g_slist_remove_link (
944 msg->priv->content_handlers,
954 static inline gboolean
955 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
957 return ((newflags & find) && !(msg->priv->msg_flags & find));
960 static inline gboolean
961 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
963 return (!(newflags & find) && (msg->priv->msg_flags & find));
967 soup_message_set_flags (SoupMessage *msg, guint flags)
969 g_return_if_fail (msg != NULL);
971 msg->priv->msg_flags = flags;
975 soup_message_get_flags (SoupMessage *msg)
977 g_return_val_if_fail (msg != NULL, 0);
979 return msg->priv->msg_flags;
983 soup_message_set_http_version (SoupMessage *msg, SoupHttpVersion version)
985 g_return_if_fail (msg != NULL);
987 msg->priv->http_version = version;
991 soup_message_set_context (SoupMessage *msg,
992 SoupContext *new_ctx)
994 soup_context_unref (msg->context);
995 msg->context = new_ctx;
996 soup_context_ref (new_ctx);
1000 soup_message_get_context (SoupMessage *msg)
1002 soup_context_ref (msg->context);
1003 return msg->context;
1007 soup_message_set_error (SoupMessage *msg, SoupKnownErrorCode errcode)
1009 g_return_if_fail (msg != NULL);
1010 g_return_if_fail (errcode != 0);
1012 g_free ((gchar *) msg->errorphrase);
1014 msg->errorcode = errcode;
1015 msg->errorclass = soup_error_get_class (errcode);
1016 msg->errorphrase = g_strdup (soup_error_get_phrase (errcode));
1020 soup_message_set_error_full (SoupMessage *msg,
1022 const gchar *errphrase)
1024 g_return_if_fail (msg != NULL);
1025 g_return_if_fail (errcode != 0);
1026 g_return_if_fail (errphrase != NULL);
1028 g_free ((gchar *) msg->errorphrase);
1030 msg->errorcode = errcode;
1031 msg->errorclass = soup_error_get_class (errcode);
1032 msg->errorphrase = g_strdup (errphrase);
1036 soup_message_set_handler_error (SoupMessage *msg,
1038 const gchar *errphrase)
1040 g_return_if_fail (msg != NULL);
1041 g_return_if_fail (errcode != 0);
1042 g_return_if_fail (errphrase != NULL);
1044 g_free ((gchar *) msg->errorphrase);
1046 msg->errorcode = errcode;
1047 msg->errorclass = SOUP_ERROR_CLASS_HANDLER;
1048 msg->errorphrase = g_strdup (errphrase);