/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2011-2025 Asynchronous B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Asynchronous B.V.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */
#include <Constants.h>
#include <Core/Controller.h>

/*************************************************************************
 *
 * Hook functions for Core::Controller. This pertains the hooks that the
 * parent classes (ServerKit::HttpServer and ServerKit::Server) provide,
 * as well as hooks by libraries such as libev.
 *
 *************************************************************************/

namespace Passenger {
namespace Core {

using namespace std;
using namespace boost;


/****************************
 *
 * Private methods
 *
 ****************************/


ServerKit::Channel::Result
Controller::onBodyBufferData(Channel *_channel, const MemoryKit::mbuf &buffer, int errcode) {
	FileBufferedChannel *channel = reinterpret_cast<FileBufferedChannel *>(_channel);
	Request *req = static_cast<Request *>(static_cast<
		ServerKit::BaseHttpRequest *>(channel->getHooks()->userData));
	Client *client = static_cast<Client *>(req->client);
	Controller *self = static_cast<Controller *>(getServerFromClient(client));
	SKC_LOG_EVENT_FROM_STATIC(self, Controller, client, "onBodyBufferData");

	assert(req->requestBodyBuffering);
	return self->whenSendingRequest_onRequestBody(client, req, buffer, errcode);
}

#ifdef DEBUG_CC_EVENT_LOOP_BLOCKING
	void
	Controller::onEventLoopPrepare(EV_P_ struct ev_prepare *w, int revents) {
		Controller *self = static_cast<Controller *>(w->data);
		ev_now_update(EV_A);
		self->timeBeforeBlocking = ev_now(EV_A);
	}
#endif

void
Controller::onEventLoopCheck(EV_P_ struct ev_check *w, int revents) {
	Controller *self = static_cast<Controller *>(w->data);
	self->turboCaching.updateState(ev_now(EV_A));
	#ifdef DEBUG_CC_EVENT_LOOP_BLOCKING
		self->reportLargeTimeDiff(NULL, "Event loop slept",
			self->timeBeforeBlocking, ev_now(EV_A));
	#endif
}


/****************************
 *
 * Protected methods
 *
 ****************************/


void
Controller::onClientAccepted(Client *client) {
	ParentClass::onClientAccepted(client);
	client->connectedAt = ev_now(getLoop());
}

void
Controller::onRequestObjectCreated(Client *client, Request *req) {
	ParentClass::onRequestObjectCreated(client, req);

	req->appSink.setContext(getContext());
	req->appSink.setHooks(&req->hooks);

	req->appSource.setContext(getContext());
	req->appSource.setHooks(&req->hooks);
	req->appSource.setDataCallback(_onAppSourceData);

	req->bodyBuffer.setContext(getContext());
	req->bodyBuffer.setHooks(&req->hooks);
	req->bodyBuffer.setDataCallback(onBodyBufferData);
}

void
Controller::deinitializeClient(Client *client) {
	ParentClass::deinitializeClient(client);
	client->output.clearBuffersFlushedCallback();
	client->output.setDataFlushedCallback(getClientOutputDataFlushedCallback());
}

void
Controller::reinitializeRequest(Client *client, Request *req) {
	ParentClass::reinitializeRequest(client, req);

	// bodyBuffer is initialized in Controller::beginBufferingBody().
	// appSink and appSource are initialized in Controller::checkoutSession().


	req->connectedWatcher.active = false;
	req->connectedWatcherTimout.active = false;

	req->startedAt = 0;
	req->state = Request::ANALYZING_REQUEST;
	req->dechunkResponse = false;
	req->requestBodyBuffering = false;
	req->https = false;
	req->stickySession = false;
	req->sessionCheckoutTry = 0;
	req->halfClosePolicy = Request::HALF_CLOSE_POLICY_UNINITIALIZED;
	req->appResponseInitialized = false;
	req->strip100ContinueHeader = false;
	req->hasPragmaHeader = false;
	req->connectTimeout = DEFAULT_CONNECT_TIMEOUT;
	req->host = NULL;
	req->config = requestConfig;
	req->bodyBytesBuffered = 0;
	req->cacheKey = HashedStaticString();
	req->cacheControl = NULL;
	req->varyCookie = NULL;
	req->envvars = NULL;

	#ifdef DEBUG_CC_EVENT_LOOP_BLOCKING
		req->timedAppPoolGet = false;
		req->timeBeforeAccessingApplicationPool = 0;
		req->timeOnRequestHeaderSent = 0;
		req->timeOnResponseBegun = 0;
	#endif

	/***************/
}

void
Controller::deinitializeRequest(Client *client, Request *req) {
	if (req->connectedWatcher.active) {
		ev_io_stop(getLoop(), &req->connectedWatcher);
	}
	if (req->connectedWatcherTimout.active) {
		ev_timer_stop(getLoop(), &req->connectedWatcherTimout);
	}

	req->session.reset();
	req->config.reset();

	req->appSink.setConsumedCallback(NULL);
	req->appSink.deinitialize();
	req->appSource.deinitialize();
	req->bodyBuffer.clearBuffersFlushedCallback();
	req->bodyBuffer.deinitialize();

	/***************/
	/***************/

	if (req->appResponseInitialized) {
		deinitializeAppResponse(client, req);
	}

	ParentClass::deinitializeRequest(client, req);
}

void
Controller::reinitializeAppResponse(Client *client, Request *req) {
	AppResponse *resp = &req->appResponse;

	req->appResponseInitialized = true;

	resp->httpMajor = 1;
	resp->httpMinor = 0;
	resp->httpState = AppResponse::PARSING_HEADERS;
	resp->bodyType  = AppResponse::RBT_NO_BODY;
	resp->wantKeepAlive = false;
	resp->oneHundredContinueSent = false;
	resp->statusCode = 0;
	resp->parserState.headerParser = getHeaderParserStatePool().construct();
	createAppResponseHeaderParser(getContext(), req).initialize();
	resp->aux.bodyInfo.contentLength = 0; // Sets the entire union to 0.
	resp->bodyAlreadyRead = 0;
	resp->date = NULL;
	resp->setCookie = NULL;
	resp->cacheControl = NULL;
	resp->expiresHeader = NULL;
	resp->lastModifiedHeader = NULL;

	resp->headerCacheBuffers = NULL;
	resp->nHeaderCacheBuffers = 0;
	psg_lstr_init(&resp->bodyCacheBuffer);
}

void
Controller::deinitializeAppResponse(Client *client, Request *req) {
	AppResponse *resp = &req->appResponse;

	req->appResponseInitialized = false;

	if (resp->httpState == AppResponse::PARSING_HEADERS
	 && resp->parserState.headerParser != NULL)
	{
		getHeaderParserStatePool().destroy(resp->parserState.headerParser);
		resp->parserState.headerParser = NULL;
	}

	ServerKit::HeaderTable::Iterator it(resp->headers);
	while (*it != NULL) {
		psg_lstr_deinit(&it->header->key);
		psg_lstr_deinit(&it->header->origKey);
		psg_lstr_deinit(&it->header->val);
		it.next();
	}

	it = ServerKit::HeaderTable::Iterator(resp->secureHeaders);
	while (*it != NULL) {
		psg_lstr_deinit(&it->header->key);
		psg_lstr_deinit(&it->header->origKey);
		psg_lstr_deinit(&it->header->val);
		it.next();
	}

	resp->headers.clear();
	resp->secureHeaders.clear();

	if (resp->setCookie != NULL) {
		psg_lstr_deinit(resp->setCookie);
	}
	psg_lstr_deinit(&resp->bodyCacheBuffer);
}

ServerKit::Channel::Result
Controller::onRequestBody(Client *client, Request *req, const MemoryKit::mbuf &buffer,
	int errcode)
{
	switch (req->state) {
	case Request::BUFFERING_REQUEST_BODY:
		return whenBufferingBody_onRequestBody(client, req, buffer, errcode);
	case Request::FORWARDING_BODY_TO_APP:
		return whenSendingRequest_onRequestBody(client, req, buffer, errcode);
	default:
		P_BUG("Unknown state " << req->state);
		return Channel::Result(0, false);
	}
}

void
Controller::onNextRequestEarlyReadError(Client *client, Request *req, int errcode) {
	ParentClass::onNextRequestEarlyReadError(client, req, errcode);
	if (req->halfClosePolicy == Request::HALF_CLOSE_UPON_NEXT_REQUEST_EARLY_READ_ERROR) {
		SKC_TRACE(client, 3, "Half-closing application socket with SHUT_WR"
			" because the next request's early read error has been detected: "
			<< ServerKit::getErrorDesc(errcode) << " (errno=" << errcode << ")");
		req->halfClosePolicy = Request::HALF_CLOSE_PERFORMED;
		assert(req->session != NULL);
		::shutdown(req->session->fd(), SHUT_WR);
	}
}

bool
Controller::shouldDisconnectClientOnShutdown(Client *client) {
	return ParentClass::shouldDisconnectClientOnShutdown(client)
		|| !mainConfig.gracefulExit;
}

bool
Controller::shouldAutoDechunkBody(Client *client, Request *req) {
	// When buffering the body, we'll want to buffer the dechunked data,
	// (and when passing the request to the app we'll also add Content-Length
	// and remove Transfer-Encoding) so turn auto-dechunking on in that case.
	//
	// Otherwise we'll want to disable auto-dechunking because we'll
	// pass the raw chunked body to the app.
	return req->requestBodyBuffering;
}

bool
Controller::supportsUpgrade(Client *client, Request *req) {
	return true;
}


/****************************
 *
 * Public methods
 *
 ****************************/


unsigned int
Controller::getClientName(const Client *client, char *buf, size_t size) const {
	char *pos = buf;
	const char *end = buf + size - 1;
	// WARNING: If you change the format, be sure to change
	// ApiServer::extractThreadNumberFromClientName() too.
	pos += uintToString(mainConfig.threadNumber, pos, end - pos);
	pos = appendData(pos, end, "-", 1);
	pos += uintToString(client->number, pos, end - pos);
	*pos = '\0';
	return pos - buf;
}

StaticString
Controller::getServerName() const {
	return mainConfig.serverLogName;
}


} // namespace Core
} // namespace Passenger
