Ejemplo n.º 1
0
/* Function is called when 2xx responses are received. Normally we don't
 * have to do anything, except for the first response after the
 * authentication handshake. This specific response includes authentication
 * data which should be validated by the client (mutual authentication).
 */
apr_status_t
serf__validate_response_kerb_auth(int code,
                                    serf_connection_t *conn,
                                    serf_request_t *request,
                                    serf_bucket_t *response,
                                    apr_pool_t *pool)
{
    gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
        conn->proxy_authn_baton;
    serf_bucket_t *hdrs;
    const char *auth_hdr;

    hdrs = serf_bucket_response_get_headers(response);
    auth_hdr = serf_bucket_headers_get(hdrs, "WWW-Authenticate");

    if (gss_info->state != gss_api_auth_completed) {
        return do_auth(code,
                       gss_info,
                       conn,
                       auth_hdr,
                       pool);
    }

    return APR_SUCCESS;
}
Ejemplo n.º 2
0
/* Dispatch authentication handling. This function matches the possible
   authentication mechanisms with those available. Server and proxy
   authentication are evaluated separately. */
static apr_status_t dispatch_auth(int code,
                                  serf_request_t *request,
                                  serf_bucket_t *response,
                                  void *baton,
                                  apr_pool_t *pool)
{
    serf_bucket_t *hdrs;

    if (code == 401 || code == 407) {
        auth_baton_t ab = { 0 };
        const char *auth_hdr;

        ab.code = code;
        ab.status = APR_SUCCESS;
        ab.request = request;
        ab.response = response;
        ab.baton = baton;
        ab.pool = pool;

        /* Before iterating over all authn headers, check if there are any. */
        if (code == 401)
            ab.header = "WWW-Authenticate";
        else
            ab.header = "Proxy-Authenticate";

        hdrs = serf_bucket_response_get_headers(response);
        auth_hdr = serf_bucket_headers_get(hdrs, ab.header);

        if (!auth_hdr) {
            return SERF_ERROR_AUTHN_FAILED;
        }

        /* Iterate over all headers. Try to find a matching authentication scheme
           handler.

           Note: it is possible to have multiple Authentication: headers. We do
           not want to combine them (per normal header combination rules) as that
           would make it hard to parse. Instead, we want to individually parse
           and handle each header in the response, looking for one that we can
           work with.
        */
        serf_bucket_headers_do(hdrs,
                               handle_auth_header,
                               &ab);
        if (ab.status != APR_SUCCESS)
            return ab.status;

        if (!ab.scheme || ab.scheme->name == NULL) {
            /* No matching authentication found. */
            return SERF_ERROR_AUTHN_NOT_SUPPORTED;
        }
    } else {
        /* Validate the response authn headers if needed. */

    }

    return APR_SUCCESS;
}
Ejemplo n.º 3
0
/* Dispatch authentication handling. This function matches the possible
   authentication mechanisms with those available. Server and proxy
   authentication are evaluated separately. */
static apr_status_t dispatch_auth(int code,
                                  serf_request_t *request,
                                  serf_bucket_t *response,
                                  void *baton,
                                  apr_pool_t *pool)
{
    serf_bucket_t *hdrs;

    if (code == 401 || code == 407) {
        auth_baton_t ab = { 0 };
        const char *auth_hdr;

        ab.hdrs = apr_hash_make(pool);
        ab.pool = pool;

        /* Before iterating over all authn headers, check if there are any. */
        if (code == 401)
            ab.header = "WWW-Authenticate";
        else
            ab.header = "Proxy-Authenticate";

        hdrs = serf_bucket_response_get_headers(response);
        auth_hdr = serf_bucket_headers_get(hdrs, ab.header);

        if (!auth_hdr) {
            return SERF_ERROR_AUTHN_FAILED;
        }
        serf__log_skt(AUTH_VERBOSE, __FILE__, request->conn->skt,
                      "%s authz required. Response header(s): %s\n",
                      code == 401 ? "Server" : "Proxy", auth_hdr);


        /* Store all WWW- or Proxy-Authenticate headers in a dictionary.

           Note: it is possible to have multiple Authentication: headers. We do
           not want to combine them (per normal header combination rules) as that
           would make it hard to parse. Instead, we want to individually parse
           and handle each header in the response, looking for one that we can
           work with.
        */
        serf_bucket_headers_do(hdrs,
                               store_header_in_dict,
                               &ab);

        /* Iterate over all authentication schemes, in order of decreasing
           security. Try to find a authentication schema the server support. */
        return handle_auth_headers(code, baton, ab.hdrs,
                                   request, response, pool);
    }

    return APR_SUCCESS;
}
Ejemplo n.º 4
0
/* If a 200 OK was received for the CONNECT request, consider the connection
   as ready for use. */
static apr_status_t handle_response(serf_request_t *request,
                                    serf_bucket_t *response,
                                    void *handler_baton,
                                    apr_pool_t *pool)
{
    apr_status_t status;
    serf_status_line sl;
    req_ctx_t *ctx = handler_baton;
    serf_connection_t *conn = request->conn;

    /* CONNECT request was cancelled. Assuming that this is during connection
       reset, we can safely discard the request as a new one will be created
       when setting up the next connection. */
    if (!response)
        return APR_SUCCESS;

    status = serf_bucket_response_status(response, &sl);
    if (SERF_BUCKET_READ_ERROR(status)) {
        return status;
    }
    if (!sl.version && (APR_STATUS_IS_EOF(status) ||
                      APR_STATUS_IS_EAGAIN(status)))
    {
        return status;
    }

    status = serf_bucket_response_wait_for_headers(response);
    if (status && !APR_STATUS_IS_EOF(status)) {
        return status;
    }

    /* RFC 2817:  Any successful (2xx) response to a CONNECT request indicates
       that the proxy has established a connection to the requested host and
       port, and has switched to tunneling the current connection to that server
       connection.
    */
    if (sl.code >= 200 && sl.code < 300) {
        serf_bucket_t *hdrs;
        const char *val;

        conn->state = SERF_CONN_CONNECTED;

        /* Body is supposed to be empty. */
        apr_pool_destroy(ctx->pool);
        serf_bucket_destroy(conn->ssltunnel_ostream);
        serf_bucket_destroy(conn->stream);
        conn->stream = NULL;
        ctx = NULL;

        serf__log_skt(CONN_VERBOSE, __FILE__, conn->skt,
                      "successfully set up ssl tunnel.\n");

        /* Fix for issue #123: ignore the "Connection: close" header here,
           leaving the header in place would make the serf's main context
           loop close this connection immediately after reading the 200 OK
           response. */

        hdrs = serf_bucket_response_get_headers(response);
        val = serf_bucket_headers_get(hdrs, "Connection");
        if (val && strcasecmp("close", val) == 0) {
            serf__log_skt(CONN_VERBOSE, __FILE__, conn->skt,
                      "Ignore Connection: close header on this reponse, don't "
                      "close the connection now that the tunnel is set up.\n");
            serf__bucket_headers_remove(hdrs, "Connection");
        }

        return APR_EOF;
    }

    /* Authentication failure and 2xx Ok are handled at this point,
       the rest are errors. */
    return SERF_ERROR_SSLTUNNEL_SETUP_FAILED;
}
Ejemplo n.º 5
0
apr_status_t
serf__validate_response_digest_auth(peer_t peer,
                                    int code,
                                    serf_connection_t *conn,
                                    serf_request_t *request,
                                    serf_bucket_t *response,
                                    apr_pool_t *pool)
{
    const char *key;
    char *auth_attr;
    char *nextkv;
    const char *rspauth = NULL;
    const char *qop = NULL;
    const char *nc_str = NULL;
    serf_bucket_t *hdrs;
    digest_authn_info_t *digest_info = (peer == HOST) ? conn->authn_baton :
        conn->proxy_authn_baton;

    hdrs = serf_bucket_response_get_headers(response);

    /* Need a copy cuz we're going to write NUL characters into the string.  */
    if (peer == HOST)
        auth_attr = apr_pstrdup(pool,
            serf_bucket_headers_get(hdrs, "Authentication-Info"));
    else
        auth_attr = apr_pstrdup(pool,
            serf_bucket_headers_get(hdrs, "Proxy-Authentication-Info"));

    /* If there's no Authentication-Info header there's nothing to validate. */
    if (! auth_attr)
        return APR_SUCCESS;

    /* We're expecting a list of key=value pairs, separated by a comma.
       Ex. rspauth="8a4b8451084b082be6b105e2b7975087",
       cnonce="346531653132652d303033392d3435", nc=00000007,
       qop=auth */
    for ( ; (key = apr_strtok(auth_attr, ",", &nextkv)) != NULL; auth_attr = NULL) {
        char *val;

        val = strchr(key, '=');
        if (val == NULL)
            continue;
        *val++ = '\0';

        /* skip leading spaces */
        while (*key && *key == ' ')
            key++;

        /* If the value is quoted, then remove the quotes.  */
        if (*val == '"') {
            apr_size_t last = strlen(val) - 1;

            if (val[last] == '"') {
                val[last] = '\0';
                val++;
            }
        }

        if (strcmp(key, "rspauth") == 0)
            rspauth = val;
        else if (strcmp(key, "qop") == 0)
            qop = val;
        else if (strcmp(key, "nc") == 0)
            nc_str = val;
    }

    if (rspauth) {
        const char *ha2, *tmp, *resp_hdr_hex;
        unsigned char resp_hdr[APR_MD5_DIGESTSIZE];
        const char *req_uri = request->auth_baton;

        ha2 = build_digest_ha2(req_uri, "", qop, pool);
        tmp = apr_psprintf(pool, "%s:%s:%s:%s:%s:%s",
                           digest_info->ha1, digest_info->nonce, nc_str,
                           digest_info->cnonce, digest_info->qop, ha2);
        apr_md5(resp_hdr, tmp, strlen(tmp));
        resp_hdr_hex =  hex_encode(resp_hdr, pool);

        /* Incorrect response-digest in Authentication-Info header. */
        if (strcmp(rspauth, resp_hdr_hex) != 0) {
            return SERF_ERROR_AUTHN_FAILED;
        }
    }

    return APR_SUCCESS;
}
Ejemplo n.º 6
0
static apr_status_t handle_response(serf_request_t *request,
                                    serf_bucket_t *response,
                                    void *handler_baton,
                                    apr_pool_t *pool)
{
    const char *data;
    apr_size_t len;
    serf_status_line sl;
    apr_status_t status;
    handler_baton_t *ctx = handler_baton;

    if (!response) {
        /* Oh no!  We've been cancelled! */
        abort();
    }

    status = serf_bucket_response_status(response, &sl);
    if (status) {
        if (APR_STATUS_IS_EAGAIN(status)) {
            return APR_SUCCESS;
        }
        abort();
    }

    while (1) {
        status = serf_bucket_read(response, 2048, &data, &len);

        if (SERF_BUCKET_READ_ERROR(status))
            return status;

        /*fwrite(data, 1, len, stdout);*/

        if (!ctx->hdr_read) {
            serf_bucket_t *hdrs;
            const char *val;

            printf("Processing %s\n", ctx->path);

            hdrs = serf_bucket_response_get_headers(response);
            val = serf_bucket_headers_get(hdrs, "Content-Type");
            /* FIXME: This check isn't quite right because Content-Type could
             * be decorated; ideally strcasestr would be correct.
             */
            if (val && strcasecmp(val, "text/html") == 0) {
                ctx->is_html = 1;
                apr_pool_create(&ctx->parser_pool, NULL);
                ctx->parser = apr_xml_parser_create(ctx->parser_pool);
            }
            else {
                ctx->is_html = 0;
            }
            ctx->hdr_read = 1;
        }
        if (ctx->is_html) {
            apr_status_t xs;

            xs = apr_xml_parser_feed(ctx->parser, data, len);
            /* Uh-oh. */
            if (xs) {
#ifdef SERF_VERBOSE
                printf("XML parser error (feed): %d\n", xs);
#endif
                ctx->is_html = 0;
            }
        }

        /* are we done yet? */
        if (APR_STATUS_IS_EOF(status)) {

            if (ctx->is_html) {
                apr_xml_doc *xmld;
                apr_status_t xs;
                doc_path_t *dup;

                xs = apr_xml_parser_done(ctx->parser, &xmld);
                if (xs) {
#ifdef SERF_VERBOSE
                    printf("XML parser error (done): %d\n", xs);
#endif
                    return xs;
                }
                dup = (doc_path_t*)
                    serf_bucket_mem_alloc(ctx->doc_queue_alloc,
                                          sizeof(doc_path_t));
                dup->doc = xmld;
                dup->path = (char*)serf_bucket_mem_alloc(ctx->doc_queue_alloc,
                                                         ctx->path_len);
                memcpy(dup->path, ctx->path, ctx->path_len);
                dup->pool = ctx->parser_pool;

                *(doc_path_t **)apr_array_push(ctx->doc_queue) = dup;

                apr_thread_cond_signal(ctx->doc_queue_condvar);
            }

            apr_atomic_dec32(ctx->requests_outstanding);
            serf_bucket_mem_free(ctx->allocator, ctx->path);
            if (ctx->query) {
                serf_bucket_mem_free(ctx->allocator, ctx->query);
                serf_bucket_mem_free(ctx->allocator, ctx->full_path);
            }
            if (ctx->fragment) {
                serf_bucket_mem_free(ctx->allocator, ctx->fragment);
            }
            serf_bucket_mem_free(ctx->allocator, ctx);
            return APR_EOF;
        }

        /* have we drained the response so far? */
        if (APR_STATUS_IS_EAGAIN(status))
            return APR_SUCCESS;

        /* loop to read some more. */
    }
    /* NOTREACHED */
}
Ejemplo n.º 7
0
static apr_status_t 
s_setup_request(serf_request_t           *request,
                void                     *setup_ctx,
                serf_bucket_t            **req_bkt,
                serf_response_acceptor_t *acceptor,
                void                     **acceptor_ctx,
                serf_response_handler_t  *handler,
                void                     **handler_ctx,
                apr_pool_t               *UNUSED(pool))
{
  handler_ctx_t *ctx = setup_ctx;
  serf_bucket_t *hdrs_bkt;
  serf_bucket_t *body_bkt = NULL;
  request_rec *r = ctx->r;
  int ii;

  if (ctx->post_data) {
    body_bkt = serf_bucket_simple_create(ctx->post_data, ctx->post_data_len, NULL, NULL, serf_request_get_alloc(request));
  }

  *req_bkt = serf_bucket_request_create(ctx->method, ctx->path, body_bkt, serf_request_get_alloc(request));
  hdrs_bkt = serf_bucket_request_get_headers(*req_bkt);


  apr_array_header_t *headers = (apr_array_header_t*)apr_table_elts(r->headers_in);
  apr_table_entry_t  *hentryp = (apr_table_entry_t*)headers->elts;
  for (ii=headers->nelts-1; ii>=0; ii--) {
    DBG(ctx->r, "REQ[%X] REQUEST PREV key:[%s], val:[%s]", TO_ADDR(ctx->r), hentryp[ii].key, hentryp[ii].val);
    serf_bucket_headers_setc(hdrs_bkt, hentryp[ii].key, (hentryp[ii].val) ? hentryp[ii].val : "");
    DBG(ctx->r, "REQ[%X] REQUEST AFTER key:[%s], val:[%s]", TO_ADDR(ctx->r), hentryp[ii].key, hentryp[ii].val);
  }
  if (ctx->post_data) {
    DBG(ctx->r, "REQ[%X] REQUEST PREV key:[%s], val:[%s]", TO_ADDR(ctx->r), "X-Chxj-Forward", "Done");
    serf_bucket_headers_setc(hdrs_bkt, "X-Chxj-Forward", "Done");
    DBG(ctx->r, "REQ[%X] REQUEST AFTER key:[%s], val:[%s]", TO_ADDR(ctx->r), "X-Chxj-Forward", "Done");
    DBG(ctx->r, "REQ[%X] REQUEST PREV key:[%s], val:[%s]", TO_ADDR(ctx->r), "X-Chxj-Content-Length", apr_psprintf(r->pool, "%" APR_SIZE_T_FMT, ctx->post_data_len));
    serf_bucket_headers_setc(hdrs_bkt, "X-Chxj-Content-Length", apr_psprintf(r->pool, "%" APR_SIZE_T_FMT , ctx->post_data_len));
    DBG(ctx->r, "REQ[%X] REQUEST AFTER key:[%s], val:[%s]", TO_ADDR(ctx->r), "X-Chxj-Content-Length", apr_psprintf(r->pool, "%" APR_SIZE_T_FMT, ctx->post_data_len));
  }
  DBG(ctx->r, "REQ[%X] REQUEST Content-Length:[%s]", TO_ADDR(ctx->r), serf_bucket_headers_get(hdrs_bkt, "Content-Length"));

  apr_atomic_inc32(&(ctx->requests_outstanding));
  if (ctx->acceptor_ctx->ssl_flag) {
    serf_bucket_alloc_t *req_alloc;
    app_ctx_t *app_ctx = ctx->acceptor_ctx;

    req_alloc = serf_request_get_alloc(request);

    if (app_ctx->ssl_ctx == NULL) {
      *req_bkt = serf_bucket_ssl_encrypt_create(*req_bkt, NULL, app_ctx->bkt_alloc);
      app_ctx->ssl_ctx = serf_bucket_ssl_encrypt_context_get(*req_bkt);
    }
    else {
      *req_bkt = serf_bucket_ssl_encrypt_create(*req_bkt, app_ctx->ssl_ctx, app_ctx->bkt_alloc);
    }
  }
  *acceptor       = ctx->acceptor;
  *acceptor_ctx   = ctx->acceptor_ctx;
  *handler        = ctx->handler;
  *handler_ctx    = ctx;

  return APR_SUCCESS;
}
Ejemplo n.º 8
0
Archivo: auth.c Proyecto: vocho/openqnx
svn_error_t *
svn_ra_serf__handle_auth(int code,
                         svn_ra_serf__session_t *session,
                         svn_ra_serf__connection_t *conn,
                         serf_request_t *request,
                         serf_bucket_t *response,
                         apr_pool_t *pool)
{
  serf_bucket_t *hdrs;
  const svn_ra_serf__auth_protocol_t *prot;
  char *auth_name, *auth_attr, *auth_hdr, *header, *header_attr;
  svn_error_t *cached_err;

  hdrs = serf_bucket_response_get_headers(response);
  if (code == 401)
    auth_hdr = (char*)serf_bucket_headers_get(hdrs, "WWW-Authenticate");
  else if (code == 407)
    auth_hdr = (char*)serf_bucket_headers_get(hdrs, "Proxy-Authenticate");

  if (!auth_hdr)
    {
      if (session->auth_protocol)
        return svn_error_createf(SVN_ERR_AUTHN_FAILED, NULL,
                                 "%s Authentication failed",
                                 session->auth_protocol->auth_name);
      else
        return svn_error_create(SVN_ERR_AUTHN_FAILED, NULL, NULL);
    }

  /* If multiple *-Authenticate headers are found, serf will combine them into
     one header, with the values separated by a comma. */
  header = apr_strtok(auth_hdr, ",", &header_attr);

  while (header)
    {
      svn_boolean_t proto_found = FALSE;
      auth_name = apr_strtok(header, " ", &auth_attr);

      cached_err = SVN_NO_ERROR;

      /* Find the matching authentication handler.
         Note that we don't reuse the auth protocol stored in the session,
         as that may have changed. (ex. fallback from ntlm to basic.) */
      for (prot = serf_auth_protocols; prot->code != 0; ++prot)
        {
          if (code == prot->code && strcmp(auth_name, prot->auth_name) == 0)
            {
              svn_serf__auth_handler_func_t handler = prot->handle_func;
              svn_error_t *err = NULL;

              /* If this is the first time we use this protocol in this session,
                 make sure to initialize the authentication part of the session
                 first. */
              if (code == 401 && session->auth_protocol != prot)
                {
                  err = prot->init_conn_func(session, conn, session->pool);
                  if (err == SVN_NO_ERROR)
                    session->auth_protocol = prot;
                  else
                    session->auth_protocol = NULL;
                }
             else if (code == 407 && session->proxy_auth_protocol != prot)
                {
                  err = prot->init_conn_func(session, conn, session->pool);
                  if (err == SVN_NO_ERROR)
                    session->proxy_auth_protocol = prot;
                  else
                    session->proxy_auth_protocol = NULL;
                }

              if (err == SVN_NO_ERROR)
                {
                  proto_found = TRUE;
                  err = handler(session, conn, request, response,
                                header, auth_attr, session->pool);
                }
              if (err)
                {
                  /* If authentication fails, cache the error for now. Try the
                     next available scheme. If there's none raise the error. */
                  proto_found = FALSE;
                  prot = NULL;
                  if (cached_err)
                    svn_error_clear(cached_err);
                  cached_err = err;
                }

              break;
            }
        }
      if (proto_found)
        break;

      /* Try the next Authentication header. */
      header = apr_strtok(NULL, ",", &header_attr);
    }

  SVN_ERR(cached_err);

  if (!prot || prot->auth_name == NULL)
    {
      /* Support more authentication mechanisms. */
      return svn_error_createf(SVN_ERR_AUTHN_FAILED, NULL,
                               "%s authentication not supported.\n"
                               "Authentication failed", auth_name);
    }

  return SVN_NO_ERROR;
}
/* Perform one iteration of the state machine.
 *
 * Will return when one the following conditions occurred:
 *  1) a state change
 *  2) an error
 *  3) the stream is not ready or at EOF
 *  4) APR_SUCCESS, meaning the machine can be run again immediately
 */
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:
        /* 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);
        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_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_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) {
            const void *v;

            /* Advance the state. */
            ctx->state = STATE_BODY;

            ctx->body =
                serf_bucket_barrier_create(ctx->stream, bkt->allocator);

            /*
             * Instaweb/mod_pagespeed change: This section is
             * re-ordered from the original code from serf to Follow
             * HTTP spec by checking "Transfer-Encoding: chunked",
             * before "Content-Length".
             */

            /* Are we C-L, chunked, or conn close? */
            v = serf_bucket_headers_get(ctx->headers, "Transfer-Encoding");

            /* Need to handle multiple transfer-encoding. */
            if (v && strcasecmp("chunked", v) == 0) {
                ctx->chunked = 1;
                ctx->body = serf_bucket_dechunk_create(ctx->body,
                                                       bkt->allocator);
            }
            else {
                v = serf_bucket_headers_get(ctx->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_limit_create(ctx->body, length,
                                                         bkt->allocator);
                }
                else if ((ctx->sl.code == 204 || ctx->sl.code == 304)) {
                    ctx->state = STATE_DONE;
                }
            }

            /*
             * Instaweb would prefer to receive gzipped output if that's what
             * was asked for.
             *
             * v = serf_bucket_headers_get(ctx->headers, "Content-Encoding");
             * if (v) {
             *   * 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);
             *  }
             *  else if (v && strcasecmp("deflate", v) == 0) {
             *      ctx->body =
             *          serf_bucket_deflate_create(ctx->body, bkt->allocator,
             *                                     SERF_DEFLATE_DEFLATE);
             *  }
             * }
             */

            /* If we're a HEAD request, we don't receive a body. */
            if (ctx->head_req) {
                ctx->state = STATE_DONE;
            }
        }
        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;
}