in buckets/response_buckets.c [266:495]
static apr_status_t run_machine(serf_bucket_t *bkt, response_context_t *ctx)
{
apr_status_t status = APR_SUCCESS; /* initialize to avoid gcc warnings */
switch (ctx->state) {
case STATE_STATUS_LINE:
case STATE_NEXT_STATUS_LINE:
/* RFC 2616 says that CRLF is the only line ending, but we can easily
* accept any kind of line ending.
*/
status = fetch_line(ctx, SERF_NEWLINE_ANY);
/* Convert generic 'line too long' error to specific one. */
if (status == SERF_ERROR_LINE_TOO_LONG)
return SERF_ERROR_STATUS_LINE_TOO_LONG;
else if (SERF_BUCKET_READ_ERROR(status))
return status;
if (ctx->linebuf.state == SERF_LINEBUF_READY) {
/* The Status-Line is in the line buffer. Process it. */
status = parse_status_line(ctx, bkt->allocator);
if (status)
return status;
/* Good times ahead: we're switching protocols! */
if (ctx->sl.code == 101) {
ctx->body =
serf_bucket_barrier_create(ctx->stream, bkt->allocator);
ctx->state = STATE_DONE;
break;
}
/* Okay... move on to reading the headers. */
ctx->state = STATE_PRE_HEADERS;
}
else {
/* The connection closed before we could get the next
* response. Treat the request as lost so that our upper
* end knows the server never tried to give us a response.
*/
if (APR_STATUS_IS_EOF(status)) {
return SERF_ERROR_REQUEST_LOST;
}
}
break;
case STATE_PRE_HEADERS:
{
serf_bucket_t *read_hdrs;
ctx->state = STATE_HEADERS;
/* Perhaps we can just read a headers bucket? */
read_hdrs = serf_bucket_read_bucket(ctx->stream,
&serf_bucket_type_headers);
if (read_hdrs) {
if (ctx->incoming_headers)
serf_bucket_destroy(ctx->incoming_headers);
ctx->incoming_headers = read_hdrs;
ctx->state = STATE_PRE_BODY;
}
else if (!ctx->incoming_headers) {
ctx->incoming_headers =
serf_bucket_headers_create(bkt->allocator);
}
if (!ctx->fetch_headers)
ctx->fetch_headers = ctx->incoming_headers;
}
break;
case STATE_HEADERS:
status = fetch_headers(bkt, ctx);
if (SERF_BUCKET_READ_ERROR(status))
return status;
/* If an empty line was read, then we hit the end of the headers.
* Move on to the body.
*/
if (ctx->linebuf.state != SERF_LINEBUF_READY || ctx->linebuf.used)
break;
/* Advance the state. */
ctx->state = STATE_PRE_BODY;
/* fall through */
case STATE_PRE_BODY:
{
const char *v;
int chunked = 0;
int gzip = 0;
if (ctx->fetch_headers != ctx->incoming_headers) {
/* We now only have one interesting set of headers remaining */
serf_bucket_destroy(ctx->fetch_headers);
ctx->fetch_headers = ctx->incoming_headers;
}
if (ctx->sl.code >= 100 && ctx->sl.code < 200) {
/* We received a set of informational headers.
Prepare for the next set */
ctx->incoming_headers = serf_bucket_headers_create(
bkt->allocator);
ctx->state = STATE_NEXT_STATUS_LINE;
break;
}
/* Advance the state. */
ctx->state = STATE_BODY;
/* If this is a response to a HEAD request, or 204 or 304
then we don't receive a real body. */
if (!expect_body(ctx)) {
ctx->body = serf_bucket_simple_create(NULL, 0, NULL, NULL,
bkt->allocator);
ctx->state = STATE_BODY;
break;
}
ctx->body =
serf_bucket_barrier_create(ctx->stream, bkt->allocator);
/* Are we chunked, C-L, or conn close? */
v = serf_bucket_headers_get(ctx->fetch_headers,
"Transfer-Encoding");
/* Need a copy cuz we're going to write NUL characters into the
string. */
if (v) {
char *attrs = serf_bstrdup(bkt->allocator, v);
char *at = attrs;
char *next = NULL;
while ((v = apr_strtok(at, ", ", &next))) {
at = NULL;
if (!strcasecmp(v, "chunked"))
chunked = 1;
else if (!strcasecmp(v, "gzip"))
gzip = 1;
/* ### Others? */
}
serf_bucket_mem_free(bkt->allocator, attrs);
}
if (chunked) {
ctx->chunked = 1;
ctx->body = serf_bucket_dechunk_create(ctx->body,
bkt->allocator);
serf_bucket_set_config(ctx->body, ctx->config);
}
else {
/* RFC 7231 specifies that we should determine the message
length via Transfer-Encoding chunked, when both chunked
and Content-Length are passed */
v = serf_bucket_headers_get(ctx->fetch_headers,
"Content-Length");
if (v) {
apr_uint64_t length;
length = apr_strtoi64(v, NULL, 10);
if (errno == ERANGE) {
return APR_FROM_OS_ERROR(ERANGE);
}
ctx->body = serf_bucket_response_body_create(
ctx->body, length, bkt->allocator);
}
}
/* Transfer encodings are handled by the transport, while content
encoding is part of the data itself. */
if (gzip) {
ctx->body =
serf_bucket_deflate_create(ctx->body, bkt->allocator,
SERF_DEFLATE_GZIP);
serf_bucket_set_config(ctx->body, ctx->config);
}
v = serf_bucket_headers_get(ctx->fetch_headers,
"Content-Encoding");
if (v && ctx->decode_content) {
/* Need to handle multiple content-encoding. */
if (v && strcasecmp("gzip", v) == 0) {
ctx->body =
serf_bucket_deflate_create(ctx->body, bkt->allocator,
SERF_DEFLATE_GZIP);
serf_bucket_set_config(ctx->body, ctx->config);
}
else if (v && strcasecmp("deflate", v) == 0) {
ctx->body =
serf_bucket_deflate_create(ctx->body, bkt->allocator,
SERF_DEFLATE_DEFLATE);
serf_bucket_set_config(ctx->body, ctx->config);
}
else if (serf_bucket_is_brotli_supported()
&& v && strcasecmp("br", v) == 0)
{
ctx->body =
serf_bucket_brotli_decompress_create(ctx->body,
bkt->allocator);
serf_bucket_set_config(ctx->body, ctx->config);
}
}
}
break;
case STATE_BODY:
/* Don't do anything. */
break;
case STATE_TRAILERS:
status = fetch_headers(bkt, ctx);
if (SERF_BUCKET_READ_ERROR(status))
return status;
/* If an empty line was read, then we're done. */
if (ctx->linebuf.state == SERF_LINEBUF_READY && !ctx->linebuf.used) {
ctx->state = STATE_DONE;
return APR_EOF;
}
break;
case STATE_DONE:
return APR_EOF;
default:
/* Not reachable */
return APR_EGENERAL;
}
return status;
}