/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2014-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.
 */
#ifndef _PASSENGER_SERVER_KIT_HTTP_REQUEST_H_
#define _PASSENGER_SERVER_KIT_HTTP_REQUEST_H_

#include <psg_sysqueue.h>
#include <boost/cstdint.hpp>
#include <boost/atomic.hpp>
#include <ServerKit/llhttp.h>
#include <ServerKit/Hooks.h>
#include <ServerKit/Client.h>
#include <ServerKit/HeaderTable.h>
#include <ServerKit/FileBufferedChannel.h>
#include <ServerKit/HttpHeaderParserState.h>
#include <ServerKit/HttpChunkedBodyParserState.h>
#include <MemoryKit/palloc.h>
#include <DataStructures/LString.h>

namespace Passenger {
namespace ServerKit {


class BaseHttpRequest {
public:
	enum HttpState {
		/** The request headers are still being parsed. */
		PARSING_HEADERS,
		/** Internal state used by the parser. Users should never see this state. */
		PARSED_HEADERS,
		/** The request headers have been parsed, and there is no body. */
		COMPLETE,
		/** The request headers have been parsed, and we are now receiving/parsing the body,
		 * which does not have the chunked transfer-encoding. */
		PARSING_BODY,
		/** The request headers have been parsed, and we are now receiving/parsing the body,
		 * which has the chunked transfer-encoding. */
		PARSING_CHUNKED_BODY,
		/** The request headers have been parsed, and the connection has been upgraded. */
		UPGRADED,

		// The following states are recognized as 'ended'.

		/** An error occurred. */
		ERROR,
		/**
		 * The request has been ended. We've deinitialized the request object, and we're now
		 * waiting for output to be flushed before transitioning to WAITING_FOR_REFERENCES.
		 * In this state, the client object's `currentRequest` field still points to this
		 * request.
		 */
		FLUSHING_OUTPUT,
		/**
		 * The request has ended. We've deinitialized the request object, and we're now
		 * waiting until all references to this request object are gone. In this state,
		 * the client object's `currentRequest` field no longer points to this request.
		 */
		WAITING_FOR_REFERENCES,
		/** This request object is in the freelist. */
		IN_FREELIST
	};

	// Enum values are deliberately chosen so that hasBody() can be branchless.
	enum BodyType {
		/** The request has no body and the connection will not be upgraded. */
		RBT_NO_BODY = 0,
		/** The connection has been upgraded. */
		RBT_UPGRADE = 1,
		/** The request body's size is determined by the Content-Length header. */
		RBT_CONTENT_LENGTH = 2,
		/** The request body's size is determined by the chunked Transfer-Encoding. */
		RBT_CHUNKED = 4
	};

	boost::uint8_t httpMajor;
	boost::uint8_t httpMinor;
	HttpState httpState: 5;
	BodyType bodyType: 3;

	llhttp_method_t method: 6;
	bool wantKeepAlive: 1;
	bool responseBegun: 1;
	bool detectingNextRequestEarlyReadError: 1;

	boost::atomic<int> refcount;

	BaseClient *client;
	union {
		// If httpState == PARSING_HEADERS
		HttpHeaderParserState *headerParser;
		// If httpState == PARSING_CHUNKED_BODY
		HttpChunkedBodyParserState chunkedBodyParser;
	} parserState;
	psg_pool_t *pool;
	Hooks hooks;
	// Guaranteed to be contiguous.
	LString path;
	HeaderTable headers;
	// We separate headers and secure headers because the number of normal
	// headers is variable, but the number of secure headers is more or less
	// constant.
	HeaderTable secureHeaders;
	// HttpServer feeds all body data received via client->input to bodyChannel
	Channel bodyChannel;

	union {
		/** Length of the message body. Only use when httpState != ERROR. */
		union {
			// If bodyType == RBT_CONTENT_LENGTH. Guaranteed to be > 0.
			boost::uint64_t contentLength;
			// If bodyType == RBT_CHUNKED
			bool endChunkReached;
		} bodyInfo;

		/** If a request parsing error occurred, the error code is stored here.
		 * Only use if httpState == ERROR.
		 */
		int parseError;
	} aux;
	boost::uint64_t bodyAlreadyRead;

	ev_tstamp lastDataReceiveTime;
	ev_tstamp lastDataSendTime;

	/**
	 * The start index of the '?' character in `path`. -1 when it doesn't exist.
	 */
	int queryStringIndex;

	/* When a body error is encountered and bodyChannel is not immediately available,
	 * the error code is temporarily stored here.
	 */
	int bodyError;

	/**
	 * When a request body read error, or a client socket EOF, has been detected
	 * after the current request body has already fully received, the error code is
	 * temporarily stored here so that it may be processed at the next request. The
	 * error is not passed to the bodyChannel immediately because it isn't an error
	 * part of the current request's body. But users of HttpServer can still query
	 * this field to see that an error is imminent, and may choose to abort early.
	 *
	 * The value is either the body read error code, or EARLY_EOF_DETECTED. The
	 * latter means that a client socket EOF has been detected.
	 *
	 * A value of 0 means that everthing is ok.
	 */
	int nextRequestEarlyReadError;


	BaseHttpRequest()
		: refcount(1),
		  client(NULL),
		  pool(NULL),
		  headers(16),
		  secureHeaders(32),
		  bodyAlreadyRead(0)
	{
		psg_lstr_init(&path);
		aux.bodyInfo.contentLength = 0; // Sets the entire union to 0.
	}

	const char *getHttpStateString() const {
		switch (httpState) {
		case PARSING_HEADERS:
			return "PARSING_HEADERS";
		case PARSED_HEADERS:
			return "PARSED_HEADERS";
		case COMPLETE:
			return "COMPLETE";
		case PARSING_BODY:
			return "PARSING_BODY";
		case PARSING_CHUNKED_BODY:
			return "PARSING_CHUNKED_BODY";
		case UPGRADED:
			return "UPGRADED";
		case ERROR:
			return "ERROR";
		case FLUSHING_OUTPUT:
			return "FLUSHING_OUTPUT";
		case WAITING_FOR_REFERENCES:
			return "WAITING_FOR_REFERENCES";
		case IN_FREELIST:
			return "IN_FREELIST";
		default:
			return "UNKNOWN";
		}
	}

	const char *getBodyTypeString() const {
		switch (bodyType) {
		case RBT_NO_BODY:
			return "NO_BODY";
		case RBT_UPGRADE:
			return "UPGRADE";
		case RBT_CONTENT_LENGTH:
			return "CONTENT_LENGTH";
		case RBT_CHUNKED:
			return "CHUNKED";
		default:
			return "UNKNOWN";
		}
	}

	bool bodyFullyRead() const {
		switch (bodyType) {
		case RBT_NO_BODY:
			return true;
		case RBT_UPGRADE:
			return false;
		case RBT_CONTENT_LENGTH:
			return bodyAlreadyRead >= aux.bodyInfo.contentLength;
		case RBT_CHUNKED:
			return aux.bodyInfo.endChunkReached;
		default:
			return false;
		}
	}

	bool hasBody() const {
		return bodyType & (RBT_CONTENT_LENGTH | RBT_CHUNKED);
	}

	bool upgraded() const {
		return bodyType == RBT_UPGRADE;
	}

	// Not mutually exclusive with ended(). If a request has begun() and is ended(),
	// then it just means that it hasn't been reinitialized for the next request yet.
	bool begun() const {
		return (int) httpState >= COMPLETE;
	}

	bool ended() const {
		// Branchless OR.
		return ((int) httpState >= (int) ERROR) | !client->connected();
	}

	StaticString getPathWithoutQueryString() const {
		if (queryStringIndex == -1) {
			return StaticString(path.start->data, path.size);
		} else {
			return StaticString(path.start->data, queryStringIndex);
		}
	}

	StaticString getQueryString() const {
		if (queryStringIndex == -1) {
			return StaticString();
		} else {
			return StaticString(path.start->data + queryStringIndex + 1,
				path.size - queryStringIndex - 1);
		}
	}
};


#define DEFINE_SERVER_KIT_BASE_HTTP_REQUEST_FOOTER(RequestType) \
	public: \
	union { \
		STAILQ_ENTRY(RequestType) freeRequest; \
		LIST_ENTRY(RequestType) lingeringRequest; \
	} nextRequest


class HttpRequest: public BaseHttpRequest {
public:
	DEFINE_SERVER_KIT_BASE_HTTP_REQUEST_FOOTER(Passenger::ServerKit::HttpRequest);
};


} // namespace ServerKit
} // namespace Passenger

#endif /* _PASSENGER_SERVER_KIT_HTTP_REQUEST_H_ */
