#include <stdlib.h>
#include <string.h>
+#include "soup-address.h"
#include "soup-auth.h"
+#include "soup-enum-types.h"
#include "soup-marshal.h"
#include "soup-message.h"
#include "soup-message-private.h"
#include "soup-misc.h"
-#include "soup-server-message.h"
#include "soup-uri.h"
+/**
+ * SECTION:soup-message
+ * @short_description: An HTTP request and response.
+ * @see_also: #SoupMessageHeaders, #SoupMessageBody
+ *
+ * A #SoupMessage represents an HTTP message that is being sent or
+ * received.
+ *
+ * For client-side usage, you would create a #SoupMessage with
+ * soup_message_new() or soup_message_new_from_uri(), set up its
+ * fields appropriate, and send it via a #SoupSession.
+ *
+ * For server-side usage, #SoupServer will create #SoupMessage<!--
+ * -->s automatically for incoming requests, which your application
+ * will receive via handlers.
+ *
+ * Note that libsoup's terminology here does not quite match the HTTP
+ * specification: in RFC 2616, an "HTTP-message" is
+ * <emphasis>either</emphasis> a Request, <emphasis>or</emphasis> a
+ * Response. In libsoup, a #SoupMessage combines both the request and
+ * the response.
+ **/
+
+/**
+ * SoupMessage:
+ * @method: the HTTP method
+ * @status_code: the HTTP status code
+ * @reason_phrase: the status phrase associated with @status_code
+ * @request_body: the request body
+ * @request_headers: the request headers
+ * @response_body: the response body
+ * @response_headers: the response headers
+ *
+ * Represents an HTTP message being sent or received.
+ *
+ * As described in the #SoupMessageBody documentation, the
+ * @request_body and @response_body %data fields will not necessarily
+ * be filled in at all times. When they are filled in, they will be
+ * terminated with a '\0' byte (which is not included in the %length),
+ * so you can use them as ordinary C strings (assuming that you know
+ * that the body doesn't have any other '\0' bytes).
+ *
+ * For a client-side #SoupMessage, @request_body's %data is usually
+ * filled in right before libsoup writes the request to the network,
+ * but you should not count on this; use soup_message_body_flatten()
+ * if you want to ensure that %data is filled in. @response_body's
+ * %data will be filled in before #SoupMessage::finished is emitted.
+ *
+ * For a server-side #SoupMessage, @request_body's %data will be
+ * filled in before #SoupMessage::got_body is emitted.
+ *
+ * To prevent the %data field from being filled in at all (eg, if you
+ * are handling the data from a #SoupMessage::got_chunk, and so don't
+ * need to see it all at the end), call
+ * soup_message_body_set_accumulate() on @response_body or
+ * @request_body as appropriate, passing %FALSE.
+ **/
+
G_DEFINE_TYPE (SoupMessage, soup_message, G_TYPE_OBJECT)
enum {
WROTE_INFORMATIONAL,
WROTE_HEADERS,
WROTE_CHUNK,
+ WROTE_BODY_DATA,
WROTE_BODY,
GOT_INFORMATIONAL,
static guint signals[LAST_SIGNAL] = { 0 };
-static void wrote_body (SoupMessage *req);
-static void got_headers (SoupMessage *req);
-static void got_chunk (SoupMessage *req);
+enum {
+ PROP_0,
+
+ PROP_METHOD,
+ PROP_URI,
+ PROP_HTTP_VERSION,
+ PROP_FLAGS,
+ PROP_SERVER_SIDE,
+ PROP_STATUS_CODE,
+ PROP_REASON_PHRASE,
+
+ LAST_PROP
+};
+
static void got_body (SoupMessage *req);
static void restarted (SoupMessage *req);
static void finished (SoupMessage *req);
-static void free_chunks (SoupMessage *msg);
+
+static void set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec);
+static void get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec);
static void
soup_message_init (SoupMessage *msg)
{
- msg->status = SOUP_MESSAGE_STATUS_IDLE;
-
- msg->request_headers = g_hash_table_new (soup_str_case_hash,
- soup_str_case_equal);
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- msg->response_headers = g_hash_table_new (soup_str_case_hash,
- soup_str_case_equal);
+ priv->io_status = SOUP_MESSAGE_IO_STATUS_IDLE;
+ priv->http_version = priv->orig_http_version = SOUP_HTTP_1_1;
- SOUP_MESSAGE_GET_PRIVATE (msg)->http_version = SOUP_HTTP_1_1;
+ msg->request_body = soup_message_body_new ();
+ msg->request_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
+ msg->response_body = soup_message_body_new ();
+ msg->response_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
}
static void
SoupMessage *msg = SOUP_MESSAGE (object);
SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- soup_message_io_stop (msg);
+ soup_message_io_cleanup (msg);
+ if (priv->chunk_allocator_dnotify)
+ priv->chunk_allocator_dnotify (priv->chunk_allocator_data);
if (priv->uri)
soup_uri_free (priv->uri);
+ if (priv->addr)
+ g_object_unref (priv->addr);
if (priv->auth)
g_object_unref (priv->auth);
if (priv->proxy_auth)
g_object_unref (priv->proxy_auth);
- if (msg->request.owner == SOUP_BUFFER_SYSTEM_OWNED)
- g_free (msg->request.body);
- if (msg->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
- g_free (msg->response.body);
- free_chunks (msg);
-
- soup_message_clear_headers (msg->request_headers);
- g_hash_table_destroy (msg->request_headers);
-
- soup_message_clear_headers (msg->response_headers);
- g_hash_table_destroy (msg->response_headers);
+ soup_message_body_free (msg->request_body);
+ soup_message_headers_free (msg->request_headers);
+ soup_message_body_free (msg->response_body);
+ soup_message_headers_free (msg->response_headers);
- g_slist_foreach (priv->content_handlers, (GFunc) g_free, NULL);
- g_slist_free (priv->content_handlers);
-
- g_free ((char *) msg->reason_phrase);
+ g_free (msg->reason_phrase);
G_OBJECT_CLASS (soup_message_parent_class)->finalize (object);
}
g_type_class_add_private (message_class, sizeof (SoupMessagePrivate));
/* virtual method definition */
- message_class->wrote_body = wrote_body;
- message_class->got_headers = got_headers;
- message_class->got_chunk = got_chunk;
message_class->got_body = got_body;
message_class->restarted = restarted;
message_class->finished = finished;
/* virtual method override */
object_class->finalize = finalize;
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
/* signals */
* @msg: the message
*
* Emitted immediately after writing a 1xx (Informational)
- * response for a message.
+ * response for a (server-side) message.
**/
signals[WROTE_INFORMATIONAL] =
g_signal_new ("wrote_informational",
* SoupMessage::wrote-headers:
* @msg: the message
*
- * Emitted immediately after writing the headers for a message.
+ * Emitted immediately after writing the headers for a
+ * message. (For a client-side message, this is after writing
+ * the request headers; for a server-side message, it is after
+ * writing the response headers.)
**/
signals[WROTE_HEADERS] =
g_signal_new ("wrote_headers",
* @msg: the message
*
* Emitted immediately after writing a body chunk for a message.
+ *
+ * Note that this signal is not parallel to
+ * #SoupMessage::got_chunk; it is emitted only when a complete
+ * chunk (added with soup_message_body_append() or
+ * soup_message_body_append_buffer()) has been written. To get
+ * more useful continuous progress information, use
+ * #SoupMessage::wrote_body_data.
**/
signals[WROTE_CHUNK] =
g_signal_new ("wrote_chunk",
G_TYPE_NONE, 0);
/**
+ * SoupMessage::wrote-body-data:
+ * @msg: the message
+ * @chunk: the data written
+ *
+ * Emitted immediately after writing a portion of the message
+ * body to the network.
+ *
+ * Unlike #SoupMessage::wrote_chunk, this is emitted after
+ * every successful write() call, not only after finishing a
+ * complete "chunk".
+ **/
+ signals[WROTE_BODY_DATA] =
+ g_signal_new ("wrote_body_data",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0, /* FIXME after next ABI break */
+ NULL, NULL,
+ soup_marshal_NONE__BOXED,
+ G_TYPE_NONE, 1,
+ SOUP_TYPE_BUFFER);
+
+ /**
* SoupMessage::wrote-body:
* @msg: the message
*
- * Emitted immediately after writing the complete body for a message.
+ * Emitted immediately after writing the complete body for a
+ * message. (For a client-side message, this means that
+ * libsoup is done writing and is now waiting for the response
+ * from the server. For a server-side message, this means that
+ * libsoup has finished writing the response and is nearly
+ * done with the message.)
**/
signals[WROTE_BODY] =
g_signal_new ("wrote_body",
* @msg: the message
*
* Emitted after receiving a 1xx (Informational) response for
- * a message.
+ * a (client-side) message. The response_headers will be
+ * filled in with the headers associated with the
+ * informational response; however, those header values will
+ * be erased after this signal is done.
+ *
+ * If you cancel or requeue @msg while processing this signal,
+ * then the current HTTP I/O will be stopped after this signal
+ * emission finished, and @msg's connection will be closed.
**/
signals[GOT_INFORMATIONAL] =
g_signal_new ("got_informational",
* @msg: the message
*
* Emitted after receiving all message headers for a message.
+ * (For a client-side message, this is after receiving the
+ * Status-Line and response headers; for a server-side
+ * message, it is after receiving the Request-Line and request
+ * headers.)
+ *
+ * See also soup_message_add_header_handler() and
+ * soup_message_add_status_code_handler(), which can be used
+ * to connect to a subset of emissions of this signal.
+ *
+ * If you cancel or requeue @msg while processing this signal,
+ * then the current HTTP I/O will be stopped after this signal
+ * emission finished, and @msg's connection will be closed.
+ * (If you need to requeue a message--eg, after handling
+ * authentication or redirection--it is usually better to
+ * requeue it from a #SoupMessage::got_body handler rather
+ * than a #SoupMessage::got_header handler, so that the
+ * existing HTTP connection can be reused.)
**/
signals[GOT_HEADERS] =
g_signal_new ("got_headers",
/**
* SoupMessage::got-chunk:
* @msg: the message
+ * @chunk: the just-read chunk
*
* Emitted after receiving a chunk of a message body. Note
* that "chunk" in this context means any subpiece of the
* body, not necessarily the specific HTTP 1.1 chunks sent by
* the other side.
+ *
+ * If you cancel or requeue @msg while processing this signal,
+ * then the current HTTP I/O will be stopped after this signal
+ * emission finished, and @msg's connection will be closed.
**/
signals[GOT_CHUNK] =
g_signal_new ("got_chunk",
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (SoupMessageClass, got_chunk),
NULL, NULL,
- soup_marshal_NONE__NONE,
- G_TYPE_NONE, 0);
+ soup_marshal_NONE__BOXED,
+ G_TYPE_NONE, 1,
+ /* Use %G_SIGNAL_TYPE_STATIC_SCOPE so that
+ * the %SOUP_MEMORY_TEMPORARY buffers used
+ * by soup-message-io.c when emitting this
+ * signal don't get forcibly copied by
+ * g_signal_emit().
+ */
+ SOUP_TYPE_BUFFER | G_SIGNAL_TYPE_STATIC_SCOPE);
/**
* SoupMessage::got-body:
* @msg: the message
*
- * Emitted after receiving the complete message body.
+ * Emitted after receiving the complete message body. (For a
+ * server-side message, this means it has received the request
+ * body. For a client-side message, this means it has received
+ * the response body and is nearly done with the message.)
+ *
+ * See also soup_message_add_header_handler() and
+ * soup_message_add_status_code_handler(), which can be used
+ * to connect to a subset of emissions of this signal.
**/
signals[GOT_BODY] =
g_signal_new ("got_body",
* SoupMessage::restarted:
* @msg: the message
*
- * Emitted when a message is about to be re-queued.
+ * Emitted when a request that was already sent once is now
+ * being sent again (eg, because the first attempt received a
+ * redirection response, or because we needed to use
+ * authentication).
**/
signals[RESTARTED] =
g_signal_new ("restarted",
* @msg: the message
*
* Emitted when all HTTP processing is finished for a message.
- * (After #read-body for client-side code, or after
- * #wrote-body for server-side code.)
+ * (After #SoupMessage::got_body for client-side messages, or
+ * after #SoupMessage::wrote_body for server-side messages.)
**/
signals[FINISHED] =
g_signal_new ("finished",
NULL, NULL,
soup_marshal_NONE__NONE,
G_TYPE_NONE, 0);
+
+ /* properties */
+ g_object_class_install_property (
+ object_class, PROP_METHOD,
+ g_param_spec_string (SOUP_MESSAGE_METHOD,
+ "Method",
+ "The message's HTTP method",
+ SOUP_METHOD_GET,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (
+ object_class, PROP_URI,
+ g_param_spec_boxed (SOUP_MESSAGE_URI,
+ "URI",
+ "The message's Request-URI",
+ SOUP_TYPE_URI,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (
+ object_class, PROP_HTTP_VERSION,
+ g_param_spec_enum (SOUP_MESSAGE_HTTP_VERSION,
+ "HTTP Version",
+ "The HTTP protocol version to use",
+ SOUP_TYPE_HTTP_VERSION,
+ SOUP_HTTP_1_1,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (
+ object_class, PROP_FLAGS,
+ g_param_spec_flags (SOUP_MESSAGE_FLAGS,
+ "Flags",
+ "Various message options",
+ SOUP_TYPE_MESSAGE_FLAGS,
+ 0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (
+ object_class, PROP_SERVER_SIDE,
+ g_param_spec_boolean (SOUP_MESSAGE_SERVER_SIDE,
+ "Server-side",
+ "Whether or not the message is server-side rather than client-side",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+ g_object_class_install_property (
+ object_class, PROP_STATUS_CODE,
+ g_param_spec_uint (SOUP_MESSAGE_STATUS_CODE,
+ "Status code",
+ "The HTTP response status code",
+ 0, 599, 0,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (
+ object_class, PROP_REASON_PHRASE,
+ g_param_spec_string (SOUP_MESSAGE_REASON_PHRASE,
+ "Reason phrase",
+ "The HTTP response reason phrase",
+ NULL,
+ G_PARAM_READWRITE));
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupMessage *msg = SOUP_MESSAGE (object);
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+
+ switch (prop_id) {
+ case PROP_METHOD:
+ msg->method = g_intern_string (g_value_get_string (value));
+ break;
+ case PROP_URI:
+ soup_message_set_uri (msg, g_value_get_boxed (value));
+ break;
+ case PROP_HTTP_VERSION:
+ soup_message_set_http_version (msg, g_value_get_enum (value));
+ break;
+ case PROP_FLAGS:
+ soup_message_set_flags (msg, g_value_get_flags (value));
+ break;
+ case PROP_SERVER_SIDE:
+ priv->server_side = g_value_get_boolean (value);
+ break;
+ case PROP_STATUS_CODE:
+ soup_message_set_status (msg, g_value_get_uint (value));
+ break;
+ case PROP_REASON_PHRASE:
+ soup_message_set_status_full (msg, msg->status_code,
+ g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupMessage *msg = SOUP_MESSAGE (object);
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+
+ switch (prop_id) {
+ case PROP_METHOD:
+ g_value_set_string (value, msg->method);
+ break;
+ case PROP_URI:
+ g_value_set_boxed (value, priv->uri);
+ break;
+ case PROP_HTTP_VERSION:
+ g_value_set_enum (value, priv->http_version);
+ break;
+ case PROP_FLAGS:
+ g_value_set_flags (value, priv->msg_flags);
+ break;
+ case PROP_SERVER_SIDE:
+ g_value_set_boolean (value, priv->server_side);
+ break;
+ case PROP_STATUS_CODE:
+ g_value_set_uint (value, msg->status_code);
+ break;
+ case PROP_REASON_PHRASE:
+ g_value_set_string (value, msg->reason_phrase);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
}
soup_message_new (const char *method, const char *uri_string)
{
SoupMessage *msg;
- SoupUri *uri;
+ SoupURI *uri;
+
+ g_return_val_if_fail (method != NULL, NULL);
+ g_return_val_if_fail (uri_string != NULL, NULL);
uri = soup_uri_new (uri_string);
if (!uri)
return NULL;
-
if (!uri->host) {
soup_uri_free (uri);
return NULL;
}
- msg = g_object_new (SOUP_TYPE_MESSAGE, NULL);
- msg->method = method ? method : SOUP_METHOD_GET;
- SOUP_MESSAGE_GET_PRIVATE (msg)->uri = uri;
-
+ msg = soup_message_new_from_uri (method, uri);
+ soup_uri_free (uri);
return msg;
}
/**
* soup_message_new_from_uri:
* @method: the HTTP method for the created request
- * @uri: the destination endpoint (as a #SoupUri)
+ * @uri: the destination endpoint (as a #SoupURI)
*
* Creates a new empty #SoupMessage, which will connect to @uri
*
* Return value: the new #SoupMessage
*/
SoupMessage *
-soup_message_new_from_uri (const char *method, const SoupUri *uri)
+soup_message_new_from_uri (const char *method, SoupURI *uri)
{
- SoupMessage *msg;
-
- msg = g_object_new (SOUP_TYPE_MESSAGE, NULL);
- msg->method = method ? method : SOUP_METHOD_GET;
- SOUP_MESSAGE_GET_PRIVATE (msg)->uri = soup_uri_copy (uri);
-
- return msg;
+ return g_object_new (SOUP_TYPE_MESSAGE,
+ SOUP_MESSAGE_METHOD, method,
+ SOUP_MESSAGE_URI, uri,
+ NULL);
}
/**
* soup_message_set_request:
* @msg: the message
* @content_type: MIME Content-Type of the body
- * @req_owner: the #SoupOwnership of the passed data buffer.
+ * @req_use: a #SoupMemoryUse describing how to handle @req_body
* @req_body: a data buffer containing the body of the message request.
* @req_length: the byte length of @req_body.
*
- * Convenience function to set the request body of a #SoupMessage
+ * Convenience function to set the request body of a #SoupMessage. If
+ * @content_type is %NULL, the request body must be empty as well.
*/
void
-soup_message_set_request (SoupMessage *msg,
- const char *content_type,
- SoupOwnership req_owner,
- char *req_body,
- gulong req_length)
+soup_message_set_request (SoupMessage *msg,
+ const char *content_type,
+ SoupMemoryUse req_use,
+ const char *req_body,
+ gsize req_length)
{
g_return_if_fail (SOUP_IS_MESSAGE (msg));
- g_return_if_fail (content_type != NULL);
- g_return_if_fail (req_body != NULL || req_length == 0);
-
- soup_message_add_header (msg->request_headers,
- "Content-Type", content_type);
- msg->request.owner = req_owner;
- msg->request.body = req_body;
- msg->request.length = req_length;
+ g_return_if_fail (content_type != NULL || req_length == 0);
+
+ if (content_type) {
+ soup_message_headers_replace (msg->request_headers,
+ "Content-Type", content_type);
+ soup_message_body_append (msg->request_body, req_use,
+ req_body, req_length);
+ } else {
+ soup_message_headers_remove (msg->request_headers,
+ "Content-Type");
+ soup_message_body_truncate (msg->request_body);
+ }
}
/**
* soup_message_set_response:
* @msg: the message
* @content_type: MIME Content-Type of the body
- * @resp_owner: the #SoupOwnership of the passed data buffer.
+ * @resp_use: a #SoupMemoryUse describing how to handle @resp_body
* @resp_body: a data buffer containing the body of the message response.
* @resp_length: the byte length of @resp_body.
*
- * Convenience function to set the response body of a #SoupMessage
+ * Convenience function to set the response body of a #SoupMessage. If
+ * @content_type is %NULL, the response body must be empty as well.
*/
void
-soup_message_set_response (SoupMessage *msg,
- const char *content_type,
- SoupOwnership resp_owner,
- char *resp_body,
- gulong resp_length)
+soup_message_set_response (SoupMessage *msg,
+ const char *content_type,
+ SoupMemoryUse resp_use,
+ const char *resp_body,
+ gsize resp_length)
{
g_return_if_fail (SOUP_IS_MESSAGE (msg));
- g_return_if_fail (content_type != NULL);
- g_return_if_fail (resp_body != NULL || resp_length == 0);
-
- soup_message_add_header (msg->response_headers,
- "Content-Type", content_type);
- msg->response.owner = resp_owner;
- msg->response.body = resp_body;
- msg->response.length = resp_length;
+ g_return_if_fail (content_type != NULL || resp_length == 0);
+
+ if (content_type) {
+ soup_message_headers_replace (msg->response_headers,
+ "Content-Type", content_type);
+ soup_message_body_append (msg->response_body, resp_use,
+ resp_body, resp_length);
+ } else {
+ soup_message_headers_remove (msg->response_headers,
+ "Content-Type");
+ soup_message_body_truncate (msg->response_body);
+ }
}
/**
g_signal_emit (msg, signals[WROTE_CHUNK], 0);
}
-static void
-wrote_body (SoupMessage *req)
+/**
+ * soup_message_wrote_body_data:
+ * @msg: a #SoupMessage
+ * @chunk: the data written
+ *
+ * Emits the %wrote_body_data signal, indicating that the IO layer
+ * finished writing a portion of @msg's body.
+ **/
+void
+soup_message_wrote_body_data (SoupMessage *msg, SoupBuffer *chunk)
{
- g_object_ref (req);
- soup_message_run_handlers (req, SOUP_HANDLER_POST_REQUEST);
- g_object_unref (req);
+ g_signal_emit (msg, signals[WROTE_BODY_DATA], 0, chunk);
}
/**
g_signal_emit (msg, signals[GOT_INFORMATIONAL], 0);
}
-static void
-got_headers (SoupMessage *req)
-{
- g_object_ref (req);
- soup_message_run_handlers (req, SOUP_HANDLER_PRE_BODY);
- if (SOUP_MESSAGE_IS_STARTING (req))
- g_signal_stop_emission (req, signals[GOT_HEADERS], 0);
- g_object_unref (req);
-}
-
/**
* soup_message_got_headers:
* @msg: a #SoupMessage
g_signal_emit (msg, signals[GOT_HEADERS], 0);
}
-static void
-got_chunk (SoupMessage *req)
-{
- g_object_ref (req);
- soup_message_run_handlers (req, SOUP_HANDLER_BODY_CHUNK);
- if (SOUP_MESSAGE_IS_STARTING (req))
- g_signal_stop_emission (req, signals[GOT_CHUNK], 0);
- g_object_unref (req);
-}
-
/**
* soup_message_got_chunk:
* @msg: a #SoupMessage
+ * @chunk: the newly-read chunk
*
* Emits the %got_chunk signal, indicating that the IO layer finished
* reading a chunk of @msg's body.
**/
void
-soup_message_got_chunk (SoupMessage *msg)
+soup_message_got_chunk (SoupMessage *msg, SoupBuffer *chunk)
{
- g_signal_emit (msg, signals[GOT_CHUNK], 0);
+ g_signal_emit (msg, signals[GOT_CHUNK], 0, chunk);
}
static void
got_body (SoupMessage *req)
{
- g_object_ref (req);
- soup_message_run_handlers (req, SOUP_HANDLER_POST_BODY);
- if (SOUP_MESSAGE_IS_STARTING (req))
- g_signal_stop_emission (req, signals[GOT_BODY], 0);
- g_object_unref (req);
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req);
+ SoupMessageBody *body;
+
+ body = priv->server_side ? req->request_body : req->response_body;
+ if (soup_message_body_get_accumulate (body)) {
+ SoupBuffer *buffer;
+
+ buffer = soup_message_body_flatten (body);
+ soup_buffer_free (buffer);
+ }
}
/**
static void
finished (SoupMessage *req)
{
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req);
+
soup_message_io_stop (req);
- req->status = SOUP_MESSAGE_STATUS_FINISHED;
+ priv->io_status = SOUP_MESSAGE_IO_STATUS_FINISHED;
}
/**
g_signal_emit (msg, signals[FINISHED], 0);
}
-static gboolean
-free_header_list (gpointer name, gpointer vals, gpointer user_data)
-{
- g_free (name);
- g_slist_foreach (vals, (GFunc) g_free, NULL);
- g_slist_free (vals);
-
- return TRUE;
-}
-
-/**
- * soup_message_clear_headers:
- * @hash: a header table (the %request_headers or %response_headers
- * field of a #SoupMessage)
- *
- * Clears @hash.
- **/
-void
-soup_message_clear_headers (GHashTable *hash)
+static void
+header_handler_free (gpointer header_name, GClosure *closure)
{
- g_return_if_fail (hash != NULL);
-
- g_hash_table_foreach_remove (hash, free_header_list, NULL);
+ g_free (header_name);
}
-/**
- * soup_message_remove_header:
- * @hash: a header table (the %request_headers or %response_headers
- * field of a #SoupMessage)
- * @name: the header name to remove
- *
- * Removes @name from @hash. If there are multiple values for @name,
- * they are all removed.
- **/
-void
-soup_message_remove_header (GHashTable *hash, const char *name)
+static void
+header_handler_metamarshal (GClosure *closure, GValue *return_value,
+ guint n_param_values, const GValue *param_values,
+ gpointer invocation_hint, gpointer marshal_data)
{
- gpointer old_key, old_vals;
+ SoupMessage *msg = g_value_get_object (¶m_values[0]);
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+ const char *header_name = marshal_data;
+ SoupMessageHeaders *hdrs;
- g_return_if_fail (hash != NULL);
- g_return_if_fail (name != NULL || name[0] != '\0');
+ if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING)
+ return;
- if (g_hash_table_lookup_extended (hash, name, &old_key, &old_vals)) {
- g_hash_table_remove (hash, name);
- free_header_list (old_key, old_vals, NULL);
+ hdrs = priv->server_side ? msg->request_headers : msg->response_headers;
+ if (soup_message_headers_get (hdrs, header_name)) {
+ closure->marshal (closure, return_value, n_param_values,
+ param_values, invocation_hint,
+ ((GCClosure *)closure)->callback);
}
}
/**
- * soup_message_add_header:
- * @hash: a header table (the %request_headers or %response_headers
- * field of a #SoupMessage)
- * @name: the header name to add
- * @value: the value of the new header
+ * soup_message_add_header_handler:
+ * @msg: a #SoupMessage
+ * @signal: signal to connect the handler to.
+ * @header: HTTP response header to match against
+ * @callback: the header handler
+ * @user_data: data to pass to @handler_cb
*
- * Adds a header with name @name and value @value to @hash. If there
- * was already a header with name @name, this one does not replace it,
- * it is merely added to it.
- **/
-void
-soup_message_add_header (GHashTable *hash, const char *name, const char *value)
-{
- GSList *old_value;
-
- g_return_if_fail (hash != NULL);
- g_return_if_fail (name != NULL || name [0] != '\0');
- g_return_if_fail (value != NULL);
-
- old_value = g_hash_table_lookup (hash, name);
-
- if (old_value)
- old_value = g_slist_append (old_value, g_strdup (value));
- else {
- g_hash_table_insert (hash, g_strdup (name),
- g_slist_append (NULL, g_strdup (value)));
- }
-}
-
-/**
- * soup_message_get_header:
- * @hash: a header table (the %request_headers or %response_headers
- * field of a #SoupMessage)
- * @name: header name.
- *
- * Finds the first header in @hash with name @name.
- *
- * Return value: the header's value or %NULL if not found.
+ * Adds a signal handler to @msg for @signal, as with
+ * g_signal_connect(), but with two differences: the @callback will
+ * only be run if @msg has a header named @header, and it will only be
+ * run if no earlier handler cancelled or requeued the message.
+ *
+ * If @signal is one of the "got" signals (eg, "got_headers"), or
+ * "finished" or "restarted", then @header is matched against the
+ * incoming message headers (that is, the #request_headers for a
+ * client #SoupMessage, or the #response_headers for a server
+ * #SoupMessage). If @signal is one of the "wrote" signals, then
+ * @header is matched against the outgoing message headers.
+ *
+ * Return value: the handler ID from g_signal_connect()
**/
-const char *
-soup_message_get_header (GHashTable *hash, const char *name)
+guint
+soup_message_add_header_handler (SoupMessage *msg,
+ const char *signal,
+ const char *header,
+ GCallback callback,
+ gpointer user_data)
{
- GSList *vals;
+ SoupMessagePrivate *priv;
+ GClosure *closure;
+ char *header_name;
- g_return_val_if_fail (hash != NULL, NULL);
- g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), 0);
+ g_return_val_if_fail (signal != NULL, 0);
+ g_return_val_if_fail (header != NULL, 0);
+ g_return_val_if_fail (callback != NULL, 0);
- vals = g_hash_table_lookup (hash, name);
- if (vals)
- return vals->data;
+ priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- return NULL;
-}
+ closure = g_cclosure_new (callback, user_data, NULL);
-/**
- * soup_message_get_header_list:
- * @hash: a header table (the %request_headers or %response_headers
- * field of a #SoupMessage)
- * @name: header name.
- *
- * Finds all headers in @hash with name @name.
- *
- * Return value: a (possibly empty) list of values of headers with
- * name @name. The caller should not modify or free this list.
- **/
-const GSList *
-soup_message_get_header_list (GHashTable *hash, const char *name)
-{
- g_return_val_if_fail (hash != NULL, NULL);
- g_return_val_if_fail (name != NULL || name [0] != '\0', NULL);
+ header_name = g_strdup (header);
+ g_closure_set_meta_marshal (closure, header_name,
+ header_handler_metamarshal);
+ g_closure_add_finalize_notifier (closure, header_name,
+ header_handler_free);
- return g_hash_table_lookup (hash, name);
+ return g_signal_connect_closure (msg, signal, closure, FALSE);
}
-typedef struct {
- GHFunc func;
- gpointer user_data;
-} SoupMessageForeachHeaderData;
-
static void
-foreach_value_in_list (gpointer name, gpointer value, gpointer user_data)
+status_handler_metamarshal (GClosure *closure, GValue *return_value,
+ guint n_param_values, const GValue *param_values,
+ gpointer invocation_hint, gpointer marshal_data)
{
- GSList *vals = value;
- SoupMessageForeachHeaderData *data = user_data;
+ SoupMessage *msg = g_value_get_object (¶m_values[0]);
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+ guint status = GPOINTER_TO_UINT (marshal_data);
+
+ if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING)
+ return;
- while (vals) {
- (*data->func) (name, vals->data, data->user_data);
- vals = vals->next;
+ if (msg->status_code == status) {
+ closure->marshal (closure, return_value, n_param_values,
+ param_values, invocation_hint,
+ ((GCClosure *)closure)->callback);
}
}
/**
- * soup_message_foreach_header:
- * @hash: a header table (the %request_headers or %response_headers
- * field of a #SoupMessage)
- * @func: callback function to run for each header
- * @user_data: data to pass to @func
- *
- * Calls @func once for each header value in @hash. (If there are
- * headers will multiple values, @func will be called once on each
- * value.)
+ * soup_message_add_status_code_handler:
+ * @msg: a #SoupMessage
+ * @signal: signal to connect the handler to.
+ * @status_code: status code to match against
+ * @callback: the header handler
+ * @user_data: data to pass to @handler_cb
+ *
+ * Adds a signal handler to @msg for @signal, as with
+ * g_signal_connect() but with two differences: the @callback will
+ * only be run if @msg has the status @status_code, and it will only
+ * be run if no earlier handler cancelled or requeued the message.
+ *
+ * @signal must be a signal that will be emitted after @msg's status
+ * is set. For a client #SoupMessage, this means it can't be a "wrote"
+ * signal. For a server #SoupMessage, this means it can't be a "got"
+ * signal.
+ *
+ * Return value: the handler ID from g_signal_connect()
**/
-void
-soup_message_foreach_header (GHashTable *hash, GHFunc func, gpointer user_data)
+guint
+soup_message_add_status_code_handler (SoupMessage *msg,
+ const char *signal,
+ guint status_code,
+ GCallback callback,
+ gpointer user_data)
{
- SoupMessageForeachHeaderData data;
+ GClosure *closure;
- g_return_if_fail (hash != NULL);
- g_return_if_fail (func != NULL);
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), 0);
+ g_return_val_if_fail (signal != NULL, 0);
+ g_return_val_if_fail (callback != NULL, 0);
+
+ closure = g_cclosure_new (callback, user_data, NULL);
+ g_closure_set_meta_marshal (closure, GUINT_TO_POINTER (status_code),
+ status_handler_metamarshal);
- data.func = func;
- data.user_data = user_data;
- g_hash_table_foreach (hash, foreach_value_in_list, &data);
+ return g_signal_connect_closure (msg, signal, closure, FALSE);
}
+
/**
* soup_message_set_auth:
* @msg: a #SoupMessage
if (priv->auth) {
g_object_unref (priv->auth);
- soup_message_remove_header (msg->request_headers, "Authorization");
+ soup_message_headers_remove (msg->request_headers,
+ "Authorization");
}
priv->auth = auth;
if (!priv->auth)
g_object_ref (priv->auth);
token = soup_auth_get_authorization (auth, msg);
- soup_message_add_header (msg->request_headers, "Authorization", token);
+ soup_message_headers_replace (msg->request_headers,
+ "Authorization", token);
g_free (token);
}
if (priv->proxy_auth) {
g_object_unref (priv->proxy_auth);
- soup_message_remove_header (msg->request_headers,
- "Proxy-Authorization");
+ soup_message_headers_remove (msg->request_headers,
+ "Proxy-Authorization");
}
priv->proxy_auth = auth;
if (!priv->proxy_auth)
g_object_ref (priv->proxy_auth);
token = soup_auth_get_authorization (auth, msg);
- soup_message_add_header (msg->request_headers,
- "Proxy-Authorization", token);
+ soup_message_headers_replace (msg->request_headers,
+ "Proxy-Authorization", token);
g_free (token);
}
void
soup_message_cleanup_response (SoupMessage *req)
{
- if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED)
- g_free (req->response.body);
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req);
- req->response.owner = 0;
- req->response.body = NULL;
- req->response.length = 0;
-
- free_chunks (req);
-
- soup_message_clear_headers (req->response_headers);
+ soup_message_body_truncate (req->response_body);
+ soup_message_headers_clear (req->response_headers);
req->status_code = SOUP_STATUS_NONE;
if (req->reason_phrase) {
- g_free ((char *) req->reason_phrase);
+ g_free (req->reason_phrase);
req->reason_phrase = NULL;
}
+ priv->http_version = priv->orig_http_version;
+
+ g_object_notify (G_OBJECT (req), SOUP_MESSAGE_STATUS_CODE);
+ g_object_notify (G_OBJECT (req), SOUP_MESSAGE_REASON_PHRASE);
+ g_object_notify (G_OBJECT (req), SOUP_MESSAGE_HTTP_VERSION);
}
/**
+ * SoupMessageFlags:
+ * @SOUP_MESSAGE_NO_REDIRECT: The session should not follow redirect
+ * (3xx) responses received by this message.
+ * @SOUP_MESSAGE_OVERWRITE_CHUNKS: Deprecated: equivalent to calling
+ * soup_message_body_set_accumulate() on the incoming message body
+ * (ie, %response_body for a client-side request), passing %FALSE.
+ *
+ * Various flags that can be set on a #SoupMessage to alter its
+ * behavior.
+ **/
+
+/**
* soup_message_set_flags:
* @msg: a #SoupMessage
* @flags: a set of #SoupMessageFlags values
* Sets the specified flags on @msg.
**/
void
-soup_message_set_flags (SoupMessage *msg, guint flags)
+soup_message_set_flags (SoupMessage *msg, SoupMessageFlags flags)
{
+ SoupMessagePrivate *priv;
+
g_return_if_fail (SOUP_IS_MESSAGE (msg));
+ priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- SOUP_MESSAGE_GET_PRIVATE (msg)->msg_flags = flags;
+ if ((priv->msg_flags ^ flags) & SOUP_MESSAGE_OVERWRITE_CHUNKS) {
+ soup_message_body_set_accumulate (
+ priv->server_side ? msg->request_body : msg->response_body,
+ !(flags & SOUP_MESSAGE_OVERWRITE_CHUNKS));
+ }
+
+ priv->msg_flags = flags;
+ g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_FLAGS);
}
/**
*
* Return value: the flags
**/
-guint
+SoupMessageFlags
soup_message_get_flags (SoupMessage *msg)
{
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), 0);
}
/**
+ * SoupHTTPVersion:
+ * @SOUP_HTTP_1_0: HTTP 1.0 (RFC 1945)
+ * @SOUP_HTTP_1_1: HTTP 1.1 (RFC 2616)
+ *
+ * Indicates the HTTP protocol version being used.
+ **/
+
+/**
* soup_message_set_http_version:
* @msg: a #SoupMessage
* @version: the HTTP version
* functionality from being used.
**/
void
-soup_message_set_http_version (SoupMessage *msg, SoupHttpVersion version)
+soup_message_set_http_version (SoupMessage *msg, SoupHTTPVersion version)
{
+ SoupMessagePrivate *priv;
+
g_return_if_fail (SOUP_IS_MESSAGE (msg));
+ priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- SOUP_MESSAGE_GET_PRIVATE (msg)->http_version = version;
+ priv->http_version = version;
+ if (msg->status_code == SOUP_STATUS_NONE)
+ priv->orig_http_version = version;
+ g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_HTTP_VERSION);
}
/**
*
* Return value: the HTTP version
**/
-SoupHttpVersion
+SoupHTTPVersion
soup_message_get_http_version (SoupMessage *msg)
{
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_HTTP_1_0);
* @msg: a #SoupMessage
*
* Determines whether or not @msg's connection can be kept alive for
- * further requests after processing @msg.
+ * further requests after processing @msg, based on the HTTP version,
+ * Connection header, etc.
*
* Return value: %TRUE or %FALSE.
**/
{
const char *c_conn, *s_conn;
- c_conn = soup_message_get_header (msg->request_headers, "Connection");
- s_conn = soup_message_get_header (msg->response_headers, "Connection");
+ c_conn = soup_message_headers_get (msg->request_headers, "Connection");
+ s_conn = soup_message_headers_get (msg->response_headers, "Connection");
if (msg->status_code == SOUP_STATUS_OK &&
- soup_method_get_id (msg->method) == SOUP_METHOD_ID_CONNECT)
+ msg->method == SOUP_METHOD_CONNECT)
return TRUE;
if (SOUP_MESSAGE_GET_PRIVATE (msg)->http_version == SOUP_HTTP_1_0) {
if (!c_conn || !s_conn)
return FALSE;
- if (g_ascii_strcasecmp (c_conn, "Keep-Alive") != 0 ||
- g_ascii_strcasecmp (s_conn, "Keep-Alive") != 0)
+ if (soup_header_contains (c_conn, "Keep-Alive") ||
+ soup_header_contains (s_conn, "Keep-Alive"))
return FALSE;
return TRUE;
} else {
/* Normally persistent unless either side requested otherwise */
- if (c_conn && g_ascii_strcasecmp (c_conn, "close") == 0)
+ if (c_conn && soup_header_contains (c_conn, "close"))
return FALSE;
- if (s_conn && g_ascii_strcasecmp (s_conn, "close") == 0)
+ if (s_conn && soup_header_contains (s_conn, "close"))
return FALSE;
/* But not if the server sent a terminate-by-EOF response */
- if (soup_message_get_response_encoding (msg, NULL) == SOUP_TRANSFER_EOF)
+ if (soup_message_headers_get_encoding (msg->response_headers) == SOUP_ENCODING_EOF)
return FALSE;
return TRUE;
/**
* soup_message_set_uri:
* @msg: a #SoupMessage
- * @uri: the new #SoupUri
+ * @uri: the new #SoupURI
*
* Sets @msg's URI to @uri. If @msg has already been sent and you want
* to re-send it with the new URI, you need to call
* soup_session_requeue_message().
**/
void
-soup_message_set_uri (SoupMessage *msg, const SoupUri *uri)
+soup_message_set_uri (SoupMessage *msg, SoupURI *uri)
{
SoupMessagePrivate *priv;
if (priv->uri)
soup_uri_free (priv->uri);
+ if (priv->addr) {
+ g_object_unref (priv->addr);
+ priv->addr = NULL;
+ }
priv->uri = soup_uri_copy (uri);
+
+ g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_URI);
}
/**
*
* Return value: the URI @msg is targeted for.
**/
-const SoupUri *
+SoupURI *
soup_message_get_uri (SoupMessage *msg)
{
g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
}
/**
- * soup_message_get_request_encoding:
+ * soup_message_get_address:
* @msg: a #SoupMessage
- * @content_length: a pointer to store the Content-Length in (or
- * %NULL).
*
- * Gets @msg's request encoding. For an outgoing (client) request,
- * this is only valid after the message has been fully set up (from
- * the library's perspective, that means not until the message has
- * been queued). For an incoming (server) request, this is valid after
- * the request headers have been read and @msg->request_headers filled
- * in.
+ * Gets the address @msg's URI points to. After first setting the
+ * URI on a message, this will be unresolved, although the message's
+ * session will resolve it before sending the message.
*
- * Return value: the request encoding (which cannot be
- * %SOUP_TRANSFER_UNKNOWN or %SOUP_TRANSFER_EOF). If it is
- * %SOUP_TRANSFER_CONTENT_LENGTH, *@content_length will be set to the
- * request body's length.
+ * Return value: the address @msg's URI points to
**/
-SoupTransferEncoding
-soup_message_get_request_encoding (SoupMessage *msg, guint *content_length)
+SoupAddress *
+soup_message_get_address (SoupMessage *msg)
{
- if (SOUP_IS_SERVER_MESSAGE (msg)) {
- const char *enc, *len;
-
- enc = soup_message_get_header (msg->request_headers,
- "Transfer-Encoding");
- len = soup_message_get_header (msg->request_headers,
- "Content-Length");
- if (enc) {
- if (g_ascii_strcasecmp (enc, "chunked") == 0)
- return SOUP_TRANSFER_CHUNKED;
- else
- return SOUP_TRANSFER_UNKNOWN;
- } else if (len) {
- int lval = atoi (len);
-
- if (lval < 0)
- return SOUP_TRANSFER_UNKNOWN;
- else {
- if (content_length)
- *content_length = lval;
- return SOUP_TRANSFER_CONTENT_LENGTH;
- }
- } else
- return SOUP_TRANSFER_NONE;
- } else {
- if (msg->request.length) {
- if (content_length)
- *content_length = msg->request.length;
- return SOUP_TRANSFER_CONTENT_LENGTH;
- } else
- return SOUP_TRANSFER_NONE;
- }
-}
+ SoupMessagePrivate *priv;
-/**
- * soup_message_get_response_encoding:
- * @msg: a #SoupMessage
- * @content_length: a pointer to store the Content-Length in (or
- * %NULL).
- *
- * Gets @msg's response encoding. For an outgoing (client) request,
- * this is only valid after the response headers have been read and
- * @msg->response_headers filled in. For an incoming (server) request,
- * this is valid after the server handler has run.
- *
- * Note that the returned value is the encoding actually used on the
- * wire; this will not agree with the response headers in some cases
- * (eg, a HEAD response may have a Content-Length header, but will
- * still be considered %SOUP_TRANSFER_NONE by this function).
- *
- * Return value: the response encoding (which will not be
- * %SOUP_TRANSFER_UNKNOWN). If it is %SOUP_TRANSFER_CONTENT_LENGTH,
- * *@content_length will be set to the response body's length.
- **/
-SoupTransferEncoding
-soup_message_get_response_encoding (SoupMessage *msg, guint *content_length)
-{
- SoupMethodId method = soup_method_get_id (msg->method);
-
- if (method == SOUP_METHOD_ID_HEAD ||
- msg->status_code == SOUP_STATUS_NO_CONTENT ||
- msg->status_code == SOUP_STATUS_NOT_MODIFIED ||
- SOUP_STATUS_IS_INFORMATIONAL (msg->status_code))
- return SOUP_TRANSFER_NONE;
-
- if (SOUP_IS_SERVER_MESSAGE (msg)) {
- SoupTransferEncoding enc =
- soup_server_message_get_encoding ((SoupServerMessage *)msg);
- if (enc == SOUP_TRANSFER_UNKNOWN)
- enc = SOUP_TRANSFER_CONTENT_LENGTH;
- if (enc == SOUP_TRANSFER_CONTENT_LENGTH && content_length)
- *content_length = msg->response.length;
- return enc;
- } else {
- const char *enc, *len;
-
- enc = soup_message_get_header (msg->response_headers,
- "Transfer-Encoding");
- len = soup_message_get_header (msg->response_headers,
- "Content-Length");
- if (enc) {
- if (g_ascii_strcasecmp (enc, "chunked") == 0)
- return SOUP_TRANSFER_CHUNKED;
- else
- return SOUP_TRANSFER_UNKNOWN;
- } else if (len) {
- int lval = atoi (len);
-
- if (lval < 0)
- return SOUP_TRANSFER_UNKNOWN;
- else {
- if (content_length)
- *content_length = lval;
- return SOUP_TRANSFER_CONTENT_LENGTH;
- }
- } else if (method == SOUP_METHOD_ID_CONNECT)
- return SOUP_TRANSFER_NONE;
- else
- return SOUP_TRANSFER_EOF;
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
+
+ priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+ if (!priv->addr) {
+ priv->addr = soup_address_new (priv->uri->host,
+ priv->uri->port);
}
+ return priv->addr;
}
/**
g_return_if_fail (SOUP_IS_MESSAGE (msg));
g_return_if_fail (status_code != 0);
- g_free ((char *) msg->reason_phrase);
+ g_free (msg->reason_phrase);
msg->status_code = status_code;
msg->reason_phrase = g_strdup (soup_status_get_phrase (status_code));
+ g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_STATUS_CODE);
+ g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_REASON_PHRASE);
}
/**
g_return_if_fail (status_code != 0);
g_return_if_fail (reason_phrase != NULL);
- g_free ((char *) msg->reason_phrase);
+ g_free (msg->reason_phrase);
msg->status_code = status_code;
msg->reason_phrase = g_strdup (reason_phrase);
+ g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_STATUS_CODE);
+ g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_REASON_PHRASE);
}
-
-/**
- * soup_message_add_chunk:
- * @msg: a #SoupMessage
- * @owner: the ownership of @body
- * @body: body data
- * @length: length of @body
- *
- * Adds a chunk of response data to @body. (Note that currently
- * there is no way to send a request using chunked encoding.)
- **/
void
-soup_message_add_chunk (SoupMessage *msg,
- SoupOwnership owner,
- const char *body,
- guint length)
+soup_message_set_io_status (SoupMessage *msg,
+ SoupMessageIOStatus status)
{
- SoupMessagePrivate *priv;
- SoupDataBuffer *chunk;
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- g_return_if_fail (SOUP_IS_MESSAGE (msg));
- priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- g_return_if_fail (body != NULL || length == 0);
+ priv->io_status = status;
+}
- chunk = g_new0 (SoupDataBuffer, 1);
- if (owner == SOUP_BUFFER_USER_OWNED) {
- chunk->owner = SOUP_BUFFER_SYSTEM_OWNED;
- chunk->body = g_memdup (body, length);
- } else {
- chunk->owner = owner;
- chunk->body = (char *)body;
- }
- chunk->length = length;
+SoupMessageIOStatus
+soup_message_get_io_status (SoupMessage *msg)
+{
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- if (priv->chunks) {
- priv->last_chunk = g_slist_append (priv->last_chunk, chunk);
- priv->last_chunk = priv->last_chunk->next;
- } else {
- priv->chunks = priv->last_chunk =
- g_slist_append (NULL, chunk);
- }
+ return priv->io_status;
}
/**
- * soup_message_add_final_chunk:
- * @msg: a #SoupMessage
+ * SoupChunkAllocator:
+ * @msg: the #SoupMessage the chunk is being allocated for
+ * @max_len: the maximum length that will be read, or 0.
+ * @user_data: the data passed to soup_message_set_chunk_allocator()
+ *
+ * The prototype for a chunk allocation callback. This should allocate
+ * a new #SoupBuffer and return it for the I/O layer to read message
+ * body data off the network into.
+ *
+ * If @max_len is non-0, it indicates the maximum number of bytes that
+ * could be read, based on what is known about the message size. Note
+ * that this might be a very large number, and you should not simply
+ * try to allocate that many bytes blindly. If @max_len is 0, that
+ * means that libsoup does not know how many bytes remain to be read,
+ * and the allocator should return a buffer of a size that it finds
+ * convenient.
*
- * Adds a final, empty chunk of response data to @body. This must
- * be called after adding the last real chunk, to indicate that
- * there is no more data.
+ * If the allocator returns %NULL, the message will be paused. It is
+ * up to the application to make sure that it gets unpaused when it
+ * becomes possible to allocate a new buffer.
+ *
+ * Return value: the new buffer (or %NULL)
**/
-void
-soup_message_add_final_chunk (SoupMessage *msg)
-{
- soup_message_add_chunk (msg, SOUP_BUFFER_STATIC, NULL, 0);
-}
/**
- * soup_message_pop_chunk:
+ * soup_message_set_chunk_allocator:
* @msg: a #SoupMessage
+ * @allocator: the chunk allocator callback
+ * @user_data: data to pass to @allocator
+ * @destroy_notify: destroy notifier to free @user_data when @msg is
+ * destroyed
*
- * Pops a chunk of response data from @msg's chunk list. The caller
- * must free @chunk itself, and must handle the data in @chunk
- * according to its %ownership.
+ * Sets an alternate chunk-allocation function to use when reading
+ * @msg's body. Every time data is available to read, libsoup will
+ * call @allocator, which should return a #SoupBuffer. (See
+ * #SoupChunkAllocator for additional details.) Libsoup will then read
+ * data from the network into that buffer, and update the buffer's
+ * %length to indicate how much data it read.
*
- * Return value: the chunk, or %NULL if there are no chunks left.
+ * Generally, a custom chunk allocator would be used in conjunction
+ * with soup_message_body_set_accumulate() %FALSE and
+ * #SoupMessage::got_chunk, as part of a strategy to avoid unnecessary
+ * copying of data. However, you cannot assume that every call to the
+ * allocator will be followed by a call to your %got_chunk handler; if
+ * an I/O error occurs, then the buffer will be unreffed without ever
+ * having been used. If your buffer-allocation strategy requires
+ * special cleanup, use soup_buffer_new_with_owner() rather than doing
+ * the cleanup from the %got_chunk handler.
+ *
+ * The other thing to remember when using non-accumulating message
+ * bodies is that the buffer passed to the %got_chunk handler will be
+ * unreffed after the handler returns, just as it would be in the
+ * non-custom-allocated case. If you want to hand the chunk data off
+ * to some other part of your program to use later, you'll need to ref
+ * the #SoupBuffer (or its owner, in the soup_buffer_new_with_owner()
+ * case) to ensure that the data remains valid.
**/
-SoupDataBuffer *
-soup_message_pop_chunk (SoupMessage *msg)
+void
+soup_message_set_chunk_allocator (SoupMessage *msg,
+ SoupChunkAllocator allocator,
+ gpointer user_data,
+ GDestroyNotify destroy_notify)
{
SoupMessagePrivate *priv;
- SoupDataBuffer *chunk;
- g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
- priv = SOUP_MESSAGE_GET_PRIVATE (msg);
-
- if (!priv->chunks)
- return NULL;
-
- chunk = priv->chunks->data;
- priv->chunks = g_slist_remove (priv->chunks, chunk);
- if (!priv->chunks)
- priv->last_chunk = NULL;
-
- return chunk;
-}
-
-static void
-free_chunks (SoupMessage *msg)
-{
- SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- SoupDataBuffer *chunk;
- GSList *ch;
+ g_return_if_fail (SOUP_IS_MESSAGE (msg));
- for (ch = priv->chunks; ch; ch = ch->next) {
- chunk = ch->data;
+ priv = SOUP_MESSAGE_GET_PRIVATE (msg);
- if (chunk->owner == SOUP_BUFFER_SYSTEM_OWNED)
- g_free (chunk->body);
- g_free (chunk);
- }
+ if (priv->chunk_allocator_dnotify)
+ priv->chunk_allocator_dnotify (priv->chunk_allocator_data);
- g_slist_free (priv->chunks);
- priv->chunks = priv->last_chunk = NULL;
+ priv->chunk_allocator = allocator;
+ priv->chunk_allocator_data = user_data;
+ priv->chunk_allocator_dnotify = destroy_notify;
}