Пример #1
0
static
void test_write_read_v1_short(void)
{
	test_begin("test_write_read_v1_short");
	unsigned char payload[1];
	const unsigned char *ptr;
	size_t pos = 0, siz;
	random_fill(payload, 1);

	buffer_t *buf = buffer_create_dynamic(default_pool, 64);
	struct ostream *os = o_stream_create_buffer(buf);
	struct ostream *os_2 = o_stream_create_encrypt(os, "<unused>", test_v2_kp.pub, IO_STREAM_ENC_VERSION_1);
	o_stream_nsend(os_2, payload, sizeof(payload));

	if (os_2->stream_errno != 0)
		i_debug("error: %s", o_stream_get_error(os_2));

	test_assert(os_2->stream_errno == 0);
	test_assert(o_stream_finish(os_2) > 0);
	test_assert(os_2->stream_errno == 0);

	o_stream_unref(&os);
	o_stream_unref(&os_2);

	struct istream *is = test_istream_create_data(buf->data, buf->used);
	struct istream *is_2 = i_stream_create_decrypt(is, test_v2_kp.priv);

	size_t offset = 0;
	test_istream_set_allow_eof(is, FALSE);
	test_istream_set_size(is, 0);
	while(i_stream_read_data(is_2, &ptr, &siz, 0)>=0) {
		if (offset == buf->used)
			test_istream_set_allow_eof(is, TRUE);
		else
			test_istream_set_size(is, ++offset);

		test_assert_idx(pos + siz <= sizeof(payload), pos);
		if (siz > sizeof(payload) || pos + siz > sizeof(payload)) break;
		test_assert_idx(siz == 0 || memcmp(ptr, payload + pos, siz) == 0, pos);
		i_stream_skip(is_2, siz); pos += siz;
	}

	test_assert(is_2->stream_errno == 0);

	i_stream_unref(&is);
	i_stream_unref(&is_2);
	buffer_free(&buf);

	test_end();
}
Пример #2
0
static int fs_crypt_write_stream_finish(struct fs_file *_file, bool success)
{
	struct crypt_fs_file *file = (struct crypt_fs_file *)_file;
	struct istream *input;
	int ret;

	if (_file->output != NULL) {
		if (_file->output == file->super_output)
			_file->output = NULL;
		else
			o_stream_unref(&_file->output);
	}
	if (!success) {
		if (file->super_output != NULL) {
			/* no encryption */
			i_assert(file->temp_output == NULL);
			fs_write_stream_abort_error(_file->parent, &file->super_output,
						    "write(%s) failed: %s",
						    o_stream_get_name(file->super_output),
						    o_stream_get_error(file->super_output));
		} else if (file->temp_output != NULL) {
			o_stream_destroy(&file->temp_output);
		}
		return -1;
	}

	if (file->super_output != NULL) {
		/* no encrypt */
		i_assert(file->temp_output == NULL);
		return fs_write_stream_finish(_file->parent, &file->super_output);
	}
	if (file->temp_output == NULL) {
		/* finishing up */
		i_assert(file->super_output == NULL);
		return fs_write_stream_finish_async(_file->parent);
	}

	/* finish writing the temporary file */
	input = iostream_temp_finish(&file->temp_output, IO_BLOCK_SIZE);
	file->super_output = fs_write_stream(_file->parent);
	o_stream_nsend_istream(file->super_output, input);
	ret = fs_write_stream_finish(_file->parent, &file->super_output);
	i_stream_unref(&input);
	return ret;
}
Пример #3
0
static
void test_write_read_v1_empty(void)
{
	const unsigned char *ptr;
	size_t siz;
	test_begin("test_write_read_v1_empty");
	buffer_t *buf = buffer_create_dynamic(default_pool, 64);
	struct ostream *os = o_stream_create_buffer(buf);
	struct ostream *os_2 = o_stream_create_encrypt(os, "<unused>", test_v1_kp.pub, IO_STREAM_ENC_VERSION_1);
	test_assert(o_stream_nfinish(os_2) == 0);
	if (os_2->stream_errno != 0)
		i_debug("error: %s", o_stream_get_error(os_2));

	o_stream_unref(&os);
	o_stream_unref(&os_2);
	/* this should've been enough */

	struct istream *is = test_istream_create_data(buf->data, buf->used);
	struct istream *is_2 = i_stream_create_decrypt(is, test_v1_kp.priv);

	/* read should not fail */
	test_istream_set_allow_eof(is, FALSE);
	test_istream_set_size(is, 0);
	size_t offset = 0;
	ssize_t ret;
	while ((ret = i_stream_read_data(is_2, &ptr, &siz, 0)) >= 0) {
		test_assert(ret == 0);
		if (offset == buf->used)
			test_istream_set_allow_eof(is, TRUE);
		else
			test_istream_set_size(is, ++offset);
	};

	test_assert(is_2->stream_errno == 0);
	if (is_2->stream_errno != 0)
		i_debug("error: %s", i_stream_get_error(is_2));
	i_stream_unref(&is);
	i_stream_unref(&is_2);
	buffer_free(&buf);
	test_end();
}
Пример #4
0
const char *io_stream_get_disconnect_reason(struct istream *input,
					    struct ostream *output)
{
	const char *errstr;

	if (input != NULL && input->stream_errno != 0) {
		errno = input->stream_errno;
		errstr = i_stream_get_error(input);
	} else if (output != NULL && output->stream_errno != 0) {
		errno = output->stream_errno;
		errstr = o_stream_get_error(output);
	} else {
		errno = 0;
		errstr = "";
	}

	if (errno == 0 || errno == EPIPE)
		return "Connection closed";
	else
		return t_strdup_printf("Connection closed: %s", errstr);
}
Пример #5
0
void sieve_tool_dump_binary_to
(struct sieve_binary *sbin, const char *filename, bool hexdump)
{
    struct ostream *dumpstream;

    if ( filename == NULL ) return;

    dumpstream = sieve_tool_open_output_stream(filename);
    if ( dumpstream != NULL ) {
        if ( hexdump )
            (void) sieve_hexdump(sbin, dumpstream);
        else
            (void) sieve_dump(sbin, dumpstream, FALSE);
        if (o_stream_nfinish(dumpstream) < 0) {
            i_fatal("write(%s) failed: %s", filename,
                    o_stream_get_error(dumpstream));
        }
        o_stream_destroy(&dumpstream);
    } else {
        i_fatal("Failed to create stream for sieve code dump.");
    }
}
static int
astream_part_finish(struct attachment_istream *astream, const char **error_r)
{
	struct attachment_istream_part *part = &astream->part;
	struct istream_attachment_info info;
	struct istream *input;
	struct ostream *output;
	string_t *digest_str;
	const unsigned char *data;
	size_t size;
	int ret = 0;

	if (o_stream_nfinish(part->temp_output) < 0) {
		*error_r = t_strdup_printf("write(%s) failed: %s",
					   o_stream_get_name(part->temp_output),
					   o_stream_get_error(part->temp_output));
		return -1;
	}

	memset(&info, 0, sizeof(info));
	info.start_offset = astream->part.start_offset;
	/* base64_bytes contains how many valid base64 bytes there are so far.
	   if the base64 ends properly, it'll specify how much of the MIME part
	   is saved as an attachment. the rest of the data (typically
	   linefeeds) is added back to main stream */
	info.encoded_size = part->base64_bytes;
	/* get the hash before base64-decoder resets it */
	digest_str = t_str_new(128);
	hash_format_write(astream->set.hash_format, digest_str);
	info.hash = str_c(digest_str);

	/* if it looks like we can decode base64 without any data loss,
	   do it and write the decoded data to another temp file. */
	if (!part->base64_failed) {
		if (part->base64_state == BASE64_STATE_0 &&
		    part->base64_bytes > 0) {
			/* there is no trailing LF or '=' characters,
			   but it's not completely empty */
			part->base64_state = BASE64_STATE_EOM;
		}
		if (part->base64_state == BASE64_STATE_EOM) {
			/* base64 data looks ok. */
			if (astream_decode_base64(astream) < 0)
				part->base64_failed = TRUE;
		} else {
			part->base64_failed = TRUE;
		}
	}

	/* open attachment output file */
	info.part = astream->cur_part;
	if (!part->base64_failed) {
		info.base64_blocks_per_line = part->base64_line_blocks;
		info.base64_have_crlf = part->base64_have_crlf;
		/* base64-decoder updated the hash, use it */
		str_truncate(digest_str, 0);
		hash_format_write(astream->set.hash_format, digest_str);
		info.hash = str_c(digest_str);
	} else {
		/* couldn't decode base64, so write the entire MIME part
		   as attachment */
		info.encoded_size = part->temp_output->offset;
	}
	if (astream->set.open_attachment_ostream(&info, &output, error_r,
						 astream->context) < 0)
		return -1;

	/* copy data to attachment from temp file */
	input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE);
	while (i_stream_read_more(input, &data, &size) > 0) {
		o_stream_nsend(output, data, size);
		i_stream_skip(input, size);
	}

	if (input->stream_errno != 0) {
		*error_r = t_strdup_printf("read(%s) failed: %s",
			i_stream_get_name(input), i_stream_get_error(input));
		ret = -1;
	}
	i_stream_destroy(&input);

	if (astream->set.close_attachment_ostream(output, ret == 0, error_r,
						  astream->context) < 0)
		ret = -1;
	return ret;
}
static int astream_decode_base64(struct attachment_istream *astream)
{
	struct attachment_istream_part *part = &astream->part;
	buffer_t *extra_buf = NULL;
	struct istream *input, *base64_input;
	struct ostream *output;
	const unsigned char *data;
	size_t size;
	ssize_t ret;
	buffer_t *buf;
	int outfd;
	bool failed = FALSE;

	if (part->base64_bytes < astream->set.min_size ||
	    part->temp_output->offset > part->base64_bytes +
	    				BASE64_ATTACHMENT_MAX_EXTRA_BYTES) {
		/* only a small part of the MIME part is base64-encoded. */
		return -1;
	}

	if (part->base64_line_blocks == 0) {
		/* only one line of base64 */
		part->base64_line_blocks = part->cur_base64_blocks;
		i_assert(part->base64_line_blocks > 0);
	}

	/* decode base64 data and write it to another temp file */
	outfd = astream->set.open_temp_fd(astream->context);
	if (outfd == -1)
		return -1;

	buf = buffer_create_dynamic(default_pool, 1024);
	input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE);
	base64_input = i_stream_create_limit(input, part->base64_bytes);
	output = o_stream_create_fd_file(outfd, 0, FALSE);
	o_stream_cork(output);

	hash_format_reset(astream->set.hash_format);
	size_t bytes_needed = 1;
	while ((ret = i_stream_read_bytes(base64_input, &data, &size,
					  bytes_needed)) > 0) {
		buffer_set_used_size(buf, 0);
		if (base64_decode(data, size, &size, buf) < 0) {
			i_error("istream-attachment: BUG: "
				"Attachment base64 data unexpectedly broke");
			failed = TRUE;
			break;
		}
		i_stream_skip(base64_input, size);
		o_stream_nsend(output, buf->data, buf->used);
		hash_format_loop(astream->set.hash_format,
				 buf->data, buf->used);
		bytes_needed = i_stream_get_data_size(base64_input) + 1;
	}
	if (ret != -1) {
		i_assert(failed);
	} else if (base64_input->stream_errno != 0) {
		i_error("istream-attachment: read(%s) failed: %s",
			i_stream_get_name(base64_input),
			i_stream_get_error(base64_input));
		failed = TRUE;
	}
	if (o_stream_nfinish(output) < 0) {
		i_error("istream-attachment: write(%s) failed: %s",
			o_stream_get_name(output), o_stream_get_error(output));
		failed = TRUE;
	}

	buffer_free(&buf);
	i_stream_unref(&base64_input);
	o_stream_unref(&output);

	if (input->v_offset != part->temp_output->offset && !failed) {
		/* write the rest of the data to the message stream */
		extra_buf = buffer_create_dynamic(default_pool, 1024);
		while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
			buffer_append(extra_buf, data, size);
			i_stream_skip(input, size);
		}
		i_assert(ret == -1);
		if (input->stream_errno != 0) {
			i_error("istream-attachment: read(%s) failed: %s",
				i_stream_get_name(input),
				i_stream_get_error(input));
			failed = TRUE;
		}
	}
	i_stream_unref(&input);

	if (failed) {
		i_close_fd(&outfd);
		return -1;
	}

	/* successfully wrote it. switch to using it. */
	o_stream_destroy(&part->temp_output);
	i_close_fd(&part->temp_fd);
	part->temp_fd = outfd;

	if (extra_buf != NULL) {
		stream_add_data(astream, extra_buf->data, extra_buf->used);
		buffer_free(&extra_buf);
	}
	return 0;
}
Пример #8
0
static int http_client_request_send_real(struct http_client_request *req,
					 bool pipelined, const char **error_r)
{
	const struct http_client_settings *set = &req->client->set;
	struct http_client_connection *conn = req->conn;
	struct ostream *output = conn->conn.output;
	string_t *rtext = t_str_new(256);
	struct const_iovec iov[3];
	int ret = 0;

	i_assert(!req->conn->output_locked);
	i_assert(req->payload_output == NULL);

	/* create request line */
	str_append(rtext, req->method);
	str_append(rtext, " ");
	str_append(rtext, req->target);
	str_append(rtext, " HTTP/1.1\r\n");

	/* create special headers implicitly if not set explicitly using
	   http_client_request_add_header() */
	if (!req->have_hdr_host) {
		str_append(rtext, "Host: ");
		str_append(rtext, req->authority);
		str_append(rtext, "\r\n");
	}
	if (!req->have_hdr_date) {
		str_append(rtext, "Date: ");
		str_append(rtext, http_date_create(req->date));
		str_append(rtext, "\r\n");
	}
	if (!req->have_hdr_authorization &&
		req->username != NULL && req->password != NULL) {
		struct http_auth_credentials auth_creds;

		http_auth_basic_credentials_init(&auth_creds,
			req->username, req->password);

		str_append(rtext, "Authorization: ");
		http_auth_create_credentials(rtext, &auth_creds);
		str_append(rtext, "\r\n");
	}
	if (http_client_request_to_proxy(req) &&
		set->proxy_username != NULL && set->proxy_password != NULL) {
		struct http_auth_credentials auth_creds;

		http_auth_basic_credentials_init(&auth_creds,
			set->proxy_username, set->proxy_password);

		str_append(rtext, "Proxy-Authorization: ");
		http_auth_create_credentials(rtext, &auth_creds);
		str_append(rtext, "\r\n");
	}
	if (!req->have_hdr_user_agent && req->client->set.user_agent != NULL) {
		str_printfa(rtext, "User-Agent: %s\r\n",
			    req->client->set.user_agent);
	}
	if (!req->have_hdr_expect && req->payload_sync) {
		str_append(rtext, "Expect: 100-continue\r\n");
	}
	if (req->payload_input != NULL) {
		if (req->payload_chunked) {
			// FIXME: can't do this for a HTTP/1.0 server
			if (!req->have_hdr_body_spec)
				str_append(rtext, "Transfer-Encoding: chunked\r\n");
			req->payload_output =
				http_transfer_chunked_ostream_create(output);
		} else {
			/* send Content-Length if we have specified a payload,
				 even if it's 0 bytes. */
			if (!req->have_hdr_body_spec) {
				str_printfa(rtext, "Content-Length: %"PRIuUOFF_T"\r\n",
					req->payload_size);
			}
			req->payload_output = output;
			o_stream_ref(output);
		}
	}
	if (!req->have_hdr_connection &&
		!http_client_request_to_proxy(req)) {
		/* https://tools.ietf.org/html/rfc2068
		     Section 19.7.1:

		   A client MUST NOT send the Keep-Alive connection token to a proxy
		   server as HTTP/1.0 proxy servers do not obey the rules of HTTP/1.1
		   for parsing the Connection header field.
		 */
		str_append(rtext, "Connection: Keep-Alive\r\n");
	}

	/* request line + implicit headers */
	iov[0].iov_base = str_data(rtext);
	iov[0].iov_len = str_len(rtext);	
	/* explicit headers */
	if (req->headers != NULL) {
		iov[1].iov_base = str_data(req->headers);
		iov[1].iov_len = str_len(req->headers);
	} else {
		iov[1].iov_base = "";
		iov[1].iov_len = 0;
	}
	/* end of header */
	iov[2].iov_base = "\r\n";
	iov[2].iov_len = 2;

	req->state = HTTP_REQUEST_STATE_PAYLOAD_OUT;
	req->sent_time = ioloop_timeval;
	o_stream_cork(output);
	if (o_stream_sendv(output, iov, N_ELEMENTS(iov)) < 0) {
		*error_r = t_strdup_printf("write(%s) failed: %s",
					   o_stream_get_name(output),
					   o_stream_get_error(output));
		ret = -1;
	} else {
		http_client_request_debug(req, "Sent header");

		if (req->payload_output != NULL) {
			if (!req->payload_sync) {
				if (http_client_request_send_more
					(req, pipelined, error_r) < 0)
					ret = -1;
			} else {
				http_client_request_debug(req, "Waiting for 100-continue");
				conn->output_locked = TRUE;
			}
		} else {
			req->state = HTTP_REQUEST_STATE_WAITING;
			if (!pipelined)
				http_client_connection_start_request_timeout(req->conn);
			conn->output_locked = FALSE;
		}
		if (ret >= 0 && o_stream_flush(output) < 0) {
			*error_r = t_strdup_printf("flush(%s) failed: %s",
   	                   o_stream_get_name(output),
           	           o_stream_get_error(output));
			ret = -1;
		}
	}
	o_stream_uncork(output);
	return ret;
}
Пример #9
0
int http_client_request_send_more(struct http_client_request *req,
				  bool pipelined, const char **error_r)
{
	struct http_client_connection *conn = req->conn;
	struct ostream *output = req->payload_output;
	enum ostream_send_istream_result res;

	i_assert(req->payload_input != NULL);
	i_assert(req->payload_output != NULL);

	if (conn->io_req_payload != NULL)
		io_remove(&conn->io_req_payload);

	/* chunked ostream needs to write to the parent stream's buffer */
	o_stream_set_max_buffer_size(output, IO_BLOCK_SIZE);
	res = o_stream_send_istream(output, req->payload_input);
	o_stream_set_max_buffer_size(output, (size_t)-1);

	switch (res) {
	case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
		/* finished sending */
		if (!req->payload_chunked &&
		    req->payload_input->v_offset - req->payload_offset != req->payload_size) {
			*error_r = t_strdup_printf("BUG: stream '%s' input size changed: "
				"%"PRIuUOFF_T"-%"PRIuUOFF_T" != %"PRIuUOFF_T,
				i_stream_get_name(req->payload_input),
				req->payload_input->v_offset, req->payload_offset, req->payload_size);
			i_error("%s", *error_r); //FIXME: remove?
			return -1;
		}

		if (req->payload_wait) {
			/* this chunk of input is finished
			   (client needs to act; disable timeout) */
			i_assert(!pipelined);
			conn->output_locked = TRUE;
			http_client_connection_stop_request_timeout(conn);
			if (req->client->ioloop != NULL)
				io_loop_stop(req->client->ioloop);
		} else {
			/* finished sending payload */
			http_client_request_finish_payload_out(req);
		}
		return 0;
	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
		/* input is blocking (client needs to act; disable timeout) */
		conn->output_locked = TRUE;	
		if (!pipelined)
			http_client_connection_stop_request_timeout(conn);
		conn->io_req_payload = io_add_istream(req->payload_input,
			http_client_request_payload_input, req);
		return 0;
	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
		/* output is blocking (server needs to act; enable timeout) */
		conn->output_locked = TRUE;
		if (!pipelined)
			http_client_connection_start_request_timeout(conn);
		http_client_request_debug(req, "Partially sent payload");
		return 0;
	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
		/* we're in the middle of sending a request, so the connection
		   will also have to be aborted */
		*error_r = t_strdup_printf("read(%s) failed: %s",
					   i_stream_get_name(req->payload_input),
					   i_stream_get_error(req->payload_input));

		/* the payload stream assigned to this request is broken,
		   fail this the request immediately */
		http_client_request_error(&req,
			HTTP_CLIENT_REQUEST_ERROR_BROKEN_PAYLOAD,
			"Broken payload stream");
		return -1;
	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
		/* failed to send request */
		*error_r = t_strdup_printf("write(%s) failed: %s",
					   o_stream_get_name(output),
					   o_stream_get_error(output));
		return -1;
	}
	i_unreached();
}
Пример #10
0
static int
sieve_file_storage_save_to(struct sieve_file_storage *fstorage,
	string_t *temp_path, struct istream *input,
	const char *target)
{
	struct sieve_storage *storage = &fstorage->storage;
	struct ostream *output;
	int fd;

	// FIXME: move this to base class
	// FIXME: use io_stream_temp

	fd = safe_mkstemp_hostpid
		(temp_path, fstorage->file_create_mode, (uid_t)-1, (gid_t)-1);
	if ( fd < 0 ) {
		if ( errno == EACCES ) {
			sieve_storage_set_critical(storage,
				"Failed to create temporary file: %s",
				eacces_error_get_creating("open", str_c(temp_path)));
		} else {
			sieve_storage_set_critical(storage,
				"Failed to create temporary file: open(%s) failed: %m",
				str_c(temp_path));
		}
		return -1;
	}

	output = o_stream_create_fd(fd, 0);
	switch ( o_stream_send_istream(output, input) ) {
	case OSTREAM_SEND_ISTREAM_RESULT_FINISHED:
		break;
	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT:
	case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT:
		i_unreached();
	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT:
		sieve_storage_set_critical(storage,
			"read(%s) failed: %s", i_stream_get_name(input),
			i_stream_get_error(input));
		o_stream_destroy(&output);
		i_unlink(str_c(temp_path));
		return -1;
	case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT:
		sieve_storage_set_critical(storage,
			"write(%s) failed: %s", str_c(temp_path),
			o_stream_get_error(output));
		o_stream_destroy(&output);
		i_unlink(str_c(temp_path));
		return -1;
	}
	o_stream_destroy(&output);

	if ( rename(str_c(temp_path), target) < 0 ) {
		if ( ENOQUOTA(errno) ) {
			sieve_storage_set_error(storage,
				SIEVE_ERROR_NO_QUOTA,
				"Not enough disk quota");
		} else if ( errno == EACCES ) {
			sieve_storage_set_critical(storage,
				"%s", eacces_error_get("rename", target));
		} else {
			sieve_storage_set_critical(storage,
				"rename(%s, %s) failed: %m",
				str_c(temp_path), target);
		}
		i_unlink(str_c(temp_path));
	}
	return 0;
}
Пример #11
0
int http_client_request_send_more(struct http_client_request *req,
				  const char **error_r)
{
	struct http_client_connection *conn = req->conn;
	struct ostream *output = req->payload_output;
	off_t ret;
	int fd;

	i_assert(req->payload_input != NULL);
	i_assert(req->payload_output != NULL);

	if (conn->io_req_payload != NULL)
		io_remove(&conn->io_req_payload);

	/* chunked ostream needs to write to the parent stream's buffer */
	o_stream_set_max_buffer_size(output, IO_BLOCK_SIZE);
	ret = o_stream_send_istream(output, req->payload_input);
	o_stream_set_max_buffer_size(output, (size_t)-1);

	if (req->payload_input->stream_errno != 0) {
		/* the payload stream assigned to this request is broken,
		   fail this the request immediately */
		http_client_request_send_error(req,
			HTTP_CLIENT_REQUEST_ERROR_BROKEN_PAYLOAD,
			"Broken payload stream");

		/* we're in the middle of sending a request, so the connection
		   will also have to be aborted */
		errno = req->payload_input->stream_errno;
		*error_r = t_strdup_printf("read(%s) failed: %s",
					   i_stream_get_name(req->payload_input),
					   i_stream_get_error(req->payload_input));
		ret = -1;
	} else if (output->stream_errno != 0) {
		/* failed to send request */
		errno = output->stream_errno;
		*error_r = t_strdup_printf("write(%s) failed: %s",
					   o_stream_get_name(output),
					   o_stream_get_error(output));
		ret = -1;
	} else {
		i_assert(ret >= 0);
	}

	if (ret < 0 || i_stream_is_eof(req->payload_input)) {
		if (!req->payload_chunked &&
			req->payload_input->v_offset - req->payload_offset != req->payload_size) {
			*error_r = "stream input size changed [BUG]";
			i_error("stream input size changed"); //FIXME
			return -1;
		}

		if (req->payload_wait) {
			conn->output_locked = TRUE;
			if (req->client->ioloop != NULL)
				io_loop_stop(req->client->ioloop);
		} else {
			http_client_request_finish_payload_out(req);
		}
	} else if (i_stream_get_data_size(req->payload_input) > 0) {
		/* output is blocking */
		conn->output_locked = TRUE;
		o_stream_set_flush_pending(output, TRUE);
		http_client_request_debug(req, "Partially sent payload");
	} else {
		/* input is blocking */
		fd = i_stream_get_fd(req->payload_input);
		conn->output_locked = TRUE;	
		i_assert(fd >= 0);
		conn->io_req_payload = io_add
			(fd, IO_READ, http_client_request_payload_input, req);
	}
	return ret < 0 ? -1 : 0;
}