/*
 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/* DEBUG: section 33    Client-side Routines */

#include "squid.h"
#include "acl/FilledChecklist.h"
#include "client_side.h"
#include "client_side_reply.h"
#include "client_side_request.h"
#include "clientStream.h"
#include "comm/Write.h"
#include "http/one/RequestParser.h"
#include "http/Stream.h"
#include "HttpHeaderTools.h"
#include "servers/Http1Server.h"
#include "SquidConfig.h"
#include "Store.h"
#include "tunnel.h"

CBDATA_NAMESPACED_CLASS_INIT(Http1, Server);

Http::One::Server::Server(const MasterXaction::Pointer &xact, bool beHttpsServer):
    AsyncJob("Http1::Server"),
    ConnStateData(xact),
    isHttpsServer(beHttpsServer)
{
}

time_t
Http::One::Server::idleTimeout() const
{
    return Config.Timeout.clientIdlePconn;
}

void
Http::One::Server::start()
{
    ConnStateData::start();

    // XXX: Until we create an HttpsServer class, use this hack to allow old
    // client_side.cc code to manipulate ConnStateData object directly
    if (isHttpsServer) {
        postHttpsAccept();
        return;
    }

    typedef CommCbMemFunT<Server, CommTimeoutCbParams> TimeoutDialer;
    AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
                                      TimeoutDialer, this, Http1::Server::requestTimeout);
    commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
    readSomeData();
}

void
Http::One::Server::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
{
    if (!handleRequestBodyData())
        return;

    // too late to read more body
    if (!isOpen() || stoppedReceiving())
        return;

    readSomeData();
}

Http::Stream *
Http::One::Server::parseOneRequest()
{
    // reset because the protocol may have changed if this is the first request
    // and because we never bypass parsing failures of N+1st same-proto request
    preservingClientData_ = shouldPreserveClientData();

    // parser is incremental. Generate new parser state if we,
    // a) do not have one already
    // b) have completed the previous request parsing already
    if (!parser_ || !parser_->needsMoreData())
        parser_ = new Http1::RequestParser(preservingClientData_);

    /* Process request */
    Http::Stream *context = parseHttpRequest(parser_);

    return context;
}

void clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &request);

bool
Http::One::Server::buildHttpRequest(Http::StreamPointer &context)
{
    HttpRequest::Pointer request;
    ClientHttpRequest *http = context->http;
    if (context->flags.parsed_ok == 0) {
        debugs(33, 2, "Invalid Request");
        // determine which error page templates to use for specific parsing errors
        err_type errPage = ERR_INVALID_REQ;
        switch (parser_->parseStatusCode) {
        case Http::scRequestHeaderFieldsTooLarge:
        case Http::scUriTooLong:
            errPage = ERR_TOO_BIG;
            break;
        case Http::scMethodNotAllowed:
            errPage = ERR_UNSUP_REQ;
            break;
        case Http::scHttpVersionNotSupported:
            errPage = ERR_UNSUP_HTTPVERSION;
            break;
        default:
            if (parser_->method() == METHOD_NONE || parser_->requestUri().length() == 0)
                // no method or url parsed, probably is wrong protocol
                errPage = ERR_PROTOCOL_UNKNOWN;
            // else use default ERR_INVALID_REQ set above.
            break;
        }
        // setReplyToError() requires log_uri
        // must be already initialized via ConnStateData::abortRequestParsing()
        assert(http->log_uri);

        const char * requestErrorBytes = inBuf.c_str();
        if (!tunnelOnError(errPage)) {
            setReplyError(context, request, errPage, parser_->parseStatusCode, requestErrorBytes);
            // HttpRequest object not build yet, there is no reason to call
            // clientProcessRequestFinished method
        }

        return false;
    }

    // TODO: move URL parse into Http Parser and INVALID_URL into the above parse error handling
    const auto mx = MasterXaction::MakePortful(port);
    mx->tcpClient = clientConnection;
    request = HttpRequest::FromUrlXXX(http->uri, mx, parser_->method());
    if (!request) {
        debugs(33, 5, "Invalid URL: " << http->uri);
        // setReplyToError() requires log_uri
        http->setLogUriToRawUri(http->uri, parser_->method());

        const char * requestErrorBytes = inBuf.c_str();
        if (!tunnelOnError(ERR_INVALID_URL)) {
            setReplyError(context, request, ERR_INVALID_URL, Http::scBadRequest, requestErrorBytes);
            // HttpRequest object not build yet, there is no reason to call
            // clientProcessRequestFinished method
        }
        return false;
    }

    /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */
    /* We currently only support 0.9, 1.0, 1.1 properly */
    /* TODO: move HTTP-specific processing into servers/HttpServer and such */
    if ( (parser_->messageProtocol().major == 0 && parser_->messageProtocol().minor != 9) ||
            (parser_->messageProtocol().major > 1) ) {

        debugs(33, 5, "Unsupported HTTP version discovered. :\n" << parser_->messageProtocol());
        // setReplyToError() requires log_uri
        http->setLogUriToRawUri(http->uri, parser_->method());

        const char * requestErrorBytes = nullptr; //HttpParserHdrBuf(parser_);
        if (!tunnelOnError(ERR_UNSUP_HTTPVERSION)) {
            setReplyError(context, request, ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, requestErrorBytes);
            clientProcessRequestFinished(this, request);
        }
        return false;
    }

    /* compile headers */
    if (parser_->messageProtocol().major >= 1 && !request->parseHeader(*parser_.getRaw())) {
        debugs(33, 5, "Failed to parse request headers:\n" << parser_->mimeHeader());
        // setReplyToError() requires log_uri
        http->setLogUriToRawUri(http->uri, parser_->method());
        const char * requestErrorBytes = nullptr; //HttpParserHdrBuf(parser_);
        if (!tunnelOnError(ERR_INVALID_REQ)) {
            setReplyError(context, request, ERR_INVALID_REQ, Http::scBadRequest, requestErrorBytes);
            clientProcessRequestFinished(this, request);
        }
        return false;
    }

    // when absolute-URI is provided Host header should be ignored. However
    // some code still uses Host directly so normalize it using the previously
    // sanitized URL authority value.
    // For now preserve the case where Host is completely absent. That matters.
    if (request->header.has(Http::HdrType::HOST))
        request->header.updateOrAddStr(Http::HdrType::HOST, request->url.authority());

    // TODO: We fill request notes here until we find a way to verify whether
    // no ACL checking is performed before ClientHttpRequest::doCallouts().
    if (hasNotes()) {
        assert(!request->hasNotes());
        request->notes()->append(notes().getRaw());
    }

    http->initRequest(request.getRaw());

    return true;
}

void
Http::One::Server::setReplyError(Http::StreamPointer &context, HttpRequest::Pointer &request, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
{
    quitAfterError(request.getRaw());
    if (!context->connRegistered()) {
        debugs(33, 2, "Client stream deregister it self, nothing to do");
        clientConnection->close();
        return;
    }
    clientStreamNode *node = context->getClientReplyContext();
    clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
    assert (repContext);

    repContext->setReplyToError(requestError, errStatusCode, context->http->uri, this, nullptr, requestErrorBytes, nullptr);

    assert(context->http->out.offset == 0);
    context->pullData();
}

void
Http::One::Server::proceedAfterBodyContinuation(Http::StreamPointer context)
{
    debugs(33, 5, "Body Continuation written");
    clientProcessRequest(this, parser_, context.getRaw());
}

int
Http::One::Server::pipelinePrefetchMax() const
{
    const auto context = pipeline.back();
    const auto request = (context && context->http) ? context->http->request : nullptr;
    if (request && request->header.has(Http::HdrType::UPGRADE))
        return 0;

    return ConnStateData::pipelinePrefetchMax();
}

void
Http::One::Server::processParsedRequest(Http::StreamPointer &context)
{
    if (!buildHttpRequest(context))
        return;

    ClientHttpRequest *http = context->http;
    HttpRequest::Pointer request = http->request;

    if (request->header.has(Http::HdrType::EXPECT)) {
        const String expect = request->header.getList(Http::HdrType::EXPECT);
        const bool supportedExpect = (expect.caseCmp("100-continue") == 0);
        if (!supportedExpect) {
            clientStreamNode *node = context->getClientReplyContext();
            quitAfterError(request.getRaw());
            // setReplyToError() requires log_uri
            assert(http->log_uri);
            clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
            assert (repContext);
            repContext->setReplyToError(ERR_INVALID_REQ, Http::scExpectationFailed, http->uri,
                                        this, request.getRaw(), nullptr, nullptr);
            assert(context->http->out.offset == 0);
            context->pullData();
            clientProcessRequestFinished(this, request);
            return;
        }

        if (Config.accessList.forceRequestBodyContinuation) {
            ACLFilledChecklist bodyContinuationCheck(Config.accessList.forceRequestBodyContinuation, request.getRaw());
            bodyContinuationCheck.al = http->al;
            bodyContinuationCheck.syncAle(request.getRaw(), http->log_uri);
            if (bodyContinuationCheck.fastCheck().allowed()) {
                debugs(33, 5, "Body Continuation forced");
                request->forcedBodyContinuation = true;
                //sendControlMsg
                HttpReply::Pointer rep = new HttpReply;
                rep->sline.set(Http::ProtocolVersion(), Http::scContinue);

                typedef UnaryMemFunT<Http1::Server, Http::StreamPointer> CbDialer;
                const AsyncCall::Pointer cb = asyncCall(11, 3,  "Http1::Server::proceedAfterBodyContinuation", CbDialer(this, &Http1::Server::proceedAfterBodyContinuation, Http::StreamPointer(context)));
                sendControlMsg(HttpControlMsg(rep, cb));
                return;
            }
        }
    }
    clientProcessRequest(this, parser_, context.getRaw());
}

void
Http::One::Server::noteBodyConsumerAborted(BodyPipe::Pointer ptr)
{
    ConnStateData::noteBodyConsumerAborted(ptr);
    stopReceiving("virgin request body consumer aborted"); // closes ASAP
}

void
Http::One::Server::handleReply(HttpReply *rep, StoreIOBuffer receivedData)
{
    // the caller guarantees that we are dealing with the current context only
    Http::StreamPointer context = pipeline.front();
    Must(context != nullptr);
    const ClientHttpRequest *http = context->http;
    Must(http != nullptr);

    // After sending Transfer-Encoding: chunked (at least), always send
    // the last-chunk if there was no error, ignoring responseFinishedOrFailed.
    const bool mustSendLastChunk = http->request->flags.chunkedReply &&
                                   !http->request->flags.streamError &&
                                   !EBIT_TEST(http->storeEntry()->flags, ENTRY_BAD_LENGTH) &&
                                   !context->startOfOutput();
    const bool responseFinishedOrFailed = !rep &&
                                          !receivedData.data &&
                                          !receivedData.length;
    if (responseFinishedOrFailed && !mustSendLastChunk) {
        context->writeComplete(0);
        return;
    }

    if (!context->startOfOutput()) {
        context->sendBody(receivedData);
        return;
    }

    assert(rep);
    context->sendStartOfMessage(rep, receivedData);
}

bool
Http::One::Server::writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call)
{
    Http::StreamPointer context = pipeline.front();
    Must(context != nullptr);

    // Ignore this late control message if we have started sending a
    // reply to the user already (e.g., after an error).
    if (context->reply) {
        debugs(11, 2, "drop 1xx made late by " << context->reply);
        return false;
    }

    const ClientHttpRequest *http = context->http;

    // remember Upgrade header; removeHopByHopEntries() will remove it
    String upgradeHeader;
    const auto switching = (rep->sline.status() == Http::scSwitchingProtocols);
    if (switching)
        upgradeHeader = rep->header.getList(Http::HdrType::UPGRADE);

    // apply selected clientReplyContext::buildReplyHeader() mods
    // it is not clear what headers are required for control messages
    rep->header.removeHopByHopEntries();
    // paranoid: ContentLengthInterpreter has cleaned non-generated replies
    rep->removeIrrelevantContentLength();

    if (switching && /* paranoid: */ upgradeHeader.size()) {
        rep->header.putStr(Http::HdrType::UPGRADE, upgradeHeader.termedBuf());
        rep->header.putStr(Http::HdrType::CONNECTION, "upgrade");
        // keep-alive is redundant, breaks some 101 (Switching Protocols) recipients
    } else {
        rep->header.putStr(Http::HdrType::CONNECTION, "keep-alive");
    }

    httpHdrMangleList(&rep->header, http->request, http->al, ROR_REPLY);

    MemBuf *mb = rep->pack();

    debugs(11, 2, "HTTP Client " << clientConnection);
    debugs(11, 2, "HTTP Client CONTROL MSG:\n---------\n" << mb->buf << "\n----------");

    Comm::Write(clientConnection, mb, call);

    delete mb;
    return true;
}

void
Http::One::Server::noteTakeServerConnectionControl(ServerConnectionContext server)
{
    const auto context = pipeline.front();
    assert(context);
    const auto http = context->http;
    assert(http);
    assert(http->request);

    stopReading();
    Must(!writer);

    switchToTunnel(http->request, clientConnection,
                   server.connection(), server.preReadServerBytes);
}

ConnStateData *
Http::NewServer(const MasterXaction::Pointer &xact)
{
    return new Http1::Server(xact, false);
}

ConnStateData *
Https::NewServer(const MasterXaction::Pointer &xact)
{
    return new Http1::Server(xact, true);
}

