/* Returns the amount written. */ static int bio_bucket_write(BIO *bio, const char *in, int inl) { serf_ssl_context_t *ctx = bio->ptr; serf_bucket_t *tmp; #ifdef SSL_VERBOSE printf("bio_bucket_write called for %d bytes\n", inl); #endif if (ctx->encrypt.status == SERF_ERROR_WAIT_CONN && !BIO_should_read(ctx->bio)) { #ifdef SSL_VERBOSE printf("bio_bucket_write waiting: (%d %d %d)\n", BIO_should_retry(ctx->bio), BIO_should_read(ctx->bio), BIO_get_retry_flags(ctx->bio)); #endif /* Falling back... */ ctx->encrypt.exhausted_reset = 1; BIO_clear_retry_flags(bio); } tmp = serf_bucket_simple_copy_create(in, inl, ctx->encrypt.pending->allocator); serf_bucket_aggregate_append(ctx->encrypt.pending, tmp); return inl; }
void serf_bucket_aggregate_append_iovec( serf_bucket_t *aggregate_bucket, struct iovec *vecs, int vecs_count) { serf_bucket_t *new_bucket; new_bucket = serf_bucket_iovec_create(vecs, vecs_count, aggregate_bucket->allocator); serf_bucket_aggregate_append(aggregate_bucket, new_bucket); }
serf_bucket_t *serf_bucket_ssl_encrypt_create( serf_bucket_t *stream, serf_ssl_context_t *ssl_ctx, serf_bucket_alloc_t *allocator) { serf_bucket_t *bkt; ssl_context_t *ctx; bkt = serf_bucket_ssl_create(ssl_ctx, allocator, &serf_bucket_type_ssl_encrypt); ctx = bkt->data; ctx->databuf = &ctx->ssl_ctx->encrypt.databuf; ctx->our_stream = &ctx->ssl_ctx->encrypt.stream; if (ctx->ssl_ctx->encrypt.stream == NULL) { serf_bucket_t *tmp = serf_bucket_aggregate_create(stream->allocator); serf_bucket_aggregate_append(tmp, stream); ctx->ssl_ctx->encrypt.stream = tmp; } else { bucket_list_t *new_list; new_list = serf_bucket_mem_alloc(ctx->ssl_ctx->allocator, sizeof(*new_list)); new_list->bucket = stream; new_list->next = NULL; if (ctx->ssl_ctx->encrypt.stream_next == NULL) { ctx->ssl_ctx->encrypt.stream_next = new_list; } else { bucket_list_t *scan = ctx->ssl_ctx->encrypt.stream_next; while (scan->next != NULL) scan = scan->next; scan->next = new_list; } } return bkt; }
/* Implements svn_ra_serf__request_body_delegate_t */ static svn_error_t * create_propfind_body(serf_bucket_t **bkt, void *setup_baton, serf_bucket_alloc_t *alloc, apr_pool_t *pool /* request pool */, apr_pool_t *scratch_pool) { propfind_context_t *ctx = setup_baton; serf_bucket_t *body_bkt, *tmp; const svn_ra_serf__dav_props_t *prop; svn_boolean_t requested_allprop = FALSE; body_bkt = serf_bucket_aggregate_create(alloc); prop = ctx->find_props; while (prop && prop->xmlns) { /* special case the allprop case. */ if (strcmp(prop->name, "allprop") == 0) { requested_allprop = TRUE; } /* <*propname* xmlns="*propns*" /> */ tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<", 1, alloc); serf_bucket_aggregate_append(body_bkt, tmp); tmp = SERF_BUCKET_SIMPLE_STRING(prop->name, alloc); serf_bucket_aggregate_append(body_bkt, tmp); tmp = SERF_BUCKET_SIMPLE_STRING_LEN(" xmlns=\"", sizeof(" xmlns=\"")-1, alloc); serf_bucket_aggregate_append(body_bkt, tmp); tmp = SERF_BUCKET_SIMPLE_STRING(prop->xmlns, alloc); serf_bucket_aggregate_append(body_bkt, tmp); tmp = SERF_BUCKET_SIMPLE_STRING_LEN("\"/>", sizeof("\"/>")-1, alloc); serf_bucket_aggregate_append(body_bkt, tmp); prop++; } /* If we're not doing an allprop, add <prop> tags. */ if (!requested_allprop) { tmp = SERF_BUCKET_SIMPLE_STRING_LEN("<prop>", sizeof("<prop>")-1, alloc); serf_bucket_aggregate_prepend(body_bkt, tmp); } tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_HEADER, sizeof(PROPFIND_HEADER)-1, alloc); serf_bucket_aggregate_prepend(body_bkt, tmp); if (!requested_allprop) { tmp = SERF_BUCKET_SIMPLE_STRING_LEN("</prop>", sizeof("</prop>")-1, alloc); serf_bucket_aggregate_append(body_bkt, tmp); } tmp = SERF_BUCKET_SIMPLE_STRING_LEN(PROPFIND_TRAILER, sizeof(PROPFIND_TRAILER)-1, alloc); serf_bucket_aggregate_append(body_bkt, tmp); *bkt = body_bkt; return SVN_NO_ERROR; }
static apr_status_t serf_deflate_read(serf_bucket_t *bucket, apr_size_t requested, const char **data, apr_size_t *len) { deflate_context_t *ctx = bucket->data; apr_status_t status; const char *private_data; apr_size_t private_len; int zRC; while (1) { switch (ctx->state) { case STATE_READING_HEADER: case STATE_READING_VERIFY: status = serf_bucket_read(ctx->stream, ctx->stream_left, &private_data, &private_len); if (SERF_BUCKET_READ_ERROR(status)) { return status; } memcpy(ctx->hdr_buffer + (ctx->stream_size - ctx->stream_left), private_data, private_len); ctx->stream_left -= private_len; if (ctx->stream_left == 0) { ctx->state++; if (APR_STATUS_IS_EAGAIN(status)) { *len = 0; return status; } } else if (status) { *len = 0; return status; } break; case STATE_HEADER: if (ctx->hdr_buffer[0] != deflate_magic[0] || ctx->hdr_buffer[1] != deflate_magic[1]) { return SERF_ERROR_DECOMPRESSION_FAILED; } if (ctx->hdr_buffer[3] != 0) { return SERF_ERROR_DECOMPRESSION_FAILED; } ctx->state++; break; case STATE_VERIFY: { unsigned long compCRC, compLen, actualLen; /* Do the checksum computation. */ compCRC = getLong((unsigned char*)ctx->hdr_buffer); if (ctx->crc != compCRC) { return SERF_ERROR_DECOMPRESSION_FAILED; } compLen = getLong((unsigned char*)ctx->hdr_buffer + 4); /* The length in the trailer is module 2^32, so do the same for the actual length. */ actualLen = ctx->zstream.total_out; actualLen &= 0xFFFFFFFF; if (actualLen != compLen) { return SERF_ERROR_DECOMPRESSION_FAILED; } ctx->state++; break; } case STATE_INIT: zRC = inflateInit2(&ctx->zstream, ctx->windowSize); if (zRC != Z_OK) { return SERF_ERROR_DECOMPRESSION_FAILED; } ctx->zstream.next_out = ctx->buffer; ctx->zstream.avail_out = ctx->bufferSize; ctx->state++; break; case STATE_FINISH: inflateEnd(&ctx->zstream); serf_bucket_aggregate_prepend(ctx->stream, ctx->inflate_stream); ctx->inflate_stream = 0; ctx->state++; break; case STATE_INFLATE: /* Do we have anything already uncompressed to read? */ status = serf_bucket_read(ctx->inflate_stream, requested, data, len); if (SERF_BUCKET_READ_ERROR(status)) { return status; } /* Hide EOF. */ if (APR_STATUS_IS_EOF(status)) { status = ctx->stream_status; if (APR_STATUS_IS_EOF(status)) { /* We've read all of the data from our stream, but we * need to continue to iterate until we flush * out the zlib buffer. */ status = APR_SUCCESS; } } if (*len != 0) { return status; } /* We tried; but we have nothing buffered. Fetch more. */ /* It is possible that we maxed out avail_out before * exhausting avail_in; therefore, continue using the * previous buffer. Otherwise, fetch more data from * our stream bucket. */ if (ctx->zstream.avail_in == 0) { /* When we empty our inflated stream, we'll return this * status - this allow us to eventually pass up EAGAINs. */ ctx->stream_status = serf_bucket_read(ctx->stream, ctx->bufferSize, &private_data, &private_len); if (SERF_BUCKET_READ_ERROR(ctx->stream_status)) { return ctx->stream_status; } if (!private_len && APR_STATUS_IS_EAGAIN(ctx->stream_status)) { *len = 0; status = ctx->stream_status; ctx->stream_status = APR_SUCCESS; return status; } ctx->zstream.next_in = (unsigned char*)private_data; ctx->zstream.avail_in = private_len; } while (1) { zRC = inflate(&ctx->zstream, Z_NO_FLUSH); /* We're full or zlib requires more space. Either case, clear out our buffer, reset, and return. */ if (zRC == Z_BUF_ERROR || ctx->zstream.avail_out == 0) { serf_bucket_t *tmp; ctx->zstream.next_out = ctx->buffer; private_len = ctx->bufferSize - ctx->zstream.avail_out; ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, private_len); /* FIXME: There probably needs to be a free func. */ tmp = SERF_BUCKET_SIMPLE_STRING_LEN((char *)ctx->buffer, private_len, bucket->allocator); serf_bucket_aggregate_append(ctx->inflate_stream, tmp); ctx->zstream.avail_out = ctx->bufferSize; break; } if (zRC == Z_STREAM_END) { serf_bucket_t *tmp; private_len = ctx->bufferSize - ctx->zstream.avail_out; ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, private_len); /* FIXME: There probably needs to be a free func. */ tmp = SERF_BUCKET_SIMPLE_STRING_LEN((char *)ctx->buffer, private_len, bucket->allocator); serf_bucket_aggregate_append(ctx->inflate_stream, tmp); ctx->zstream.avail_out = ctx->bufferSize; /* Push back the remaining data to be read. */ tmp = serf_bucket_aggregate_create(bucket->allocator); serf_bucket_aggregate_prepend(tmp, ctx->stream); ctx->stream = tmp; /* We now need to take the remaining avail_in and * throw it in ctx->stream so our next read picks it up. */ tmp = SERF_BUCKET_SIMPLE_STRING_LEN( (const char*)ctx->zstream.next_in, ctx->zstream.avail_in, bucket->allocator); serf_bucket_aggregate_prepend(ctx->stream, tmp); switch (ctx->format) { case SERF_DEFLATE_GZIP: ctx->stream_left = ctx->stream_size = DEFLATE_VERIFY_SIZE; ctx->state++; break; case SERF_DEFLATE_DEFLATE: /* Deflate does not have a verify footer. */ ctx->state = STATE_FINISH; break; default: /* Not reachable */ return APR_EGENERAL; } break; } /* Any other error? */ if (zRC != Z_OK) { return SERF_ERROR_DECOMPRESSION_FAILED; } /* As long as zRC == Z_OK, just keep looping. */ } /* Okay, we've inflated. Try to read. */ status = serf_bucket_read(ctx->inflate_stream, requested, data, len); /* Hide EOF. */ if (APR_STATUS_IS_EOF(status)) { status = ctx->stream_status; /* If the inflation wasn't finished, return APR_SUCCESS. */ if (zRC != Z_STREAM_END) return APR_SUCCESS; /* If our stream is finished too and all data was inflated, * return SUCCESS so we'll iterate one more time. */ if (APR_STATUS_IS_EOF(status)) { /* No more data to read from the stream, and everything inflated. If all data was received correctly, state should have been advanced to STATE_READING_VERIFY or STATE_FINISH. If not, then the data was incomplete and we have an error. */ if (ctx->state != STATE_INFLATE) return APR_SUCCESS; else return SERF_ERROR_DECOMPRESSION_FAILED; } } return status; case STATE_DONE: /* We're done inflating. Use our finished buffer. */ return serf_bucket_read(ctx->stream, requested, data, len); default: /* Not reachable */ return APR_EGENERAL; } } /* NOTREACHED */ }
/* Exchange data between proxy and server */ static apr_status_t proxy_replay(serv_ctx_t *servctx, apr_int16_t rtnevents, apr_pool_t *pool) { apr_status_t status; if (rtnevents & APR_POLLIN) { apr_size_t len; char buf[BUFSIZE]; serf_bucket_t *tmp; serf__log(TEST_VERBOSE, __FILE__, "proxy_replay: POLLIN\n"); /* Read all incoming data from the server to forward it to the client later. */ do { len = BUFSIZE; status = apr_socket_recv(servctx->proxy_client_sock, buf, &len); if (SERF_BUCKET_READ_ERROR(status)) return status; serf__log(TEST_VERBOSE, __FILE__, "proxy: reading %d bytes %.*s from server.\n", len, len, buf); tmp = serf_bucket_simple_copy_create(buf, len, servctx->allocator); serf_bucket_aggregate_append(servctx->clientstream, tmp); } while (!status); } if (rtnevents & APR_POLLOUT) { apr_size_t len; char *buf; serf__log(TEST_VERBOSE, __FILE__, "proxy_replay: POLLOUT\n"); /* Send all data received from the client to the server. */ do { apr_size_t readlen; readlen = BUFSIZE; if (!servctx->servstream) servctx->servstream = serf__bucket_stream_create( servctx->allocator, detect_eof,servctx); status = serf_bucket_read(servctx->servstream, BUFSIZE, &buf, &readlen); if (SERF_BUCKET_READ_ERROR(status)) return status; if (!readlen) break; len = readlen; serf__log(TEST_VERBOSE, __FILE__, "proxy: sending %d bytes %.*s to server.\n", len, len, buf); status = apr_socket_send(servctx->proxy_client_sock, buf, &len); if (status != APR_SUCCESS) { return status; } if (len != readlen) /* abort for now */ return APR_EGENERAL; } while (!status); } else if (rtnevents & APR_POLLIN) { /* ignore */ } else { printf("Unknown rtnevents: %d\n", rtnevents); abort(); } return status; }
/* Verify received requests and take the necessary actions (return a response, kill the connection ...) */ static apr_status_t replay(serv_ctx_t *servctx, apr_int16_t rtnevents, apr_pool_t *pool) { apr_status_t status = APR_SUCCESS; test_server_action_t *action; if (rtnevents & APR_POLLIN) { if (servctx->message_list == NULL) { /* we're not expecting any requests to reach this server! */ serf__log(TEST_VERBOSE, __FILE__, "Received request where none was expected.\n"); return SERF_ERROR_ISSUE_IN_TESTSUITE; } if (servctx->cur_action >= servctx->action_count) { char buf[128]; apr_size_t len = sizeof(buf); status = servctx->read(servctx, buf, &len); if (! APR_STATUS_IS_EAGAIN(status)) { /* we're out of actions! */ serf__log(TEST_VERBOSE, __FILE__, "Received more requests than expected.\n"); return SERF_ERROR_ISSUE_IN_TESTSUITE; } return status; } action = &servctx->action_list[servctx->cur_action]; serf__log(TEST_VERBOSE, __FILE__, "POLLIN while replaying action %d, kind: %d.\n", servctx->cur_action, action->kind); /* Read the remaining data from the client and kill the socket. */ if (action->kind == SERVER_IGNORE_AND_KILL_CONNECTION) { char buf[128]; apr_size_t len = sizeof(buf); status = servctx->read(servctx, buf, &len); if (status == APR_EOF) { serf__log(TEST_VERBOSE, __FILE__, "Killing this connection.\n"); apr_socket_close(servctx->client_sock); servctx->client_sock = NULL; next_action(servctx); return APR_SUCCESS; } return status; } else if (action->kind == SERVER_RECV || (action->kind == SERVER_RESPOND && servctx->outstanding_responses == 0)) { apr_size_t msg_len, len; char buf[128]; test_server_message_t *message; message = &servctx->message_list[servctx->cur_message]; msg_len = strlen(message->text); do { len = msg_len - servctx->message_buf_pos; if (len > sizeof(buf)) len = sizeof(buf); status = servctx->read(servctx, buf, &len); if (SERF_BUCKET_READ_ERROR(status)) return status; if (status == APR_EOF) { serf__log(TEST_VERBOSE, __FILE__, "Server: Client hung up the connection.\n"); break; } if (servctx->options & TEST_SERVER_DUMP) fwrite(buf, len, 1, stdout); if (strncmp(buf, message->text + servctx->message_buf_pos, len) != 0) { /* ## TODO: Better diagnostics. */ printf("Expected: (\n"); fwrite(message->text + servctx->message_buf_pos, len, 1, stdout); printf(")\n"); printf("Actual: (\n"); fwrite(buf, len, 1, stdout); printf(")\n"); return SERF_ERROR_ISSUE_IN_TESTSUITE; } servctx->message_buf_pos += len; if (servctx->message_buf_pos >= msg_len) { next_message(servctx); servctx->message_buf_pos -= msg_len; if (action->kind == SERVER_RESPOND) servctx->outstanding_responses++; if (action->kind == SERVER_RECV) next_action(servctx); break; } } while (!status); } else if (action->kind == PROXY_FORWARD) { apr_size_t len; char buf[BUFSIZE]; serf_bucket_t *tmp; /* Read all incoming data from the client to forward it to the server later. */ do { len = BUFSIZE; status = servctx->read(servctx, buf, &len); if (SERF_BUCKET_READ_ERROR(status)) return status; serf__log(TEST_VERBOSE, __FILE__, "proxy: reading %d bytes %.*s from client with " "status %d.\n", len, len, buf, status); if (status == APR_EOF) { serf__log(TEST_VERBOSE, __FILE__, "Proxy: client hung up the connection. Reset the " "connection to the server.\n"); /* We have to stop forwarding, if a new connection opens the CONNECT request should not be forwarded to the server. */ next_action(servctx); } if (!servctx->servstream) servctx->servstream = serf__bucket_stream_create( servctx->allocator, detect_eof,servctx); if (len) { tmp = serf_bucket_simple_copy_create(buf, len, servctx->allocator); serf_bucket_aggregate_append(servctx->servstream, tmp); } } while (!status); } } if (rtnevents & APR_POLLOUT) { action = &servctx->action_list[servctx->cur_action]; serf__log(TEST_VERBOSE, __FILE__, "POLLOUT when replaying action %d, kind: %d.\n", servctx->cur_action, action->kind); if (action->kind == SERVER_RESPOND && servctx->outstanding_responses) { apr_size_t msg_len; apr_size_t len; msg_len = strlen(action->text); len = msg_len - servctx->action_buf_pos; status = servctx->send(servctx, action->text + servctx->action_buf_pos, &len); if (status != APR_SUCCESS) return status; if (servctx->options & TEST_SERVER_DUMP) fwrite(action->text + servctx->action_buf_pos, len, 1, stdout); servctx->action_buf_pos += len; if (servctx->action_buf_pos >= msg_len) { next_action(servctx); servctx->outstanding_responses--; } } else if (action->kind == SERVER_KILL_CONNECTION || action->kind == SERVER_IGNORE_AND_KILL_CONNECTION) { serf__log(TEST_VERBOSE, __FILE__, "Killing this connection.\n"); apr_socket_close(servctx->client_sock); servctx->client_sock = NULL; next_action(servctx); } else if (action->kind == PROXY_FORWARD) { apr_size_t len; char *buf; if (!servctx->proxy_client_sock) { serf__log(TEST_VERBOSE, __FILE__, "Proxy: setting up connection " "to server.\n"); status = create_client_socket(&servctx->proxy_client_sock, servctx, action->text); if (!servctx->clientstream) servctx->clientstream = serf__bucket_stream_create( servctx->allocator, detect_eof,servctx); } /* Send all data received from the server to the client. */ do { apr_size_t readlen; readlen = BUFSIZE; status = serf_bucket_read(servctx->clientstream, readlen, &buf, &readlen); if (SERF_BUCKET_READ_ERROR(status)) return status; if (!readlen) break; len = readlen; serf__log(TEST_VERBOSE, __FILE__, "proxy: sending %d bytes to client.\n", len); status = servctx->send(servctx, buf, &len); if (status != APR_SUCCESS) { return status; } if (len != readlen) /* abort for now, return buf to aggregate if not everything could be sent. */ return APR_EGENERAL; } while (!status); } } else if (rtnevents & APR_POLLIN) { /* ignore */ } else { printf("Unknown rtnevents: %d\n", rtnevents); abort(); } return status; }
/* This function reads an encrypted stream and returns the decrypted stream. */ static apr_status_t ssl_decrypt(void *baton, apr_size_t bufsize, char *buf, apr_size_t *len) { serf_ssl_context_t *ctx = baton; apr_size_t priv_len; apr_status_t status; const char *data; int ssl_len; #ifdef SSL_VERBOSE printf("ssl_decrypt: begin %d\n", bufsize); #endif /* Is there some data waiting to be read? */ ssl_len = SSL_read(ctx->ssl, buf, bufsize); if (ssl_len > 0) { #ifdef SSL_VERBOSE printf("ssl_decrypt: %d bytes (%d); status: %d; flags: %d\n", ssl_len, bufsize, ctx->decrypt.status, BIO_get_retry_flags(ctx->bio)); #endif *len = ssl_len; return APR_SUCCESS; } status = serf_bucket_read(ctx->decrypt.stream, bufsize, &data, &priv_len); if (!SERF_BUCKET_READ_ERROR(status) && priv_len) { serf_bucket_t *tmp; #ifdef SSL_VERBOSE printf("ssl_decrypt: read %d bytes (%d); status: %d\n", priv_len, bufsize, status); #endif tmp = serf_bucket_simple_copy_create(data, priv_len, ctx->decrypt.pending->allocator); serf_bucket_aggregate_append(ctx->decrypt.pending, tmp); ssl_len = SSL_read(ctx->ssl, buf, bufsize); if (ssl_len < 0) { int ssl_err; ssl_err = SSL_get_error(ctx->ssl, ssl_len); switch (ssl_err) { case SSL_ERROR_SYSCALL: *len = 0; status = ctx->decrypt.status; break; case SSL_ERROR_WANT_READ: *len = 0; status = APR_EAGAIN; break; case SSL_ERROR_SSL: *len = 0; status = ctx->pending_err ? ctx->pending_err : APR_EGENERAL; ctx->pending_err = 0; break; default: *len = 0; status = APR_EGENERAL; break; } } else { *len = ssl_len; #ifdef SSL_VERBOSE printf("---\n%s\n-(%d)-\n", buf, *len); #endif } } else { *len = 0; } #ifdef SSL_VERBOSE printf("ssl_decrypt: %d %d %d\n", status, *len, BIO_get_retry_flags(ctx->bio)); #endif return status; }
static apr_status_t create_chunk(serf_bucket_t *bucket) { chunk_context_t *ctx = bucket->data; serf_bucket_t *simple_bkt; apr_size_t chunk_len; apr_size_t stream_len; struct iovec vecs[66]; /* 64 + chunk trailer + EOF trailer = 66 */ int vecs_read; int i; if (ctx->state != STATE_FETCH) { return APR_SUCCESS; } ctx->last_status = serf_bucket_read_iovec(ctx->stream, SERF_READ_ALL_AVAIL, 64, vecs, &vecs_read); if (SERF_BUCKET_READ_ERROR(ctx->last_status)) { /* Uh-oh. */ return ctx->last_status; } /* Count the length of the data we read. */ stream_len = 0; for (i = 0; i < vecs_read; i++) { stream_len += vecs[i].iov_len; } /* assert: stream_len in hex < sizeof(ctx->chunk_hdr) */ /* Inserting a 0 byte chunk indicates a terminator, which already happens * during the EOF handler below. Adding another one here will cause the * EOF chunk to be interpreted by the server as a new request. So, * we'll only do this if we have something to write. */ if (stream_len) { /* Build the chunk header. */ chunk_len = apr_snprintf(ctx->chunk_hdr, sizeof(ctx->chunk_hdr), "%" APR_UINT64_T_HEX_FMT CRLF, (apr_uint64_t)stream_len); /* Create a copy of the chunk header so we can have multiple chunks * in the pipeline at the same time. */ simple_bkt = serf_bucket_simple_copy_create(ctx->chunk_hdr, chunk_len, bucket->allocator); serf_bucket_aggregate_append(ctx->chunk, simple_bkt); /* Insert the chunk footer. */ vecs[vecs_read].iov_base = CRLF; vecs[vecs_read++].iov_len = sizeof(CRLF) - 1; } /* We've reached the end of the line for the stream. */ if (APR_STATUS_IS_EOF(ctx->last_status)) { /* Insert the chunk footer. */ vecs[vecs_read].iov_base = "0" CRLF CRLF; vecs[vecs_read++].iov_len = sizeof("0" CRLF CRLF) - 1; ctx->state = STATE_EOF; } else { /* Okay, we can return data. */ ctx->state = STATE_CHUNK; } serf_bucket_aggregate_append_iovec(ctx->chunk, vecs, vecs_read); return APR_SUCCESS; }