/*---------------------------------------------------------------------------*/
/* webserverplugin.cpp                                                       */
/* copyright (c) innovaphone 2017                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

/*****************************************************************************/
/*                                   NOTE                                    */
/*                                   ----                                    */
/*                                                                           */
/* This file will be used by the innovaphone webserver and the interface     */
/* IWebserverPlugin. There is now need to use the class or defines in your   */
/* own projects.                                                             */
/*                                                                           */
/*****************************************************************************/

#include <platform.h>

#include "sdk/common/lib/httplib.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/webserver_plugin.h"

#include "webserver_internal.h"

ExtendedHeader::ExtendedHeader(IInstanceLog * const log, uint16_t protocolVersion)
    : log(log)
{
    this->ClearData(*this);
    this->protocolVersion = protocolVersion;
}


ExtendedHeader::~ExtendedHeader()
{
    if (this->releaseBuffer) free(this->buffer);
    if (this->releaseFieldListBuffer) free(this->fieldListBuffer);
}


void ExtendedHeader::ClearData(ExtendedHeader & eh)
{
    eh.protocolVersion = 0;
    eh.requestPath = nullptr;
    eh.requestPathLen = 0;
    eh.registeredPathForRequest = nullptr;
    eh.registeredPathForRequestLen = 0;
    eh.etag = nullptr;
    eh.etagLen = 0;
    eh.ranges = nullptr;
    eh.rangeCount = 0;
    eh.rangesBuffer = nullptr;
    eh.connectionFlags.dwordVal = 0;
    eh.fieldListBuffer = nullptr;
    eh.fieldListBufferSize = 0;
    eh.host = nullptr;
    eh.hostLen = 0;
    eh.pathToRegister = nullptr;
    eh.pathToRegisterLen = 0;
    eh.authUser = nullptr;
    eh.authUserLen = 0;
    eh.authPassword = nullptr;
    eh.authPasswordLen = 0;
    eh.dynAuth = false;
    eh.wsCloseReason = WSCR_NORMAL_CLOSE;
    eh.transferInfo = {};
    eh.certHeader = {};
    eh.redirectPath = nullptr;
    eh.redirectPathLen = 0;
    eh.dataSize = 0;
    eh.chunkEncoded = false;
	eh.statusCode = 0; // 0 = no response code, will end up in a 200 OK response.
	eh.statusDescription = nullptr;
	eh.statusDescriptionLen = 0;
    eh.remoteAddress = nullptr;
    eh.remoteAddressLen = 0;
    eh.remotePort = 0;

    eh.wsCloseReasonSet = false;
    eh.resourceTypeSet = false;
    eh.transferInfoSet = false;
    eh.releaseFieldListBuffer = false;
    eh.releaseBuffer = false;

    eh.buffer = nullptr;
    eh.headerLength = 0;
}


void ExtendedHeader::MoveData(ExtendedHeader & from)
{
    this->protocolVersion = from.protocolVersion;
    this->requestPath = from.requestPath;
    this->requestPathLen = from.requestPathLen;
    this->registeredPathForRequest = from.registeredPathForRequest;
    this->registeredPathForRequestLen = from.registeredPathForRequestLen;
    this->etag = from.etag;
    this->etagLen = from.etagLen;
    this->ranges = from.ranges;
    this->rangeCount = from.rangeCount;
    this->rangesBuffer = from.rangesBuffer;
    this->connectionFlags = from.connectionFlags;
    this->fieldListBuffer = from.fieldListBuffer;
    this->fieldListBufferSize = from.fieldListBufferSize;
    this->host = from.host;
    this->hostLen = from.hostLen;
    this->pathToRegister = from.pathToRegister;
    this->pathToRegisterLen = from.pathToRegisterLen;
    this->authUser = from.authUser;
    this->authUserLen = from.authUserLen;
    this->authPassword = from.authPassword;
    this->authPasswordLen = from.authPasswordLen;
    this->dynAuth = from.dynAuth;
    this->wsCloseReason = from.wsCloseReason;
    this->transferInfo = from.transferInfo;
    this->certHeader = from.certHeader;
    this->redirectPath = from.redirectPath;
    this->redirectPathLen = from.redirectPathLen;
    this->dataSize = from.dataSize;
    this->chunkEncoded = from.chunkEncoded;
	this->statusCode = from.statusCode;
	this->statusDescription = from.statusDescription;
	this->statusDescriptionLen = from.statusDescriptionLen;
    memcpy(this->remoteAddressBuffer, from.remoteAddressBuffer, sizeof(from.remoteAddressBuffer));
    this->remoteAddress = (from.remoteAddress == from.remoteAddressBuffer) ? this->remoteAddressBuffer : from.remoteAddress;
    this->remoteAddressLen = from.remoteAddressLen;
    this->remotePort = from.remotePort;

    this->wsCloseReasonSet = from.wsCloseReasonSet;
    this->resourceTypeSet = from.resourceTypeSet;
    this->transferInfoSet = from.transferInfoSet;
    this->releaseFieldListBuffer = from.releaseFieldListBuffer;
    this->releaseBuffer = from.releaseBuffer;

    this->buffer = from.buffer;
    this->headerLength = from.headerLength;
}


inline byte * ExtendedHeader::WriteDetailDataHeader(byte * dest, extender_header_item_type_t dt, word len)
{
    *dest = dt;
    ++dest;
    WRITE_WORD_NET(dest, len);
    return dest + sizeof(word);
}


inline byte * ExtendedHeader::WriteDetailData(byte * dest, extender_header_item_type_t dt, word len, const char * data)
{
    dest = this->WriteDetailDataHeader(dest, dt, len);
    memcpy(dest, data, len);
    dest[len - 1] = '\0';
    return dest + len;
}


inline byte * ExtendedHeader::WriteDetailData(byte * dest, extender_header_item_type_t dt, dword value)
{
    dest = this->WriteDetailDataHeader(dest, dt, sizeof(dword));
    WRITE_DWORD_NET(dest, value);
    return dest + sizeof(dword);
}


inline byte * ExtendedHeader::WriteDetailData(byte * dest, extender_header_item_type_t dt, ulong64 value)
{
    dest = this->WriteDetailDataHeader(dest, dt, sizeof(ulong64));
    WRITE_QUAD_NET(dest, value);
    return dest + sizeof(ulong64);
}


inline byte * ExtendedHeader::ReadDetailDataHeader(byte * buffer, extender_header_item_type_t &outType, word & outLen)
{
    outType = extender_header_item_type_t(*buffer);
    ++buffer;
    outLen = READ_WORD_NET(buffer);
    return buffer + sizeof(word);
}


void ExtendedHeader::Encode(dword connectionID, byte * buffer)
{
    byte * curPos;
    if (buffer == nullptr) {
        this->releaseBuffer = true;
        this->buffer = static_cast<byte *>(malloc(this->headerLength + sizeof(dword)));
    }
    else this->buffer = buffer;

    if (this->headerLength > 0) {
        curPos = this->buffer;
        WRITE_DWORD_NET(curPos, (dword)this->headerLength);
        curPos += sizeof(dword);

	    if (this->requestPath != nullptr) curPos = this->WriteDetailData(curPos, DDT_REQUEST_PATH, static_cast<word>(this->requestPathLen + 1), this->requestPath);
	    if (this->registeredPathForRequest != nullptr) curPos = this->WriteDetailData(curPos, DDT_REGISTERED_PATH_FOR_REQUEST, static_cast<word>(this->registeredPathForRequestLen + 1), this->registeredPathForRequest);
	    if (this->etag != nullptr) curPos = this->WriteDetailData(curPos, DDT_ETAG, static_cast<word>(this->etagLen + 1), this->etag);
	    if (this->fieldListBuffer != nullptr) curPos = this->WriteDetailData(curPos, DDT_HEADER_FIELDS, static_cast<word>(this->fieldListBufferSize), this->fieldListBuffer);
	    if (this->pathToRegister != nullptr) curPos = this->WriteDetailData(curPos, DDT_PATH_TO_REGISTER, static_cast<word>(this->pathToRegisterLen + 1), this->pathToRegister);
	    if (this->authUser != nullptr) curPos = this->WriteDetailData(curPos, DDT_AUTH_USER, static_cast<word>(this->authUserLen + 1), this->authUser);
	    if (this->authPassword != nullptr) curPos = this->WriteDetailData(curPos, DDT_AUTH_PASSWORD, static_cast<word>(this->authPasswordLen + 1), this->authPassword);
	    if (this->hostLen > 0) curPos = this->WriteDetailData(curPos, DDT_HOST, static_cast<word>(this->hostLen + 1), this->host);
        if (this->connectionFlags.dwordVal != 0) curPos = this->WriteDetailData(curPos, DDT_CONNECTION_FLAGS, this->connectionFlags.dwordVal);
	    if (this->wsCloseReasonSet) curPos = this->WriteDetailData(curPos, DDT_WS_SHUTDOWN_REASON, static_cast<dword>(this->wsCloseReason));
	    if (this->certHeader.certHostNameLen > 0) curPos = this->WriteDetailData(curPos, DDT_CERTIFICATE_HOST, static_cast<word>(this->certHeader.certHostNameLen + 1), this->certHeader.certHostName);
        if (this->certHeader.certPasswordLen > 0) curPos = this->WriteDetailData(curPos, DDT_CERTIFICATE_PASSWORD, static_cast<word>(this->certHeader.certPasswordLen + 1), this->certHeader.certPassword);
        if (this->certHeader.certSize > 0) curPos = this->WriteDetailData(curPos, DDT_CERTIFICATE_SIZE, this->certHeader.certSize);
	    if (this->redirectPathLen > 0) curPos = this->WriteDetailData(curPos, DDT_REDIRECT_PATH, static_cast<word>(this->redirectPathLen + 1), this->redirectPath);
        if (this->dynAuth) curPos = this->WriteDetailDataHeader(curPos, DDT_DYNAMIC_AUTHENTICATION, 0); // Only the header need to exist for flags
        if (this->dataSize > 0) curPos = this->WriteDetailData(curPos, DDT_DATA_SIZE, this->dataSize);
        if (this->chunkEncoded) curPos = this->WriteDetailDataHeader(curPos, DDT_CHUNK_ENCODED_TRANSFER, 0);
	    if (this->statusCode != 0) curPos = this->WriteDetailData(curPos, DDT_STATUS_CODE, this->statusCode);
	    if (this->statusDescription != nullptr) curPos = this->WriteDetailData(curPos, DDT_STATUS_DESCRIPTION, static_cast<word>(this->statusDescriptionLen + 1), this->statusDescription);
        if (this->remoteAddress != nullptr) {
            curPos = this->WriteDetailData(curPos, DDT_REMOTE_ADDRESS, static_cast<word>(this->remoteAddressLen + 1), this->remoteAddress);
            curPos = this->WriteDetailData(curPos, DDT_REMOTE_PORT, this->remotePort);
        }

        if (this->transferInfoSet) {
            if (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0) {
                curPos = this->WriteDetailDataHeader(curPos, DDT_TRANSFER_INFO, sizeof(word) * 2 + sizeof(dword));
            }
            else {
                curPos = this->WriteDetailDataHeader(curPos, DDT_TRANSFER_INFO, sizeof(word) * 2);
            }
	        WRITE_WORD_NET(curPos, static_cast<word>(this->transferInfo.resourceType));
            curPos += sizeof(word);
	        WRITE_WORD_NET(curPos, static_cast<word>(this->transferInfo.flags));
            curPos += sizeof(word);
            // Starting from protocol v1, the data size will be set in an extra item (see above). So only
            // for backward compatibility (protocol v0) write the data size - also only as 32bit value.
            if (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0) {
	            WRITE_DWORD_NET(curPos, static_cast<dword>(this->transferInfo.dataSize));
                curPos += sizeof(dword);
            }
        }

        if (this->rangeCount > 0) {
            if (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0) {
	            curPos = this->WriteDetailDataHeader(curPos, DDT_RANGES, sizeof(dword) + static_cast<word>(this->rangeCount * (sizeof(dword) * 2 + sizeof(byte))));
            }
            else {
	            curPos = this->WriteDetailDataHeader(curPos, DDT_RANGES, sizeof(dword) + static_cast<word>(this->rangeCount * (sizeof(ulong64) * 2 + sizeof(byte))));
            }
            WRITE_DWORD_NET(curPos, (dword)rangeCount);
            curPos += sizeof(dword);

            const HTTPRange * r = this->ranges;
            while (r) {
                *(byte *)curPos = (byte)(r->rangeType);
                curPos += sizeof(byte);
                if (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0) {
	                WRITE_DWORD_NET(curPos, static_cast<dword>(r->start));
                    curPos += sizeof(dword);
	                WRITE_DWORD_NET(curPos, static_cast<dword>(r->end));
                    curPos += sizeof(dword);
                }
                else {
                    WRITE_QUAD_NET(curPos, r->start);
                    curPos += sizeof(ulong64);
                    WRITE_QUAD_NET(curPos, r->end);
                    curPos += sizeof(ulong64);
                }
                r = r->next;
            }
        }
    }
    
    if (this->log) this->log->Log(LOG_WEBSERVER_PLUGIN, "ExtendedHeader::Encode(): Extended header encoded (cid: %u, h-len: %u, b-size: %u)", connectionID, dword(this->headerLength), dword(this->headerLength + sizeof(dword)));
}


void ExtendedHeader::Decode(dword connectionID, byte * buffer, size_t bufferSize)
{
    size_t dataHandled = 0;
    extender_header_item_type_t detailType;
    word dataLen;

    if (bufferSize > 0) {
        if (bufferSize < 4) {
            debug->printf("ExtendedHeader::Decode(): ERROR: size of buffer with data to decode must be at least 4 bytes, but only has %u bytes. (cid: %u)", bufferSize, connectionID);
            ASSERT(false, "ExtendedHeader::Decode(): bufferSize error");
        }

        byte * curBufPos = buffer;
        this->headerLength = READ_DWORD_NET(curBufPos);
        curBufPos += sizeof(dword);

        if (this->headerLength > bufferSize - sizeof(dword)) {
	        debug->printf("ExtendedHeader::Decode(): Header length exceeds buffer size (cid: %u, b-size: %u, h-len: %u)", connectionID, static_cast<dword>(bufferSize), static_cast<dword>(this->headerLength));
            debug->hexdump(buffer, bufferSize);
            ASSERT(false, "ExtendedHeader::Decode(): buffer overflow")
        }

        if (this->headerLength < bufferSize - sizeof(dword)) {
	        debug->printf("ExtendedHeader::Decode(): WARNING: Header length is less than buffer size (cid: %u, b-size: %u, h-len: %u) - possible data loss", connectionID, static_cast<dword>(bufferSize), static_cast<dword>(this->headerLength));
            debug->hexdump(buffer, bufferSize);
            ASSERT(false, "ExtendedHeader::Decode(): bufferSize <-> header size missmatch");
        }

        while (dataHandled < bufferSize - sizeof(dword)) {
            if (bufferSize - dataHandled < 3) {
	            debug->printf("ExtendedHeader::Decode(): ERROR: Bufferoverflow while parsing data (cid: %u, h-length: %u, b-size: %u, handled: %u)", connectionID, static_cast<dword>(this->headerLength), static_cast<dword>(bufferSize), static_cast<dword>(dataHandled));
                debug->hexdump(buffer, bufferSize);
                ASSERT(false, "ExtendedHeader::Decode(): buffer overflow")
            }

            curBufPos = this->ReadDetailDataHeader(curBufPos, detailType, dataLen);
            if (dataHandled + EXTENDED_HEADER_SIZE + dataLen > bufferSize) {
	            debug->printf("ExtendedHeader::Decode(): ERROR: data length of current item exceeds buffer size (cid: %u, h-len: %u, b-size: %u, handled: %u, data-len: %u)", connectionID, static_cast<dword>(this->headerLength), static_cast<dword>(bufferSize), static_cast<dword>(dataHandled), static_cast<dword>(dataLen));
                debug->hexdump(buffer, bufferSize);
                ASSERT(false, "ExtendedHeader::Decode(): buffer overflow")
            }

            switch (detailType) {
            case DDT_REQUEST_PATH:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_REQUEST_PATH.");
                this->requestPath = reinterpret_cast<char *>(curBufPos);
                this->requestPathLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_REGISTERED_PATH_FOR_REQUEST:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_REGISTERED_PATH_FOR_REQUEST.");
                this->registeredPathForRequest = reinterpret_cast<char *>(curBufPos);
                this->registeredPathForRequestLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_ETAG:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_ETAG.");
                this->etag = reinterpret_cast<char *>(curBufPos);
                this->etagLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_RANGES:
                this->rangeCount = READ_DWORD_NET(curBufPos);
                this->rangesBuffer = curBufPos + sizeof(dword);
                break;

            case DDT_CONNECTION_FLAGS:
                this->connectionFlags.dwordVal = READ_DWORD_NET(curBufPos);
                break;

            case DDT_HEADER_FIELDS:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_HEADER_FIELDS.");
                this->fieldListBuffer = reinterpret_cast<char *>(curBufPos);
                this->fieldListBufferSize = dataLen;
                this->releaseFieldListBuffer = false;
                break;

            case DDT_PATH_TO_REGISTER:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_PATH_TO_REGISTER.");
                this->pathToRegister = reinterpret_cast<char *>(curBufPos);
                this->pathToRegisterLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_AUTH_USER:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_AUTH_USER.");
                this->authUser = reinterpret_cast<char *>(curBufPos);
                this->authUserLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_AUTH_PASSWORD:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_AUTH_PASSWORD.");
                this->authPassword = reinterpret_cast<char *>(curBufPos);
                this->authPasswordLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_HOST:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_HOST.");
                this->host = reinterpret_cast<char *>(curBufPos);
                this->hostLen = dataLen - 1;
                break;

            case DDT_WS_SHUTDOWN_REASON:
                this->wsCloseReason = ws_close_reason_t(READ_DWORD_NET(curBufPos));
                break;

            case DDT_TRANSFER_INFO:
                this->transferInfo.resourceType = wsr_type_t(READ_WORD_NET(curBufPos));
                this->transferInfo.flags = wsr_flags_t(READ_WORD_NET(curBufPos + sizeof(word)));
                // The datasize will only be send with v0 protocol. Starting with v1, the data size will
                // be send with an extra item (see below).
                if (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0) {
                    this->transferInfo.dataSize = READ_DWORD_NET(curBufPos + sizeof(word) + sizeof(word));
                    if (this->transferInfo.dataSize == WS_RESPONSE_CHUNKED_V0) {
                        this->chunkEncoded = true;
                        this->transferInfo.dataSize = 0;
                    }
                }
                break;

            case DDT_CERTIFICATE_HOST:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_CERTIFICATE_HOST.");
                this->certHeader.certHostName = reinterpret_cast<char *>(curBufPos);
                this->certHeader.certHostNameLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_CERTIFICATE_PASSWORD:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_CERTIFICATE_PASSWORD.");
                this->certHeader.certPassword = reinterpret_cast<char *>(curBufPos);
                this->certHeader.certPasswordLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_CERTIFICATE_SIZE:
                this->certHeader.certSize = READ_DWORD_NET(curBufPos);
                break;

            case DDT_REDIRECT_PATH:
                ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_REDIRECT_PATH.");
                this->redirectPath = reinterpret_cast<char *>(curBufPos);
                this->redirectPathLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_DYNAMIC_AUTHENTICATION:
                this->dynAuth = true;
                break;

            case DDT_DATA_SIZE:
                this->dataSize = READ_QUAD_NET(curBufPos);
                // Set the dataSize for compatibility reasons.
                if (this->transferInfo.dataSize == 0) this->transferInfo.dataSize = this->dataSize;
                break;

            case DDT_CHUNK_ENCODED_TRANSFER:
                // The presence of the id alone activates the flag. No additional data available.
                this->chunkEncoded = true;
                break;
	            
            case DDT_STATUS_CODE:
	            this->statusCode = READ_DWORD_NET(curBufPos);
	            break;

            case DDT_STATUS_DESCRIPTION:
	            ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_STATUS_DESCRIPTION.");
	            this->statusDescription = reinterpret_cast<char *>(curBufPos);
	            this->statusDescriptionLen = dataLen - 1; // The closing zero is not part of the string len
	            break;

            case DDT_REMOTE_ADDRESS:
	            ASSERT(dataLen > 0, "ExtendedHeader::Decode(): dataLen must not be 0 for DDT_REMOTE_ADDRESS.");
                this->remoteAddress = reinterpret_cast<char *>(curBufPos);
	            this->remoteAddressLen = dataLen - 1; // The closing zero is not part of the string len
                break;

            case DDT_REMOTE_PORT:
                this->remotePort = READ_DWORD_NET(curBufPos);
                break;

        	default:
                // Nothing to do here - if (because of a never Webserver or IWebserverPlugin version) an detailType is unkown,
                // it will be ignored.
                break;
            }

            curBufPos += dataLen;
            dataHandled += dataLen + EXTENDED_HEADER_SIZE;
        }
    }
}


void ExtendedHeader::SetConnectionFlags(connection_flags_t & connectionFlags)
{
    if (connectionFlags.dwordVal != 0) {
        ASSERT(this->connectionFlags.dwordVal == 0, "ExtendedHeader::SetConnectionFlags() called multiple times.");
        this->connectionFlags.dwordVal = connectionFlags.dwordVal;
        this->headerLength += EXTENDED_HEADER_SIZE + sizeof(connection_flags_t);
    }
}


void ExtendedHeader::SetRequestPath(const char * path, size_t pathLen)
{
    if (path != nullptr) {
        ASSERT(this->requestPath == nullptr, "ExtendedHeader::SetRequestPath() called multiple times.");
        this->requestPath = const_cast<char *>(path);
        this->requestPathLen = pathLen;
        this->headerLength += EXTENDED_HEADER_SIZE + pathLen + 1;// For the closing 0 of the string
    }
}


void ExtendedHeader::SetRegisteredPathForRequest(const char * regPath, size_t regPathLen)
{
    if (regPath != nullptr) {
        ASSERT(this->registeredPathForRequest == nullptr, "ExtendedHeader::SetRegisteredPathForRequest() called multiple times.");
        this->registeredPathForRequest = const_cast<char *>(regPath);
        this->registeredPathForRequestLen = regPathLen;
        this->headerLength += EXTENDED_HEADER_SIZE + regPathLen + 1;  // For the closing 0 of the string
    }
}


void ExtendedHeader::SetETag(const char * etag)
{
    if (etag != nullptr) {
        ASSERT(this->etag == nullptr, "ExtendedHeader::SetETag() called multiple times.");
        this->etag = const_cast<char *>(etag);
        this->etagLen = strlen(etag);
        this->headerLength += EXTENDED_HEADER_SIZE + this->etagLen + 1;  // For the closing 0 of the string
    }
}


void ExtendedHeader::SetRequestRange(const HTTPRange * ranges, int rangeCount)
{
    if (ranges != nullptr) {
        size_t rangeFieldSize = (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0 ? sizeof(dword) : sizeof(ulong64));
        ASSERT(this->ranges == nullptr, "ExtendedHeader::SetRequestRange() called multiple times.");
        this->ranges = ranges;
        this->rangeCount = rangeCount;
        this->headerLength += EXTENDED_HEADER_SIZE + sizeof(dword) + this->rangeCount * (rangeFieldSize * 2 + sizeof(byte));
    }
}


void ExtendedHeader::SetHeaderFields(const HTTPParser * parser)
{
    if (parser != nullptr) {
        ASSERT(this->fieldListBuffer == nullptr, "ExtendedHeader::SetHeaderFields(HTTPParser *) called multiple times.");
        byte * theBuf;
        parser->GetFieldListCopy(theBuf, this->fieldListBufferSize);
        this->fieldListBuffer = reinterpret_cast<char *>(theBuf);
        if (this->fieldListBufferSize > 0) {
            this->releaseFieldListBuffer = true;
            this->headerLength += EXTENDED_HEADER_SIZE + this->fieldListBufferSize;
        }
    }
}


void ExtendedHeader::SetHeaderFields(const char * headerFieldBuffer, size_t headerFieldBufferSize)
{
    if (headerFieldBuffer != nullptr) {
        ASSERT(this->fieldListBuffer == nullptr, "ExtendedHeader::SetHeaderFields() called multiple times.");
        this->fieldListBuffer = const_cast<char *>(headerFieldBuffer);
        this->fieldListBufferSize = headerFieldBufferSize;
        this->releaseFieldListBuffer = false;
        this->headerLength += EXTENDED_HEADER_SIZE + headerFieldBufferSize;
    }
}


void ExtendedHeader::SetHost(const char * host, size_t hostLen)
{
    if (host != nullptr) {
        ASSERT(this->host == nullptr, "ExtendedHeader::SetHost() called multiple times.");
        this->host = const_cast<char *>(host);
        this->hostLen = hostLen;
        this->headerLength += EXTENDED_HEADER_SIZE + hostLen + 1; // For the closing 0 of the string
    }
}


void ExtendedHeader::SetPathToRegister(const char * path)
{
    if (path != nullptr) {
        ASSERT(this->pathToRegister == nullptr, "ExtendedHeader::SetPathToRegister() called multiple times.");
        this->pathToRegister = const_cast<char *>(path);
        this->pathToRegisterLen = strlen(path);
        this->headerLength += EXTENDED_HEADER_SIZE + this->pathToRegisterLen + 1; // For the closing 0 of the string
    }
}


void ExtendedHeader::SetAuthInfos(const char * authUser, const char * authPassword)
{
    if (authUser != nullptr) {
        ASSERT(this->authUser == nullptr, "ExtendedHeader::SetAuthInfos() called multiple times with authUser != nullptr.");
        this->authUser = const_cast<char *>(authUser);
        this->authUserLen = strlen(authUser);
        this->headerLength += EXTENDED_HEADER_SIZE + this->authUserLen + 1; // For the closing 0 of the string
    }

    if (authPassword != nullptr) {
        ASSERT(this->authPassword == nullptr, "ExtendedHeader::SetAuthInfos() called multiple times with authPassword != nullptr.");
        this->authPassword = const_cast<char *>(authPassword);
        this->authPasswordLen = strlen(authPassword);
        this->headerLength += EXTENDED_HEADER_SIZE + this->authPasswordLen + 1; // For the closing 0 of the string
    }
}


void ExtendedHeader::SetWebsocketCloseReason(ws_close_reason_t closeReason)
{
    ASSERT(this->wsCloseReasonSet == false, "ExtendedHeader::SetWebsocketCloseReason() called multiple times.");
    this->wsCloseReason = closeReason;
    this->wsCloseReasonSet = true;
    this->headerLength += EXTENDED_HEADER_SIZE + sizeof(dword);
}


void ExtendedHeader::SetTransferInfo(wsr_type_t resourceType, wsr_flags_t flags, ulong64 dataSize)
{
    ASSERT(this->transferInfoSet == false, "ExtendedHeader::SetTransferInfo() called multiple times.");
    this->transferInfoSet = true;
    this->transferInfo.resourceType = resourceType;
    this->transferInfo.flags = flags;
    this->headerLength += EXTENDED_HEADER_SIZE + sizeof(word) + sizeof(word);

    // Starting with protocol version 1, then way how the data will be transfered had been changed.
    // The difference now: the dataSize will be send in a seperate field and for chunk encoded
    // transfer a flag exists now. So the seperation will be done here as well as on the side
    // when reading the data.
    if (this->protocolVersion == WEBSERVER_PROTOCOL_VERSION_0) {
        this->transferInfo.dataSize = dword(dataSize); // Protocol v0 uses 32bit values.
        this->headerLength += sizeof(dword);
    }
    else {
        if (dataSize == WS_RESPONSE_CHUNKED) this->SetChunkEncodedTransfer(true);
        else this->SetDataSize(dataSize);
    }
}


void ExtendedHeader::SetCertificateHeader(const char * certHost, const char * certPassword, dword certSize)
{
    if (certHost != nullptr && *certHost != '\0') {
        ASSERT(this->certHeader.certHostName == nullptr, "ExtendedHeader::SetCertificateHeader() called multiple times with certHost != nullptr.");
        this->certHeader.certHostName = const_cast<char *>(certHost);
        this->certHeader.certHostNameLen = strlen(certHost);
        this->headerLength += EXTENDED_HEADER_SIZE + this->certHeader.certHostNameLen + 1; // For the closing 0 of the string
    }

    if (certPassword != nullptr && *certPassword != '\0') {
        ASSERT(this->certHeader.certPassword == nullptr, "ExtendedHeader::SetCertificateHeader() called multiple times with certPassword != nullptr.");
        this->certHeader.certPassword = const_cast<char *>(certPassword);
        this->certHeader.certPasswordLen = strlen(certPassword);
        this->headerLength += EXTENDED_HEADER_SIZE + this->certHeader.certPasswordLen + 1; // For the closing 0 of the string
    }

    if (certSize > 0) {
        ASSERT(this->certHeader.certSize == 0, "ExtendedHeader::SetCertificateHeader() called multiple times with certSize != nullptr.");
        this->certHeader.certSize = certSize;
        this->headerLength += EXTENDED_HEADER_SIZE + sizeof(dword);
    }
}


void ExtendedHeader::SetRedirectPath(const char * path)
{
    if (path != nullptr) {
        ASSERT(this->redirectPath == nullptr, "ExtendedHeader::SetRedirectPath() called multiple times.");
        this->redirectPath = const_cast<char *>(path);
        this->redirectPathLen = strlen(path);
        this->headerLength += EXTENDED_HEADER_SIZE + this->redirectPathLen + 1;
    }
}


void ExtendedHeader::SetDynamicAuthentication(bool dynAuth)
{
    if (dynAuth) {
        ASSERT(this->dynAuth == false, "ExtendedHeader::SetDynamicAuthentication() called multiple times.");
        this->dynAuth = dynAuth;
        this->headerLength += EXTENDED_HEADER_SIZE;
    }
}


void ExtendedHeader::SetDataSize(ulong64 dataSize)
{
    if (dataSize != 0) {
        ASSERT(this->dataSize == 0, "ExtendedHeader::SetDataSize() called multiple times.");
        this->dataSize = dataSize;
        this->headerLength += EXTENDED_HEADER_SIZE + sizeof(ulong64);
    }
}


void ExtendedHeader::SetChunkEncodedTransfer(bool chunkEncoded)
{
    if (chunkEncoded) {
        ASSERT(this->chunkEncoded == false, "ExtendedHeader::SetChunkEncodedTransfer() called multiple times.");
        this->chunkEncoded = chunkEncoded;
        this->headerLength += EXTENDED_HEADER_SIZE;
    }
}

void ExtendedHeader::SetStatusCode(dword responseCode)
{
	if (responseCode != 0) {
		ASSERT(this->statusCode == 0, "ExtendedHeader::SetStatusCode() called multiple times.");
		this->statusCode = responseCode;
		this->headerLength += EXTENDED_HEADER_SIZE + sizeof(dword);
	}
}


void ExtendedHeader::SetStatusDescription(const char * statusDescription)
{
	if (statusDescription != nullptr) {
		ASSERT(this->statusDescription == nullptr, "ExtendedHeader::SetStatusDescription() called multiple times.");
		this->statusDescription = statusDescription;
		this->statusDescriptionLen = strlen(statusDescription);
		this->headerLength += EXTENDED_HEADER_SIZE + this->statusDescriptionLen + 1; // For the closing 0 of the string
	}
}

void ExtendedHeader::SetRemoteAddress(class ISocket * socket)
{
    if (socket != nullptr) {
        word port;
        socket->GetRemoteAddress(this->remoteAddressBuffer, sizeof(this->remoteAddressBuffer), &port);
        this->remoteAddress = this->remoteAddressBuffer;
        this->remoteAddressLen = strlen(this->remoteAddressBuffer);
        this->headerLength += EXTENDED_HEADER_SIZE + this->remoteAddressLen + 1; // For the closing 0 of the string
        this->remotePort = port;
        this->headerLength += EXTENDED_HEADER_SIZE + sizeof(dword);
    }
}
