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-message.h"
13 #include "soup-misc.h"
14 #include "soup-context.h"
15 #include "soup-private.h"
16 #include "soup-queue.h"
17 #include "soup-transfer.h"
21 * @context: a %SoupContext for the destination endpoint.
22 * @method: a string which will be used as the HTTP method for the created
25 * Creates a new empty %SoupMessage, which will connect to the URL represented
26 * by @context. The new message has a status of @SOUP_STATUS_IDLE.
28 * Return value: the new %SoupMessage.
31 soup_message_new (SoupContext *context, const gchar *method)
35 g_return_val_if_fail (context, NULL);
37 ret = g_new0 (SoupMessage, 1);
38 ret->priv = g_new0 (SoupMessagePrivate, 1);
39 ret->status = SOUP_STATUS_IDLE;
40 ret->context = context;
41 ret->method = method ? method : SOUP_METHOD_POST;
43 ret->priv->http_version = SOUP_HTTP_1_1;
45 soup_context_ref (context);
51 * soup_message_new_full:
52 * @context: a %SoupContext for the destination endpoint.
53 * @method: a string which will be used as the HTTP method for the created
55 * @req_owner: the %SoupOwnership of the passed data buffer.
56 * @req_body: a data buffer containing the body of the message request.
57 * @req_length: the byte length of @req_body.
59 * Creates a new %SoupMessage, which will connect to the URL represented by
60 * @context. The new message has a status of @SOUP_STATUS_IDLE. The request data
61 * buffer will be filled from @req_owner, @req_body, and @req_length
64 * Return value: the new %SoupMessage.
67 soup_message_new_full (SoupContext *context,
69 SoupOwnership req_owner,
73 SoupMessage *ret = soup_message_new (context, method);
75 ret->request.owner = req_owner;
76 ret->request.body = req_body;
77 ret->request.length = req_length;
83 * soup_message_cleanup:
84 * @req: a %SoupMessage.
86 * Frees any temporary resources created in the processing of @req. Request and
87 * response data buffers are left intact.
90 soup_message_cleanup (SoupMessage *req)
92 g_return_if_fail (req != NULL);
94 if (req->priv->read_tag) {
95 soup_transfer_read_cancel (req->priv->read_tag);
96 req->priv->read_tag = 0;
99 if (req->priv->write_tag) {
100 soup_transfer_write_cancel (req->priv->write_tag);
101 req->priv->write_tag = 0;
104 if (req->priv->connect_tag) {
105 soup_context_cancel_connect (req->priv->connect_tag);
106 req->priv->connect_tag = NULL;
108 if (req->connection) {
109 soup_connection_release (req->connection);
110 req->connection = NULL;
113 soup_active_requests = g_slist_remove (soup_active_requests, req);
117 free_header (gchar *name, gchar *value, gpointer unused)
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 if (req->request_headers) {
137 g_hash_table_foreach (req->request_headers,
138 (GHFunc) free_header,
140 g_hash_table_destroy (req->request_headers);
143 if (req->response_headers) {
144 g_hash_table_foreach (req->response_headers,
145 (GHFunc) free_header,
147 g_hash_table_destroy (req->response_headers);
150 g_slist_foreach (req->priv->content_handlers, (GFunc) g_free, NULL);
151 g_slist_free (req->priv->content_handlers);
153 g_free ((gchar *) req->errorphrase);
160 * @req: a %SoupMessage to destroy.
162 * Destroys the %SoupMessage pointed to by @req. Request and response headers
163 * are freed. Request and response data buffers are also freed if their
164 * ownership is %SOUP_BUFFER_SYSTEM_OWNED. The message's destination context
165 * will be de-referenced.
168 soup_message_free (SoupMessage *req)
170 g_return_if_fail (req != NULL);
172 soup_message_cleanup (req);
174 finalize_message (req);
178 * soup_message_issue_callback:
179 * @req: a %SoupMessage currently being processed.
180 * @error: a %SoupErrorCode to be passed to %req's completion callback.
182 * Finalizes the message request, by first freeing any temporary resources, then
183 * issuing the callback function pointer passed in %soup_message_new or
184 * %soup_message_new_full. If, after returning from the callback, the message
185 * has not been requeued, @msg is destroyed using %soup_message_free.
188 soup_message_issue_callback (SoupMessage *req)
190 g_return_if_fail (req != NULL);
193 * Make sure we don't have some icky recursion if the callback
194 * runs the main loop, and the connection has some data or error
195 * which causes the callback to be run again.
197 soup_message_cleanup (req);
199 if (req->priv->callback) {
200 (*req->priv->callback) (req, req->priv->user_data);
202 if (req->status != SOUP_STATUS_QUEUED)
203 finalize_message (req);
208 * soup_message_cancel:
209 * @req: a %SoupMessage currently being processed.
211 * Cancel a running message, and issue completion callback with a
212 * %SoupTransferStatus of %SOUP_ERROR_CANCELLED. If not requeued by the
213 * completion callback, the @msg will be destroyed.
216 soup_message_cancel (SoupMessage *msg)
218 soup_message_set_error (msg, SOUP_ERROR_CANCELLED);
219 soup_message_issue_callback (msg);
223 soup_message_set_header (GHashTable **hash,
227 gpointer old_name, old_value;
230 *hash = g_hash_table_new (soup_str_case_hash,
231 soup_str_case_equal);
232 else if (g_hash_table_lookup_extended (*hash,
236 g_hash_table_remove (*hash, name);
242 g_hash_table_insert (*hash, g_strdup (name), g_strdup (value));
246 * soup_message_set_request_header:
247 * @req: a %SoupMessage.
248 * @name: header name.
249 * @value: header value.
251 * Adds a new transport header to be sent on an outgoing request. Passing a NULL
252 * @value will remove the header name supplied.
255 soup_message_set_request_header (SoupMessage *req,
259 g_return_if_fail (req != NULL);
260 g_return_if_fail (name != NULL || name [0] != '\0');
262 soup_message_set_header (&req->request_headers, name, value);
266 * soup_message_get_request_header:
267 * @req: a %SoupMessage.
268 * @name: header name.
270 * Lookup the transport request header with a key equal to @name.
272 * Return value: the header's value or NULL if not found.
275 soup_message_get_request_header (SoupMessage *req,
278 g_return_val_if_fail (req != NULL, NULL);
279 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
281 return req->request_headers ?
282 g_hash_table_lookup (req->request_headers, name) : NULL;
286 * soup_message_set_response_header:
287 * @req: a %SoupMessage.
288 * @name: header name.
289 * @value: header value.
291 * Adds a new transport header to be sent on an outgoing response. Passing a
292 * NULL @value will remove the header name supplied.
295 soup_message_set_response_header (SoupMessage *req,
299 g_return_if_fail (req != NULL);
300 g_return_if_fail (name != NULL || name [0] != '\0');
302 soup_message_set_header (&req->response_headers, name, value);
306 * soup_message_get_response_header:
307 * @req: a %SoupMessage.
308 * @name: header name.
310 * Lookup the transport response header with a key equal to @name.
312 * Return value: the header's value or NULL if not found.
315 soup_message_get_response_header (SoupMessage *req,
318 g_return_val_if_fail (req != NULL, NULL);
319 g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
321 return req->response_headers ?
322 g_hash_table_lookup (req->response_headers, name) : NULL;
326 * soup_message_queue:
327 * @req: a %SoupMessage.
328 * @callback: a %SoupCallbackFn which will be called after the message completes
329 * or when an unrecoverable error occurs.
330 * @user_data: a pointer passed to @callback.
332 * Queues the message @req for sending. All messages are processed while the
333 * glib main loop runs. If this %SoupMessage has been processed before, any
334 * resources related to the time it was last sent are freed.
336 * If the response %SoupDataBuffer has an owner of %SOUP_BUFFER_USER_OWNED, the
337 * message will not be queued, and @callback will be called with a
338 * %SoupErrorCode of %SOUP_ERROR_CANCELLED.
340 * Upon message completetion, the callback specified in @callback will be
341 * invoked. If after returning from this callback the message has not been
342 * requeued using %soup_message_queue, %soup_message_free will be called on
346 soup_message_queue (SoupMessage *req,
347 SoupCallbackFn callback,
350 soup_queue_message (req, callback, user_data);
355 * @msg: a %SoupMessage.
357 * Syncronously send @msg. This call will not return until the transfer is
358 * finished successfully or there is an unrecoverable error.
360 * @msg is not free'd upon return.
362 * Return value: the %SoupErrorClass of the error encountered while sending or
363 * reading the response.
366 soup_message_send (SoupMessage *msg)
368 soup_message_queue (msg, NULL, NULL);
371 g_main_iteration (TRUE);
372 if (msg->status == SOUP_STATUS_FINISHED ||
377 return msg->errorclass;
381 authorize_handler (SoupMessage *msg, gboolean proxy)
383 const char *auth_header;
387 ctx = proxy ? soup_get_proxy () : msg->context;
389 if (!soup_context_get_uri (ctx)->user)
390 goto THROW_CANT_AUTHENTICATE;
393 soup_message_get_response_header (
395 proxy ? "Proxy-Authenticate" : "WWW-Authenticate");
396 if (!auth_header) goto THROW_CANT_AUTHENTICATE;
398 auth = soup_auth_new_from_header (ctx, auth_header);
400 soup_message_set_error_full (
403 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
404 SOUP_ERROR_CANT_AUTHENTICATE,
406 "Unknown authentication scheme "
407 "required by proxy" :
408 "Unknown authentication scheme "
414 if (soup_auth_invalidates_prior (auth, ctx->auth))
415 soup_auth_free (ctx->auth);
417 soup_auth_free (auth);
418 goto THROW_CANT_AUTHENTICATE;
424 soup_message_queue (msg, msg->priv->callback, msg->priv->user_data);
428 THROW_CANT_AUTHENTICATE:
429 soup_message_set_error (msg,
431 SOUP_ERROR_CANT_AUTHENTICATE_PROXY :
432 SOUP_ERROR_CANT_AUTHENTICATE);
436 redirect_handler (SoupMessage *msg, gpointer user_data)
438 const gchar *new_url;
440 if (msg->errorclass != SOUP_ERROR_CLASS_REDIRECT ||
441 msg->priv->msg_flags & SOUP_MESSAGE_NO_REDIRECT) return;
443 new_url = soup_message_get_response_header (msg, "Location");
446 SoupContext *new_ctx, *old_ctx;
448 new_ctx = soup_context_get (new_url);
450 soup_message_set_error_full (msg,
451 SOUP_ERROR_MALFORMED,
452 "Invalid Redirect URL");
456 old_ctx = msg->context;
457 msg->context = new_ctx;
459 soup_message_queue (msg,
461 msg->priv->user_data);
463 soup_context_unref (old_ctx);
468 RESPONSE_HEADER_HANDLER = 1,
469 RESPONSE_ERROR_CODE_HANDLER,
470 RESPONSE_ERROR_CLASS_HANDLER
474 SoupHandlerType type;
475 SoupCallbackFn handler_cb;
478 SoupHandlerKind kind;
481 SoupErrorClass errorclass;
\r
486 static SoupHandlerData global_handlers [] = {
488 * Handle authorization.
491 SOUP_HANDLER_PRE_BODY,
492 (SoupCallbackFn) authorize_handler,
493 GINT_TO_POINTER (FALSE),
494 RESPONSE_ERROR_CODE_HANDLER,
498 * Handle proxy authorization.
501 SOUP_HANDLER_PRE_BODY,
502 (SoupCallbackFn) authorize_handler,
503 GINT_TO_POINTER (TRUE),
504 RESPONSE_ERROR_CODE_HANDLER,
508 * Handle redirect response codes 300, 301, 302, 303, and 305.
511 SOUP_HANDLER_PRE_BODY,
514 RESPONSE_HEADER_HANDLER,
515 { (guint) "Location" }
521 run_handler (SoupMessage *msg,
522 SoupHandlerType invoke_type,
523 SoupHandlerData *data)
525 if (data->type != invoke_type) return;
527 switch (data->kind) {
528 case RESPONSE_HEADER_HANDLER:
529 if (!soup_message_get_response_header (msg,
533 case RESPONSE_ERROR_CODE_HANDLER:
534 if (msg->errorcode != data->data.errorcode) return;
536 case RESPONSE_ERROR_CLASS_HANDLER:
537 if (msg->errorclass != data->data.errorclass) return;
543 (*data->handler_cb) (msg, data->user_data);
547 * Run each handler with matching criteria (first per-message then global
548 * handlers). If a handler requeues a message, we stop processing and terminate
549 * the current request.
551 * After running all handlers, if there is an error set or the invoke type was
552 * post_body, issue the final callback.
554 * FIXME: If the errorcode is changed by a handler, we should restart the
558 soup_message_run_handlers (SoupMessage *msg, SoupHandlerType invoke_type)
561 SoupHandlerData *data;
563 g_return_val_if_fail (msg != NULL, FALSE);
565 for (list = msg->priv->content_handlers; list; list = list->next) {
568 run_handler (msg, invoke_type, data);
570 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
573 for (data = global_handlers; data->type; data++) {
574 run_handler (msg, invoke_type, data);
576 if (msg->status == SOUP_STATUS_QUEUED) return TRUE;
580 * Issue final callback if the invoke_type is POST_BODY and the error
581 * class is not INFORMATIONAL.
583 if (invoke_type == SOUP_HANDLER_POST_BODY &&
584 msg->errorclass != SOUP_ERROR_CLASS_INFORMATIONAL) {
585 soup_message_issue_callback (msg);
593 add_handler (SoupMessage *msg,
594 SoupHandlerType type,
595 SoupCallbackFn handler_cb,
597 SoupHandlerKind kind,
602 SoupHandlerData *data;
604 data = g_new0 (SoupHandlerData, 1);
606 data->handler_cb = handler_cb;
607 data->user_data = user_data;
611 case RESPONSE_HEADER_HANDLER:
612 data->data.header = header;
614 case RESPONSE_ERROR_CODE_HANDLER:
615 data->data.errorcode = errorcode;
617 case RESPONSE_ERROR_CLASS_HANDLER:
618 data->data.errorclass = errorclass;
624 msg->priv->content_handlers =
625 g_slist_append (msg->priv->content_handlers, data);
629 soup_message_add_header_handler (SoupMessage *msg,
631 SoupHandlerType type,
632 SoupCallbackFn handler_cb,
635 g_return_if_fail (msg != NULL);
636 g_return_if_fail (header != NULL);
637 g_return_if_fail (handler_cb != NULL);
643 RESPONSE_HEADER_HANDLER,
650 soup_message_add_error_code_handler (SoupMessage *msg,
652 SoupHandlerType type,
653 SoupCallbackFn handler_cb,
656 g_return_if_fail (msg != NULL);
657 g_return_if_fail (errorcode != 0);
658 g_return_if_fail (handler_cb != NULL);
664 RESPONSE_ERROR_CODE_HANDLER,
671 soup_message_add_error_class_handler (SoupMessage *msg,
672 SoupErrorClass errorclass,
673 SoupHandlerType type,
674 SoupCallbackFn handler_cb,
677 g_return_if_fail (msg != NULL);
678 g_return_if_fail (errorclass != 0);
679 g_return_if_fail (handler_cb != NULL);
685 RESPONSE_ERROR_CLASS_HANDLER,
692 soup_message_add_handler (SoupMessage *msg,
693 SoupHandlerType type,
694 SoupCallbackFn handler_cb,
697 g_return_if_fail (msg != NULL);
698 g_return_if_fail (handler_cb != NULL);
711 soup_message_remove_handler (SoupMessage *msg,
712 SoupHandlerType type,
713 SoupCallbackFn handler_cb,
716 GSList *iter = msg->priv->content_handlers;
719 SoupHandlerData *data = iter->data;
721 if (data->handler_cb == handler_cb &&
722 data->user_data == user_data &&
723 data->type == type) {
724 msg->priv->content_handlers =
725 g_slist_remove_link (
726 msg->priv->content_handlers,
736 static inline gboolean
737 ADDED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
739 return ((newflags & find) && !(msg->priv->msg_flags & find));
742 static inline gboolean
743 REMOVED_FLAG (SoupMessage *msg, guint newflags, SoupMessageFlags find)
745 return (!(newflags & find) && (msg->priv->msg_flags & find));
749 soup_message_set_flags (SoupMessage *msg, guint flags)
751 g_return_if_fail (msg != NULL);
753 msg->priv->msg_flags = flags;
757 soup_message_get_flags (SoupMessage *msg)
759 g_return_val_if_fail (msg != NULL, 0);
761 return msg->priv->msg_flags;
765 soup_message_set_http_version (SoupMessage *msg, SoupHttpVersion version)
767 g_return_if_fail (msg != NULL);
769 msg->priv->http_version = version;
773 soup_message_set_error (SoupMessage *msg, SoupKnownErrorCode errcode)
775 g_return_if_fail (msg != NULL);
776 g_return_if_fail (errcode != 0);
778 g_free ((gchar *) msg->errorphrase);
780 msg->errorcode = errcode;
781 msg->errorclass = soup_get_error_class (errcode);
782 msg->errorphrase = g_strdup (soup_get_error_phrase (errcode));
786 soup_message_set_error_full (SoupMessage *msg,
788 const gchar *errphrase)
790 g_return_if_fail (msg != NULL);
791 g_return_if_fail (errcode != 0);
792 g_return_if_fail (errphrase != NULL);
794 g_free ((gchar *) msg->errorphrase);
796 msg->errorcode = errcode;
797 msg->errorclass = soup_get_error_class (errcode);
798 msg->errorphrase = g_strdup (errphrase);
802 soup_message_set_handler_error (SoupMessage *msg,
804 const gchar *errphrase)
806 g_return_if_fail (msg != NULL);
807 g_return_if_fail (errcode != 0);
808 g_return_if_fail (errphrase != NULL);
810 g_free ((gchar *) msg->errorphrase);
812 msg->errorcode = errcode;
813 msg->errorclass = SOUP_ERROR_CLASS_HANDLER;
814 msg->errorphrase = g_strdup (errphrase);
820 } error_code_phrases [] = {
822 * SOUP_ERROR_CLASS_TRANSPORT
824 { SOUP_ERROR_CANCELLED, "Cancelled" },
825 { SOUP_ERROR_CANT_CONNECT, "Cannot connect to destination" },
826 { SOUP_ERROR_CANT_CONNECT_PROXY, "Cannot connect to proxy" },
827 { SOUP_ERROR_IO, "Connection terminated "
829 { SOUP_ERROR_MALFORMED, "Message Corrupt" },
830 { SOUP_ERROR_CANT_AUTHENTICATE, "Authentication Failed" },
831 { SOUP_ERROR_CANT_AUTHENTICATE_PROXY, "Proxy Authentication Failed" },
834 * SOUP_ERROR_CLASS_INFORMATIONAL
836 { SOUP_ERROR_CONTINUE, "Continue" },
837 { SOUP_ERROR_PROTOCOL_SWITCH, "Protocol Switch" },
838 { SOUP_ERROR_DAV_PROCESSING, "Processing" },
841 * SOUP_ERROR_CLASS_SUCCESS
843 { SOUP_ERROR_OK, "OK" },
844 { SOUP_ERROR_CREATED, "Created" },
845 { SOUP_ERROR_ACCEPTED, "Accepted" },
846 { SOUP_ERROR_NON_AUTHORITATIVE, "Non-Authoritative" },
847 { SOUP_ERROR_NO_CONTENT, "No Content" },
848 { SOUP_ERROR_RESET_CONTENT, "Reset Content" },
849 { SOUP_ERROR_PARTIAL_CONTENT, "Partial Content" },
850 { SOUP_ERROR_DAV_MULTISTATUS, "Multi-Status" },
853 * SOUP_ERROR_CLASS_REDIRECT
855 { SOUP_ERROR_MULTIPLE_CHOICES, "Multiple Choices" },
856 { SOUP_ERROR_MOVED_PERMANANTLY, "Moved Permanantly" },
857 { SOUP_ERROR_FOUND, "Found" },
858 { SOUP_ERROR_SEE_OTHER, "See Other" },
859 { SOUP_ERROR_NOT_MODIFIED, "Not Modified" },
860 { SOUP_ERROR_USE_PROXY, "Use Proxy" },
861 { SOUP_ERROR_TEMPORARY_REDIRECT, "Temporary Redirect" },
864 * SOUP_ERROR_CLASS_CLIENT_ERROR
866 { SOUP_ERROR_BAD_REQUEST, "Bad Request" },
867 { SOUP_ERROR_UNAUTHORIZED, "Unauthorized" },
868 { SOUP_ERROR_PAYMENT_REQUIRED, "Payment Required" },
869 { SOUP_ERROR_FORBIDDEN, "Forbidden" },
870 { SOUP_ERROR_NOT_FOUND, "Not Found" },
871 { SOUP_ERROR_METHOD_NOT_ALLOWED, "Method Not Allowed" },
872 { SOUP_ERROR_NOT_ACCEPTABLE, "Not Acceptable" },
873 { SOUP_ERROR_PROXY_UNAUTHORIZED, "Proxy Unauthorized" },
874 { SOUP_ERROR_TIMED_OUT, "Timed Out" },
875 { SOUP_ERROR_CONFLICT, "Conflict" },
876 { SOUP_ERROR_GONE, "Gone" },
877 { SOUP_ERROR_LENGTH_REQUIRED, "Length Required" },
878 { SOUP_ERROR_PRECONDITION_FAILED, "Precondition Failed" },
879 { SOUP_ERROR_BODY_TOO_LARGE, "Entity Body Too Large" },
880 { SOUP_ERROR_URI_TOO_LARGE, "Request-URI Too Large" },
881 { SOUP_ERROR_UNKNOWN_MEDIA_TYPE, "Unknown Media Type" },
882 { SOUP_ERROR_INVALID_RANGE, "Invalid Range" },
883 { SOUP_ERROR_EXPECTATION_FAILED, "Expectation Failed" },
884 { SOUP_ERROR_DAV_UNPROCESSABLE, "Unprocessable Entity" },
885 { SOUP_ERROR_DAV_LOCKED, "Locked" },
886 { SOUP_ERROR_DAV_DEPENDENCY_FAILED, "Dependency Failed" },
889 * SOUP_ERROR_CLASS_SERVER_ERROR
891 { SOUP_ERROR_INTERNAL, "Internal Server Error" },
892 { SOUP_ERROR_NOT_IMPLEMENTED, "Not Implemented" },
893 { SOUP_ERROR_BAD_GATEWAY, "Bad Gateway" },
894 { SOUP_ERROR_SERVICE_UNAVAILABLE, "Service Unavailable" },
895 { SOUP_ERROR_GATEWAY_TIMEOUT, "Gateway Timeout" },
896 { SOUP_ERROR_VERSION_UNSUPPORTED, "Version Unsupported" },
897 { SOUP_ERROR_DAV_OUT_OF_SPACE, "Out Of Space" },
903 soup_get_error_phrase (SoupKnownErrorCode errcode)
907 for (i = 0; error_code_phrases [i].sc; i++) {
908 if (error_code_phrases [i].sc == (guint) errcode)
909 return error_code_phrases [i].phrase;
912 return "Unknown Error";
916 soup_get_error_class (SoupKnownErrorCode errcode)
918 if (errcode < 100) return SOUP_ERROR_CLASS_TRANSPORT;
919 if (errcode < 200) return SOUP_ERROR_CLASS_INFORMATIONAL;
920 if (errcode < 300) return SOUP_ERROR_CLASS_SUCCESS;
921 if (errcode < 400) return SOUP_ERROR_CLASS_REDIRECT;
922 if (errcode < 500) return SOUP_ERROR_CLASS_CLIENT_ERROR;
923 if (errcode < 600) return SOUP_ERROR_CLASS_SERVER_ERROR;
924 return SOUP_ERROR_CLASS_UNKNOWN;