/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/*
 * soup-message-body.c: SoupMessage request/response bodies
 *
 * Copyright (C) 2000-2003, Ximian, Inc.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include "soup-message-body.h"
#include "soup.h"

/**
 * SoupMemoryUse:
 * @SOUP_MEMORY_STATIC: The memory is statically allocated and
 *   constant; libsoup can use the passed-in buffer directly and not
 *   need to worry about it being modified or freed.
 * @SOUP_MEMORY_TAKE: The caller has allocated the memory and libsoup
 *   will assume ownership of it and free it with [func@GLib.free].
 * @SOUP_MEMORY_COPY: The passed-in data belongs to the caller and
 *   libsoup will copy it into new memory leaving the caller free
 *   to reuse the original memory.
 *
 * The lifetime of the memory being passed.
 **/

/**
 * SoupMessageBody:
 * @data: (array length=length) (element-type guint8): the data
 * @length: length of @data
 *
 * #SoupMessageBody represents the request or response body of a
 * [class@Message].
 *
 * Note that while @length always reflects the full length of the
 * message body, @data is normally %NULL, and will only be filled in
 * after [method@MessageBody.flatten] is called. For client-side
 * messages, this automatically happens for the response body after it
 * has been fully read. Likewise, for server-side
 * messages, the request body is automatically filled in after being
 * read.
 *
 * As an added bonus, when @data is filled in, it is always terminated
 * with a `\0` byte (which is not reflected in @length).
 **/

typedef struct {
	SoupMessageBody body;
	GSList *chunks, *last;
	GBytes *flattened;
	gboolean accumulate;
	goffset base_offset;
} SoupMessageBodyPrivate;

/**
 * soup_message_body_new:
 *
 * Creates a new #SoupMessageBody.
 *
 * [class@Message] uses this internally; you
 * will not normally need to call it yourself.
 *
 * Returns: a new #SoupMessageBody.
 **/
SoupMessageBody *
soup_message_body_new (void)
{
	SoupMessageBodyPrivate *priv;

	priv = g_atomic_rc_box_new0 (SoupMessageBodyPrivate);
	priv->accumulate = TRUE;

	return (SoupMessageBody *)priv;
}

/**
 * soup_message_body_set_accumulate:
 * @body: a #SoupMessageBody
 * @accumulate: whether or not to accumulate body chunks in @body
 *
 * Sets or clears the accumulate flag on @body.
 *
 * (The default value is %TRUE.) If set to %FALSE, @body's data field will not
 * be filled in after the body is fully sent/received, and the chunks that make
 * up @body may be discarded when they are no longer needed.
 *
 * If you set the flag to %FALSE on the [class@Message] request_body of a
 * client-side message, it will block the accumulation of chunks into
 * @body's data field, but it will not normally cause the chunks to
 * be discarded after being written like in the server-side
 * [class@Message] response_body case, because the request body needs to
 * be kept around in case the request needs to be sent a second time
 * due to redirection or authentication.
 **/
void
soup_message_body_set_accumulate (SoupMessageBody *body,
				  gboolean         accumulate)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;

	priv->accumulate = accumulate;
}

/**
 * soup_message_body_get_accumulate:
 * @body: a #SoupMessageBody
 *
 * Gets the accumulate flag on @body.
 *
 * See [method@MessageBody.set_accumulate. for details.
 *
 * Returns: the accumulate flag for @body.
 **/
gboolean
soup_message_body_get_accumulate (SoupMessageBody *body)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;

	return priv->accumulate;
}

static void
append_buffer (SoupMessageBody *body, GBytes *buffer)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;

	if (priv->last) {
		priv->last = g_slist_append (priv->last, buffer);
		priv->last = priv->last->next;
	} else
		priv->chunks = priv->last = g_slist_append (NULL, buffer);

        g_clear_pointer (&priv->flattened, g_bytes_unref);
        body->data = NULL;
	body->length += g_bytes_get_size (buffer);
}

/**
 * soup_message_body_append:
 * @body: a #SoupMessageBody
 * @use: how to use @data
 * @data: (array length=length) (element-type guint8): data to append
 * @length: length of @data
 *
 * Appends @length bytes from @data to @body according to @use.
 **/
void
soup_message_body_append (SoupMessageBody *body, SoupMemoryUse use,
			  gconstpointer data, gsize length)
{
        GBytes *bytes;
        if (length > 0) {
                if (use == SOUP_MEMORY_TAKE)
                        bytes = g_bytes_new_take ((guchar*)data, length);
                else if (use == SOUP_MEMORY_STATIC)
                        bytes = g_bytes_new_static (data, length);
                else
                        bytes = g_bytes_new (data, length);
                append_buffer (body, g_steal_pointer (&bytes));
        }
	else if (use == SOUP_MEMORY_TAKE)
		g_free ((gpointer)data);
}

/**
 * soup_message_body_append_take: (rename-to soup_message_body_append)
 * @body: a #SoupMessageBody
 * @data: (array length=length) (transfer full): data to append
 * @length: length of @data
 *
 * Appends @length bytes from @data to @body.
 *
 * This function is exactly equivalent to [method@MessageBody.append]
 * with %SOUP_MEMORY_TAKE as second argument; it exists mainly for
 * convenience and simplifying language bindings.
 **/
void
soup_message_body_append_take (SoupMessageBody *body,
			       guchar *data, gsize length)
{
	soup_message_body_append(body, SOUP_MEMORY_TAKE, data, length);
}

/**
 * soup_message_body_append_bytes:
 * @body: a #SoupMessageBody
 * @buffer: a #GBytes
 *
 * Appends the data from @buffer to @body.
 **/
void
soup_message_body_append_bytes (SoupMessageBody *body, GBytes *buffer)
{
	g_return_if_fail (g_bytes_get_size (buffer) > 0);
	append_buffer (body, g_bytes_ref (buffer));
}

/**
 * soup_message_body_truncate:
 * @body: a #SoupMessageBody
 *
 * Deletes all of the data in @body.
 **/
void
soup_message_body_truncate (SoupMessageBody *body)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;

	g_slist_free_full (priv->chunks, (GDestroyNotify)g_bytes_unref);
	priv->chunks = priv->last = NULL;
	priv->base_offset = 0;
        g_clear_pointer (&priv->flattened, g_bytes_unref);
        body->data = NULL;
	body->length = 0;
}

/**
 * soup_message_body_complete:
 * @body: a #SoupMessageBody
 *
 * Tags @body as being complete.
 *
 * Call this when using chunked encoding after you have appended the last chunk.
 **/
void
soup_message_body_complete (SoupMessageBody *body)
{
	append_buffer (body, g_bytes_new_static (NULL, 0));
}

/**
 * soup_message_body_flatten:
 * @body: a #SoupMessageBody
 *
 * Fills in @body's data field with a buffer containing all of the
 * data in @body.
 *
 * Adds an additional `\0` byte not counted by @body's
 * length field.
 *
 * Returns: (transfer full): a #GBytes containing the same data as @body.
 *   (You must [method@GLib.Bytes.unref] this if you do not want it.)
 **/
GBytes *
soup_message_body_flatten (SoupMessageBody *body)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;

	g_return_val_if_fail (priv->accumulate == TRUE, NULL);

	if (!priv->flattened) {
#if GLIB_SIZEOF_SIZE_T < 8
		g_return_val_if_fail (body->length < G_MAXSIZE, NULL);
#endif

                GByteArray *array = g_byte_array_sized_new (body->length + 1);
		for (GSList *iter = priv->chunks; iter; iter = iter->next) {
			GBytes *chunk = iter->data;
                        gsize chunk_size;
                        const guchar *chunk_data = g_bytes_get_data (chunk, &chunk_size);
                        g_byte_array_append (array, chunk_data, chunk_size);
		}
                // NUL terminate the array but don't reflect that in the length
                g_byte_array_append (array, (guchar*)"\0", 1);
                array->len -= 1;

		priv->flattened = g_byte_array_free_to_bytes (array);
                body->data = g_bytes_get_data (priv->flattened, NULL);
	}

	return g_bytes_ref (priv->flattened);
}

/**
 * soup_message_body_get_chunk:
 * @body: a #SoupMessageBody
 * @offset: an offset
 *
 * Gets a [struct@GLib.Bytes] containing data from @body starting at @offset.
 *
 * The size of the returned chunk is unspecified. You can iterate
 * through the entire body by first calling
 * [method@MessageBody.get_chunk] with an offset of 0, and then on each
 * successive call, increment the offset by the length of the
 * previously-returned chunk.
 *
 * If @offset is greater than or equal to the total length of @body,
 * then the return value depends on whether or not
 * [method@MessageBody.complete] has been called or not; if it has,
 * then [method@MessageBody.get_chunk] will return a 0-length chunk
 * (indicating the end of @body). If it has not, then
 * [method@MessageBody.get_chunk] will return %NULL (indicating that
 * @body may still potentially have more data, but that data is not
 * currently available).
 *
 * Returns: (nullable): a #GBytes
 **/
GBytes *
soup_message_body_get_chunk (SoupMessageBody *body, goffset offset)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;
	GSList *iter;
	GBytes *chunk = NULL;

	offset -= priv->base_offset;
	for (iter = priv->chunks; iter; iter = iter->next) {
		chunk = iter->data;
                gsize chunk_length = g_bytes_get_size (chunk);

		if (offset < chunk_length || offset == 0)
			break;

		offset -= chunk_length;
	}

	if (!iter)
		return NULL;

        return g_bytes_new_from_bytes (chunk, offset, g_bytes_get_size (chunk) - offset);
}

/**
 * soup_message_body_got_chunk:
 * @body: a #SoupMessageBody
 * @chunk: a #GBytes received from the network
 *
 * Handles the #SoupMessageBody part of receiving a chunk of data from
 * the network.
 *
 * Normally this means appending @chunk to @body, exactly as with
 * [method@MessageBody.append_bytes], but if you have set @body's accumulate
 * flag to %FALSE, then that will not happen.
 *
 * This is a low-level method which you should not normally need to
 * use.
 **/
void
soup_message_body_got_chunk (SoupMessageBody *body, GBytes *chunk)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;

	if (!priv->accumulate)
		return;

	soup_message_body_append_bytes (body, chunk);
}

/**
 * soup_message_body_wrote_chunk:
 * @body: a #SoupMessageBody
 * @chunk: a #GBytes returned from [method@MessageBody.get_chunk]
 *
 * Handles the #SoupMessageBody part of writing a chunk of data to the
 * network.
 *
 * Normally this is a no-op, but if you have set @body's accumulate flag to
 * %FALSE, then this will cause @chunk to be discarded to free up memory.
 *
 * This is a low-level method which you should not need to use, and
 * there are further restrictions on its proper use which are not
 * documented here.
 **/
void
soup_message_body_wrote_chunk (SoupMessageBody *body, GBytes *chunk)
{
	SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body;
	GBytes *chunk2;

	if (priv->accumulate)
		return;

	chunk2 = priv->chunks->data;
	g_return_if_fail (g_bytes_get_size (chunk) == g_bytes_get_size (chunk2));
	g_return_if_fail (chunk == chunk2);

	priv->chunks = g_slist_remove (priv->chunks, chunk2);
	if (!priv->chunks)
		priv->last = NULL;

	priv->base_offset += g_bytes_get_size (chunk2);
	g_bytes_unref (chunk2);
}

/**
 * soup_message_body_ref:
 * @body: a #SoupMessageBody
 *
 * Atomically increments the reference count of @body by one.
 *
 * Returns: the passed in #SoupMessageBody
 */
SoupMessageBody *
soup_message_body_ref (SoupMessageBody *body)
{
        g_atomic_rc_box_acquire (body);

	return body;
}

/**
 * soup_message_body_unref:
 * @body: a #SoupMessageBody
 *
 * Atomically decrements the reference count of @body by one.
 *
 * When the reference count reaches zero, the resources allocated by
 * @body are freed
 */
void
soup_message_body_unref (SoupMessageBody *body)
{
        g_atomic_rc_box_release_full (body, (GDestroyNotify)soup_message_body_truncate);
}

G_DEFINE_BOXED_TYPE (SoupMessageBody, soup_message_body, soup_message_body_ref, soup_message_body_unref)
