static apr_status_t run_machine()

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;
}