HttpTransact::handle_cache_operation_on_forward_server_response(State* s)
{
  DebugTxn("http_trans", "[handle_cache_operation_on_forward_server_response] (hcoofsr)");
  DebugTxn("http_seq", "[handle_cache_operation_on_forward_server_response]");

  HTTPHdr *base_response = NULL;
  HTTPStatus server_response_code = HTTP_STATUS_NONE;
  HTTPStatus client_response_code = HTTP_STATUS_NONE;
  const char *warn_text = NULL;
  bool cacheable = false;

  cacheable = is_response_cacheable(s, &s->hdr_info.client_request, &s->hdr_info.server_response);
  DebugTxn("http_trans", "[hcoofsr] response %s cacheable", cacheable ? "is" : "is not");

  // set the correct next action, cache action, response code, and base response

  server_response_code = s->hdr_info.server_response.status_get();
  switch (server_response_code) {
  case HTTP_STATUS_NOT_MODIFIED:       // 304
    SET_VIA_STRING(VIA_SERVER_RESULT, VIA_SERVER_NOT_MODIFIED);

    // determine the correct cache action, next state, and response
    // precondition: s->cache_info.action should be one of the following
    // CACHE_DO_DELETE, or CACHE_DO_UPDATE; otherwise, it's an error.
    if (s->api_server_response_ignore && s->cache_info.action == CACHE_DO_UPDATE) {
      s->api_server_response_ignore = false;
      ink_assert(s->cache_info.object_read);
      base_response = s->cache_info.object_read->response_get();
      s->cache_info.action = CACHE_DO_SERVE;
      DebugTxn("http_trans", "[hcoofsr] not merging, cache action changed to: %s",
            HttpDebugNames::get_cache_action_name(s->cache_info.action));
      s->next_action = SM_ACTION_SERVE_FROM_CACHE;
      client_response_code = base_response->status_get();
    } else if ((s->cache_info.action == CACHE_DO_DELETE) || ((s->cache_info.action == CACHE_DO_UPDATE) && !cacheable)) {
      if (is_request_conditional(&s->hdr_info.client_request)) {
        client_response_code =
          HttpTransactCache::match_response_to_request_conditionals(&s->hdr_info.client_request,
                                                                    s->cache_info.object_read->response_get());
      } else {
        client_response_code = HTTP_STATUS_OK;
      }

      if (client_response_code != HTTP_STATUS_OK) {
        // we can just forward the not modified response
        // from the server and delete the cached copy
        base_response = &s->hdr_info.server_response;
        client_response_code = base_response->status_get();
        s->cache_info.action = CACHE_DO_DELETE;
        s->next_action = SM_ACTION_INTERNAL_CACHE_DELETE;
      } else {
        // We got screwed. The client did not send a conditional request,
        // but we had a cached copy which we revalidated. The server has
        // now told us to delete the cached copy and sent back a 304.
        // We need to send the cached copy to the client, then delete it.
        if (s->method == HTTP_WKSIDX_HEAD) {
          s->cache_info.action = CACHE_DO_DELETE;
          s->next_action = SM_ACTION_SERVER_READ;
        } else {
          s->cache_info.action = CACHE_DO_SERVE_AND_DELETE;
          s->next_action = SM_ACTION_SERVE_FROM_CACHE;
        }
        base_response = s->cache_info.object_read->response_get();
        client_response_code = base_response->status_get();
      }

    } else if (s->cache_info.action == CACHE_DO_UPDATE && is_request_conditional(&s->hdr_info.server_request)) {
      // CACHE_DO_UPDATE and server response is cacheable
      if (is_request_conditional(&s->hdr_info.client_request)) {
        if (s->txn_conf->cache_when_to_revalidate != 4)
          client_response_code =
            HttpTransactCache::match_response_to_request_conditionals(&s->hdr_info.client_request,
                                                                      s->cache_info.object_read->response_get());
        else
          client_response_code = server_response_code;
      } else {
        client_response_code = HTTP_STATUS_OK;
      }

      if (client_response_code != HTTP_STATUS_OK) {
        // delete the cached copy unless configured to always verify IMS
        if (s->txn_conf->cache_when_to_revalidate != 4) {
          s->cache_info.action = CACHE_DO_UPDATE;
          s->next_action = SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS;
          /* base_response will be set after updating headers below */
        } else {
          s->cache_info.action = CACHE_DO_NO_ACTION;
          s->next_action = SM_ACTION_INTERNAL_CACHE_NOOP;
          base_response = &s->hdr_info.server_response;
        }
      } else {
        if (s->method == HTTP_WKSIDX_HEAD) {
          s->cache_info.action = CACHE_DO_UPDATE;
          s->next_action = SM_ACTION_SERVER_READ;
        } else {
          if (s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE)) {
            s->state_machine->do_range_setup_if_necessary();
            // Note that even if the Range request is not satisfiable, we
            // update and serve this cache. This will give a 200 response to
            // a bad client, but allows us to avoid pegging the origin (e.g. abuse).
          }
          s->cache_info.action = CACHE_DO_SERVE_AND_UPDATE;
          s->next_action = SM_ACTION_SERVE_FROM_CACHE;
        }
        /* base_response will be set after updating headers below */
      }

    } else {                    // cache action != CACHE_DO_DELETE and != CACHE_DO_UPDATE

      // bogus response from server. deal by tunnelling to client.
      // server should not have sent back a 304 because our request
      // should not have been an conditional.
      DebugTxn("http_trans", "[hcoofsr] 304 for non-conditional request");
      s->cache_info.action = CACHE_DO_NO_ACTION;
      s->next_action = SM_ACTION_INTERNAL_CACHE_NOOP;
      client_response_code = s->hdr_info.server_response.status_get();
      base_response = &s->hdr_info.server_response;

      // since this is bad, insert warning header into client response
      // The only exception case is conditional client request,
      // cache miss, and client request being unlikely cacheable.
      // In this case, the server request is given the same
      // conditional headers as client request (see build_request()).
      // So an unexpected 304 might be received.
      // FIXME: check this case
      if (is_request_likely_cacheable(s, &s->hdr_info.client_request)) {
        warn_text = "Proxy received unexpected 304 response; " "content may be stale";
      }
    }

    break;

  case HTTP_STATUS_HTTPVER_NOT_SUPPORTED:      // 505
    {
      bool keep_alive = (s->current.server->keep_alive == HTTP_KEEPALIVE);

      s->next_action = how_to_open_connection(s);

      /* Downgrade the request level and retry */
      if (!HttpTransactHeaders::downgrade_request(&keep_alive, &s->hdr_info.server_request)) {
        build_error_response(s, HTTP_STATUS_HTTPVER_NOT_SUPPORTED, "HTTP Version Not Supported",
                             "response#bad_version", NULL);
        s->next_action = SM_ACTION_SEND_ERROR_CACHE_NOOP;
        s->already_downgraded = true;
      } else {
        if (!keep_alive) {
          /* START Hack */
          (s->hdr_info.server_request).field_delete(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION);
          /* END   Hack */
        }
        s->already_downgraded = true;
        s->next_action = how_to_open_connection(s);
      }
    }
    return;

  default:
    DebugTxn("http_trans", "[hcoofsr] response code: %d", server_response_code);
    SET_VIA_STRING(VIA_SERVER_RESULT, VIA_SERVER_SERVED);
    SET_VIA_STRING(VIA_PROXY_RESULT, VIA_PROXY_SERVED);


    /* if we receive a 500, 502, 503 or 504 while revalidating
       a document, treat the response as a 304 and in effect revalidate the document for
       negative_revalidating_lifetime. (negative revalidating)
     */

    if ((server_response_code == HTTP_STATUS_INTERNAL_SERVER_ERROR ||
         server_response_code == HTTP_STATUS_GATEWAY_TIMEOUT ||
         server_response_code == HTTP_STATUS_BAD_GATEWAY ||
         server_response_code == HTTP_STATUS_SERVICE_UNAVAILABLE) &&
        s->cache_info.action == CACHE_DO_UPDATE &&
        s->txn_conf->negative_revalidating_enabled && is_stale_cache_response_returnable(s)) {
      DebugTxn("http_trans", "[hcoofsr] negative revalidating: revalidate stale object and serve from cache");

      s->cache_info.object_store.create();
      s->cache_info.object_store.request_set(&s->hdr_info.client_request);
      s->cache_info.object_store.response_set(s->cache_info.object_read->response_get());
      base_response = s->cache_info.object_store.response_get();
      time_t exp_time = s->txn_conf->negative_revalidating_lifetime + ink_cluster_time();
      base_response->set_expires(exp_time);

      SET_VIA_STRING(VIA_CACHE_FILL_ACTION, VIA_CACHE_UPDATED);
      HTTP_INCREMENT_TRANS_STAT(http_cache_updates_stat);

      // unset Cache-control: "need-revalidate-once" (if it's set)
      // This directive is used internally by T.S. to invalidate
      // documents so that an invalidated document needs to be
      // revalidated again.
      base_response->unset_cooked_cc_need_revalidate_once();

      if (is_request_conditional(&s->hdr_info.client_request) &&
          HttpTransactCache::match_response_to_request_conditionals(&s->hdr_info.client_request,
                                                                    s->cache_info.object_read->response_get()) == HTTP_STATUS_NOT_MODIFIED) {
        s->next_action = SM_ACTION_INTERNAL_CACHE_UPDATE_HEADERS;
        client_response_code = HTTP_STATUS_NOT_MODIFIED;
      } else {
        if (s->method == HTTP_WKSIDX_HEAD) {
          s->cache_info.action = CACHE_DO_UPDATE;
          s->next_action = SM_ACTION_INTERNAL_CACHE_NOOP;
        } else {
          s->cache_info.action = CACHE_DO_SERVE_AND_UPDATE;
          s->next_action = SM_ACTION_SERVE_FROM_CACHE;
        }

        client_response_code = s->cache_info.object_read->response_get()->status_get();
      }

      ink_assert(base_response->valid());

      if (client_response_code == HTTP_STATUS_NOT_MODIFIED) {
        ink_assert(GET_VIA_STRING(VIA_CLIENT_REQUEST) != VIA_CLIENT_SIMPLE);
        SET_VIA_STRING(VIA_CLIENT_REQUEST, VIA_CLIENT_IMS);
        SET_VIA_STRING(VIA_PROXY_RESULT, VIA_PROXY_NOT_MODIFIED);
      } else {
        SET_VIA_STRING(VIA_PROXY_RESULT, VIA_PROXY_SERVED);
      }

      ink_assert(client_response_code != HTTP_STATUS_NONE);

      if (s->next_action == SM_ACTION_SERVE_FROM_CACHE && s->state_machine->do_transform_open()) {
        set_header_for_transform(s, base_response);
      } else {
        build_response(s, base_response, &s->hdr_info.client_response, s->client_info.http_version, client_response_code);
      }

      return;
    }

    s->next_action = SM_ACTION_SERVER_READ;
    client_response_code = server_response_code;
    base_response = &s->hdr_info.server_response;

    s->negative_caching = is_negative_caching_appropriate(s);

    // determine the correct cache action given the original cache action,
    // cacheability of server response, and request method
    // precondition: s->cache_info.action is one of the following
    // CACHE_DO_UPDATE, CACHE_DO_WRITE, or CACHE_DO_DELETE
    if (s->api_server_response_no_store) {
      s->cache_info.action = CACHE_DO_NO_ACTION;
    } else if (s->api_server_response_ignore &&
               server_response_code == HTTP_STATUS_OK &&
               s->hdr_info.server_request.method_get_wksidx() == HTTP_WKSIDX_HEAD) {
      s->api_server_response_ignore = false;
      ink_assert(s->cache_info.object_read);
      base_response = s->cache_info.object_read->response_get();
      s->cache_info.action = CACHE_DO_SERVE;
      DebugTxn("http_trans", "[hcoofsr] ignoring server response, "
            "cache action changed to: %s", HttpDebugNames::get_cache_action_name(s->cache_info.action));
      s->next_action = SM_ACTION_SERVE_FROM_CACHE;
      client_response_code = base_response->status_get();
    } else if (s->cache_info.action == CACHE_DO_UPDATE) {
      if (s->www_auth_content == CACHE_AUTH_FRESH) {
        s->cache_info.action = CACHE_DO_NO_ACTION;
      } else if (s->www_auth_content == CACHE_AUTH_STALE && server_response_code == HTTP_STATUS_UNAUTHORIZED) {
        s->cache_info.action = CACHE_DO_NO_ACTION;
      } else if (!cacheable) {
        s->cache_info.action = CACHE_DO_DELETE;
      } else if (s->method == HTTP_WKSIDX_HEAD) {
        s->cache_info.action = CACHE_DO_DELETE;
      } else {
        ink_assert(s->cache_info.object_read != 0);
        s->cache_info.action = CACHE_DO_REPLACE;
      }

    } else if (s->cache_info.action == CACHE_DO_WRITE) {
      if (!cacheable && !s->negative_caching) {
        s->cache_info.action = CACHE_DO_NO_ACTION;
      } else if (s->method == HTTP_WKSIDX_HEAD) {
        s->cache_info.action = CACHE_DO_NO_ACTION;
      } else {
        s->cache_info.action = CACHE_DO_WRITE;
      }

    } else if (s->cache_info.action == CACHE_DO_DELETE) {
      // do nothing

    } else {
      ink_assert(!("cache action inconsistent with current state"));
    }
    // postcondition: s->cache_info.action is one of the following
    // CACHE_DO_REPLACE, CACHE_DO_WRITE, CACHE_DO_DELETE, or
    // CACHE_DO_NO_ACTION

    // Check see if we ought to serve the client a 304 based on
    //   it's IMS date.  We may gotten a 200 back from the origin
    //   server if our (the proxies's) cached copy was out of date
    //   but the client's wasn't.  However, if the response is
    //   not cacheable we ought not issue a 304 to the client so
    //   make sure we are writing the document to the cache if
    //   before issuing a 304
    if (s->cache_info.action == CACHE_DO_WRITE ||
        s->cache_info.action == CACHE_DO_NO_ACTION || s->cache_info.action == CACHE_DO_REPLACE) {
      if (s->negative_caching) {
        HTTPHdr *resp;
        s->cache_info.object_store.create();
        s->cache_info.object_store.request_set(&s->hdr_info.client_request);
        s->cache_info.object_store.response_set(&s->hdr_info.server_response);
        resp = s->cache_info.object_store.response_get();
        if (!resp->presence(MIME_PRESENCE_EXPIRES)) {
          time_t exp_time = s->txn_conf->negative_caching_lifetime + ink_cluster_time();

          resp->set_expires(exp_time);
        }
      } else if (is_request_conditional(&s->hdr_info.client_request) && server_response_code == HTTP_STATUS_OK) {
        client_response_code =
          HttpTransactCache::match_response_to_request_conditionals(&s->hdr_info.client_request,
                                                                    &s->hdr_info.server_response);

        DebugTxn("http_trans", "[hcoofsr] conditional request, 200 " "response, send back 304 if possible [crc=%d]", client_response_code);
        if ((client_response_code == HTTP_STATUS_NOT_MODIFIED) || (client_response_code == HTTP_STATUS_PRECONDITION_FAILED)) {
          switch (s->cache_info.action) {
          case CACHE_DO_WRITE:
          case CACHE_DO_REPLACE:
            s->next_action = SM_ACTION_INTERNAL_CACHE_WRITE;
            break;
          case CACHE_DO_DELETE:
            s->next_action = SM_ACTION_INTERNAL_CACHE_DELETE;
            break;
          default:
            s->next_action = SM_ACTION_INTERNAL_CACHE_NOOP;
            break;
          }
        } else {
          SET_VIA_STRING(VIA_PROXY_RESULT, VIA_PROXY_SERVER_REVALIDATED);
        }
      }
    } else if (s->negative_caching) {
      s->negative_caching = false;
    }

    break;
  }

  // update stat, set via string, etc

  switch (s->cache_info.action) {
  case CACHE_DO_SERVE_AND_DELETE:
    // fall through
  case CACHE_DO_DELETE:
    DebugTxn("http_trans", "[hcoofsr] delete cached copy");
    SET_VIA_STRING(VIA_CACHE_FILL_ACTION, VIA_CACHE_DELETED);
    HTTP_INCREMENT_TRANS_STAT(http_cache_deletes_stat);
    break;
  case CACHE_DO_WRITE:
    DebugTxn("http_trans", "[hcoofsr] cache write");
    SET_VIA_STRING(VIA_CACHE_FILL_ACTION, VIA_CACHE_WRITTEN);
    HTTP_INCREMENT_TRANS_STAT(http_cache_writes_stat);
    break;
  case CACHE_DO_SERVE_AND_UPDATE:
    // fall through
  case CACHE_DO_UPDATE:
    // fall through
  case CACHE_DO_REPLACE:
    DebugTxn("http_trans", "[hcoofsr] cache update/replace");
    SET_VIA_STRING(VIA_CACHE_FILL_ACTION, VIA_CACHE_UPDATED);
    HTTP_INCREMENT_TRANS_STAT(http_cache_updates_stat);
    break;
  default:
    break;
  }

  if ((client_response_code == HTTP_STATUS_NOT_MODIFIED) && (s->cache_info.action != CACHE_DO_NO_ACTION)) {
    /* ink_assert(GET_VIA_STRING(VIA_CLIENT_REQUEST)
       != VIA_CLIENT_SIMPLE); */
    DebugTxn("http_trans", "[hcoofsr] Client request was conditional");
    SET_VIA_STRING(VIA_CLIENT_REQUEST, VIA_CLIENT_IMS);
    SET_VIA_STRING(VIA_PROXY_RESULT, VIA_PROXY_NOT_MODIFIED);
  } else {
    SET_VIA_STRING(VIA_PROXY_RESULT, VIA_PROXY_SERVED);
  }

  ink_assert(client_response_code != HTTP_STATUS_NONE);

  // The correct cache action, next action, and response code are set.
  // Do the real work below.

  // first update the cached object
  if ((s->cache_info.action == CACHE_DO_UPDATE) || (s->cache_info.action == CACHE_DO_SERVE_AND_UPDATE)) {
    DebugTxn("http_trans", "[hcoofsr] merge and update cached copy");
    merge_and_update_headers_for_cache_update(s);
    base_response = s->cache_info.object_store.response_get();
    // unset Cache-control: "need-revalidate-once" (if it's set)
    // This directive is used internally by T.S. to invalidate documents
    // so that an invalidated document needs to be revalidated again.
    base_response->unset_cooked_cc_need_revalidate_once();
    // unset warning revalidation failed header if it set
    // (potentially added by negative revalidating)
    delete_warning_value(base_response, HTTP_WARNING_CODE_REVALIDATION_FAILED);
  }
  ink_assert(base_response->valid());

  if ((s->cache_info.action == CACHE_DO_WRITE) || (s->cache_info.action == CACHE_DO_REPLACE)) {
    set_headers_for_cache_write(s, &s->cache_info.object_store, &s->hdr_info.server_request, &s->hdr_info.server_response);
  }
  // 304, 412, and 416 responses are handled here
  if ((client_response_code == HTTP_STATUS_NOT_MODIFIED) || (client_response_code == HTTP_STATUS_PRECONDITION_FAILED)) {
    // Because we are decoupling User-Agent validation from
    //  Traffic Server validation just build a regular 304
    //  if the exception of adding prepending the VIA
    //  header to show the revalidation path
    build_response(s, base_response, &s->hdr_info.client_response, s->client_info.http_version, client_response_code);

    // Copy over the response via field (if any) preserving
    //  the order of the fields
    MIMEField *resp_via = s->hdr_info.server_response.field_find(MIME_FIELD_VIA, MIME_LEN_VIA);
    if (resp_via) {
      MIMEField *our_via;
      our_via = s->hdr_info.client_response.field_find(MIME_FIELD_VIA, MIME_LEN_VIA);
      if (our_via == NULL) {
        our_via = s->hdr_info.client_response.field_create(MIME_FIELD_VIA, MIME_LEN_VIA);
        s->hdr_info.client_response.field_attach(our_via);
      }
      // HDR FIX ME - Mulitple appends are VERY slow
      while (resp_via) {
        int clen;
        const char *cfield = resp_via->value_get(&clen);
        s->hdr_info.client_response.field_value_append(our_via, cfield, clen, true);
        resp_via = resp_via->m_next_dup;
      }
    }
    // a warning text is added only in the case of a NOT MODIFIED response
    if (warn_text) {
      HttpTransactHeaders::insert_warning_header(s->http_config_param, &s->hdr_info.client_response,
                                                 HTTP_WARNING_CODE_MISC_WARNING, warn_text, strlen(warn_text));
    }

    if (!s->cop_test_page)
      DUMP_HEADER("http_hdrs", &s->hdr_info.client_response, s->state_machine_id, "Proxy's Response (Client Conditionals)");
    return;
  }
  // all other responses (not 304, 412, 416) are handled here
  else {
    if (((s->next_action == SM_ACTION_SERVE_FROM_CACHE) ||
         (s->next_action == SM_ACTION_SERVER_READ)) && s->state_machine->do_transform_open()) {
      set_header_for_transform(s, base_response);
    } else {
      build_response(s, base_response, &s->hdr_info.client_response, s->client_info.http_version, client_response_code);
    }
  }

  return;
}