static gssize
soup_content_sniffer_stream_skip (GInputStream  *stream,
				  gsize          count,
				  GCancellable  *cancellable,
				  GError       **error)
{
	SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream);
	gssize nskipped;

	if (sniffer->priv->sniffing) {
		/* Read into the internal buffer... */
		nskipped = soup_content_sniffer_stream_read (stream, NULL, 0, cancellable, error);
		if (nskipped == -1)
			return -1;
		/* Now fall through */
	}

	if (sniffer->priv->buffer) {
		nskipped = MIN (count, sniffer->priv->buffer_nread);
		if (nskipped == sniffer->priv->buffer_nread) {
			g_free (sniffer->priv->buffer);
			sniffer->priv->buffer = NULL;
		} else {
			/* FIXME */
			memmove (sniffer->priv->buffer,
				 sniffer->priv->buffer + nskipped,
				 sniffer->priv->buffer_nread - nskipped);
			sniffer->priv->buffer_nread -= nskipped;
		}
	} else {
		nskipped = G_INPUT_STREAM_CLASS (soup_content_sniffer_stream_parent_class)->
			skip (stream, count, cancellable, error);
	}
	return nskipped;
}
static gboolean
soup_content_sniffer_stream_is_readable (GPollableInputStream *stream)
{
	SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream);

	if (sniffer->priv->error ||
	    (!sniffer->priv->sniffing && sniffer->priv->buffer))
		return TRUE;

	return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream));
}
static void
soup_content_sniffer_stream_finalize (GObject *object)
{
	SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (object);

	g_clear_object (&sniffer->priv->sniffer);
	g_clear_object (&sniffer->priv->msg);
	g_free (sniffer->priv->buffer);
	g_clear_error (&sniffer->priv->error);
	g_free (sniffer->priv->sniffed_type);
	g_clear_pointer (&sniffer->priv->sniffed_params, g_hash_table_unref);

	G_OBJECT_CLASS (soup_content_sniffer_stream_parent_class)->finalize (object);
}
static void
soup_content_sniffer_stream_get_property (GObject *object, guint prop_id,
					  GValue *value, GParamSpec *pspec)
{
	SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (object);

	switch (prop_id) {
	case PROP_SNIFFER:
		g_value_set_object (value, sniffer->priv->sniffer);
		break;
	case PROP_MESSAGE:
		g_value_set_object (value, sniffer->priv->msg);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
static gssize
read_and_sniff (GInputStream *stream, gboolean blocking,
		GCancellable *cancellable, GError **error)
{
	SoupContentSnifferStreamPrivate *priv = SOUP_CONTENT_SNIFFER_STREAM (stream)->priv;
	gssize nread;
	GError *my_error = NULL;
	SoupBuffer *buf;

	do {
		nread = g_pollable_stream_read (G_FILTER_INPUT_STREAM (stream)->base_stream,
						priv->buffer + priv->buffer_nread,
						priv->buffer_size - priv->buffer_nread,
						blocking, cancellable, &my_error);
		if (nread <= 0)
			break;
		priv->buffer_nread += nread;
	} while (priv->buffer_nread < priv->buffer_size);

	/* If we got EAGAIN or cancellation before filling the buffer,
	 * just return that right away. Likewise if we got any other
	 * error without ever reading any data. Otherwise, save the
	 * error to return after we're done sniffing.
	 */
	if (my_error) {
		if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) ||
		    g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_CANCELLED) ||
		    priv->buffer_nread == 0) {
			g_propagate_error (error, my_error);
			return -1;
		} else
			priv->error = my_error;
	}

	/* Sniff, then return the data */
	buf = soup_buffer_new (SOUP_MEMORY_TEMPORARY, priv->buffer, priv->buffer_nread);
	priv->sniffed_type =
		soup_content_sniffer_sniff (priv->sniffer, priv->msg, buf,
					    &priv->sniffed_params);
	soup_buffer_free (buf);
	priv->sniffing = FALSE;

	return priv->buffer_nread;
}	
static gssize
read_internal (GInputStream  *stream,
	       void          *buffer,
	       gsize          count,
	       gboolean       blocking,
	       GCancellable  *cancellable,
	       GError       **error)
{
	SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream);
	gssize nread;

	if (sniffer->priv->error) {
		g_propagate_error (error, sniffer->priv->error);
		sniffer->priv->error = NULL;
		return -1;
	}

	if (sniffer->priv->sniffing) {
		nread = read_and_sniff (stream, blocking, cancellable, error);
		if (nread <= 0)
			return nread;
	}

	if (sniffer->priv->buffer) {
		nread = MIN (count, sniffer->priv->buffer_nread);
		memcpy (buffer, sniffer->priv->buffer, nread);
		if (nread == sniffer->priv->buffer_nread) {
			g_free (sniffer->priv->buffer);
			sniffer->priv->buffer = NULL;
		} else {
			/* FIXME, inefficient */
			memmove (sniffer->priv->buffer,
				 sniffer->priv->buffer + nread,
				 sniffer->priv->buffer_nread - nread);
			sniffer->priv->buffer_nread -= nread;
		}
	} else {
		nread = g_pollable_stream_read (G_FILTER_INPUT_STREAM (stream)->base_stream,
						buffer, count, blocking,
						cancellable, error);
	}
	return nread;
}
static GSource *
soup_content_sniffer_stream_create_source (GPollableInputStream *stream,
					   GCancellable         *cancellable)
{
	SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream);
	GSource *base_source, *pollable_source;

	if (sniffer->priv->error ||
	    (!sniffer->priv->sniffing && sniffer->priv->buffer))
		base_source = g_timeout_source_new (0);
	else
		base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream), cancellable);

	g_source_set_dummy_callback (base_source);
	pollable_source = g_pollable_source_new (G_OBJECT (stream));
	g_source_add_child_source (pollable_source, base_source);
	g_source_unref (base_source);

	return pollable_source;
}
static void
soup_content_sniffer_stream_set_property (GObject *object, guint prop_id,
					  const GValue *value, GParamSpec *pspec)
{
	SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (object);

	switch (prop_id) {
	case PROP_SNIFFER:
		sniffer->priv->sniffer = g_value_dup_object (value);
		/* FIXME: supposed to wait until after got-headers for this */
		sniffer->priv->buffer_size = soup_content_sniffer_get_buffer_size (sniffer->priv->sniffer);
		sniffer->priv->buffer = g_malloc (sniffer->priv->buffer_size);
		break;
	case PROP_MESSAGE:
		sniffer->priv->msg = g_value_dup_object (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}
示例#9
0
/* Attempts to push forward the reading side of @msg's I/O. Returns
 * %TRUE if it manages to make some progress, and it is likely that
 * further progress can be made. Returns %FALSE if it has reached a
 * stopping point of some sort (need input from the application,
 * socket not readable, read is complete, etc).
 */
static gboolean
io_read (SoupMessage *msg, gboolean blocking,
	 GCancellable *cancellable, GError **error)
{
	SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
	SoupMessageIOData *io = priv->io_data;
	guchar *stack_buf = NULL;
	gssize nread;
	SoupBuffer *buffer;
	guint status;

	switch (io->read_state) {
	case SOUP_MESSAGE_IO_STATE_HEADERS:
		if (!read_headers (msg, blocking, cancellable, error))
			return FALSE;

		status = io->parse_headers_cb (msg, (char *)io->read_header_buf->data,
					       io->read_header_buf->len,
					       &io->read_encoding,
					       io->header_data, error);
		g_byte_array_set_size (io->read_header_buf, 0);

		if (status != SOUP_STATUS_OK) {
			/* Either we couldn't parse the headers, or they
			 * indicated something that would mean we wouldn't
			 * be able to parse the body. (Eg, unknown
			 * Transfer-Encoding.). Skip the rest of the
			 * reading, and make sure the connection gets
			 * closed when we're done.
			 */
			soup_message_set_status (msg, status);
			soup_message_headers_append (msg->request_headers,
						     "Connection", "close");
			io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING;
			break;
		}

		if (io->mode == SOUP_MESSAGE_IO_CLIENT &&
		    SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) {
			if (msg->status_code == SOUP_STATUS_CONTINUE &&
			    io->write_state == SOUP_MESSAGE_IO_STATE_BLOCKING) {
				/* Pause the reader, unpause the writer */
				io->read_state =
					SOUP_MESSAGE_IO_STATE_BLOCKING;
				io->write_state =
					SOUP_MESSAGE_IO_STATE_BODY_START;
			} else {
				/* Just stay in HEADERS */
				io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS;
			}

			/* Informational responses have no bodies, so
			 * bail out here rather than parsing encoding, etc
			 */
			soup_message_got_informational (msg);

			/* If this was "101 Switching Protocols", then
			 * the session may have stolen the connection...
			 */
			if (io != priv->io_data)
				return FALSE;

			soup_message_cleanup_response (msg);
			break;
		} else if (io->mode == SOUP_MESSAGE_IO_SERVER &&
			   soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) {
			/* We must return a status code and response
			 * headers to the client; either an error to
			 * be set by a got-headers handler below, or
			 * else %SOUP_STATUS_CONTINUE otherwise.
			 */
			io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS;
			io->read_state = SOUP_MESSAGE_IO_STATE_BLOCKING;
		} else {
			io->read_state = SOUP_MESSAGE_IO_STATE_BODY_START;

			/* If the client was waiting for a Continue
			 * but got something else, then it's done
			 * writing.
			 */
			if (io->mode == SOUP_MESSAGE_IO_CLIENT &&
			    io->write_state == SOUP_MESSAGE_IO_STATE_BLOCKING)
				io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING;
		}

		if (io->read_encoding == SOUP_ENCODING_CONTENT_LENGTH) {
			SoupMessageHeaders *hdrs =
				(io->mode == SOUP_MESSAGE_IO_CLIENT) ?
				msg->response_headers : msg->request_headers;
			io->read_length = soup_message_headers_get_content_length (hdrs);

			if (io->mode == SOUP_MESSAGE_IO_CLIENT &&
			    !soup_message_is_keepalive (msg)) {
				/* Some servers suck and send
				 * incorrect Content-Length values, so
				 * allow EOF termination in this case
				 * (iff the message is too short) too.
				 */
				io->read_encoding = SOUP_ENCODING_EOF;
			}
		} else
			io->read_length = -1;

		soup_message_got_headers (msg);
		break;


	case SOUP_MESSAGE_IO_STATE_BODY_START:
		if (!io->body_istream) {
			GInputStream *body_istream = soup_body_input_stream_new (G_INPUT_STREAM (io->istream),
										 io->read_encoding,
										 io->read_length);

			/* TODO: server-side messages do not have a io->item. This means
			 * that we cannot use content processors for them right now.
			 */
			if (io->mode == SOUP_MESSAGE_IO_CLIENT) {
				io->body_istream = soup_message_setup_body_istream (body_istream, msg,
										    io->item->session,
										    SOUP_STAGE_MESSAGE_BODY);
				g_object_unref (body_istream);
			} else {
				io->body_istream = body_istream;
			}
		}

		if (priv->sniffer) {
			SoupContentSnifferStream *sniffer_stream = SOUP_CONTENT_SNIFFER_STREAM (io->body_istream);
			const char *content_type;
			GHashTable *params;

			if (!soup_content_sniffer_stream_is_ready (sniffer_stream, blocking,
								   cancellable, error))
				return FALSE;

			content_type = soup_content_sniffer_stream_sniff (sniffer_stream, &params);
			soup_message_content_sniffed (msg, content_type, params);
		}

		io->read_state = SOUP_MESSAGE_IO_STATE_BODY;
		break;


	case SOUP_MESSAGE_IO_STATE_BODY:
		if (priv->chunk_allocator) {
			buffer = priv->chunk_allocator (msg, io->read_length, priv->chunk_allocator_data);
			if (!buffer) {
				g_return_val_if_fail (!io->item || !io->item->new_api, FALSE);
				soup_message_io_pause (msg);
				return FALSE;
			}
		} else {
			if (!stack_buf)
				stack_buf = alloca (RESPONSE_BLOCK_SIZE);
			buffer = soup_buffer_new (SOUP_MEMORY_TEMPORARY,
						  stack_buf,
						  RESPONSE_BLOCK_SIZE);
		}

		nread = g_pollable_stream_read (io->body_istream,
						(guchar *)buffer->data,
						buffer->length,
						blocking,
						cancellable, error);
		if (nread > 0) {
			buffer->length = nread;
			soup_message_body_got_chunk (io->read_body, buffer);
			soup_message_got_chunk (msg, buffer);
			soup_buffer_free (buffer);
			break;
		}

		soup_buffer_free (buffer);
		if (nread == -1)
			return FALSE;

		/* else nread == 0 */
		io->read_state = SOUP_MESSAGE_IO_STATE_BODY_DONE;
		break;


	case SOUP_MESSAGE_IO_STATE_BODY_DONE:
		io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING;
		soup_message_got_body (msg);
		break;


	case SOUP_MESSAGE_IO_STATE_FINISHING:
		io->read_state = SOUP_MESSAGE_IO_STATE_DONE;

		if (io->mode == SOUP_MESSAGE_IO_SERVER)
			io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS;
		break;


	default:
		g_return_val_if_reached (FALSE);
	}

	return TRUE;
}