
/*---------------------------------------------------------------------------*/
/* http_auth_digest.cpp                                                      */
/* copyright (c) innovaphone 2016                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#include <platform.h>

#include "sdk/common/interface/random.h"
#include "sdk/common/interface/time.h"

#include "sdk/common/lib/httplib.h"

#include "http_digest_host.h"
#include "http_auth_digest.h"


HTTPAuthDigest::HTTPAuthDigest()
{
    this->digestHost = nullptr;
    this->serverNonceReused = false;
    this->serverNonce = nullptr;
    this->rawString = nullptr;
    this->reqMethod = HTTPLib::HTTP_NONE;
    this->dynPassword = nullptr;
    this->dynUserName = nullptr;

    this->username = nullptr;
    this->realm = nullptr;
    this->nonce = nullptr;
    this->uri = nullptr;
    this->qop = nullptr;
    this->nc = nullptr;
    this->cnonce = nullptr;
    this->response = nullptr;
    this->opaque = nullptr;

    this->saveRequestBuffer = nullptr;
    this->saveRequestBufferSize = 0;
    this->saveParser = nullptr;
}


HTTPAuthDigest::~HTTPAuthDigest()
{
    free(rawString);
    free(dynUserName);
    free(dynPassword);
}


void HTTPAuthDigest::SetDigestHost(HTTPDigestHost * const dgHost)
{
    digestHost = dgHost;
}


void HTTPAuthDigest::SetDynAuthData(const char * dynUser, const char * dynPassword)
{
    free(this->dynUserName);
    free(this->dynPassword);

    this->dynUserName = _strdup(dynUser);
    this->dynPassword = _strdup(dynPassword);
}

void HTTPAuthDigest::ReadDigestNonce(digest_nonce_t & dn_out)
{
    if (!this->serverNonce) {
        dn_out.needStale = this->nonce && *this->nonce != '\0';
        dn_out.nonce = this->serverNonce = this->digestHost->CreateNonce();
        this->serverNonce->SetOwner(this);
    }
    else {
        dn_out.needStale = false;
        dn_out.nonce = this->serverNonce;
    }
}

bool HTTPAuthDigest::ParseAuthField(const HTTPParser * parser)
{
	const HTTPHeaderField * hf = parser->GetHeaderField("Authorization");
	return hf == nullptr ? false : this->ParseAuthField(hf->GetValue(0), parser->GetRequestMethod());
}


bool HTTPAuthDigest::ParseAuthField(const char * headerFieldData, HTTPLib::http_request_method_t reqMethod)
{
    if (headerFieldData == nullptr) return false;

	this->reqMethod = reqMethod;
    free(rawString);
    rawString = _strdup(headerFieldData);

    if (strncmp(headerFieldData, "Digest", 6) != 0) return false;

    bool cont = true;
    char * next = rawString + 6;
    while (*next == ' ') ++next;

    while (cont) {
        char * p = strchr(next, '=');
        if (!p) cont = false;
        else {
            char * keyWord = next;
            char * value = p + 1;
            *p++ = '\0';
            char * e;
            if (*p == '\"') {
                ++p;
                e = strchr(p, '\"');
            }
            else e = strchr(p, ',');

            if (e) {
                *e = '\0';
                next = e + 1;
                while (*next == ' ' || *next == ',') ++next;
            }

            // Remove leading and ending ", if there is any...
            if (*value == '\"') value++;
            size_t tmp = strlen(value);
            if (value[tmp - 1] == '\"') value[tmp - 1] = '\0';

            if (strcmp(keyWord, "username") == 0) username = value;
            else if (strcmp(keyWord, "realm") == 0) realm = value;
            else if (strcmp(keyWord, "nonce") == 0) nonce = value;
            else if (strcmp(keyWord, "uri") == 0) uri = value;
            else if (strcmp(keyWord, "qop") == 0) qop = value;
            else if (strcmp(keyWord, "nc") == 0) nc = value;
            else if (strcmp(keyWord, "cnonce") == 0) cnonce = value;
            else if (strcmp(keyWord, "response") == 0) response = value;
            else if (strcmp(keyWord, "opaque") == 0) opaque = value;
        }
    }

    if (!username || !realm || !nonce || !nc || !cnonce || !qop || !uri || !response) return false;

    if (this->digestHost) this->serverNonce = this->digestHost->GetNonceObj(nonce);
    if (this->serverNonce == nullptr) this->SetDynAuthData(nullptr, nullptr);
    else this->serverNonce->SetOwner(this);

    return true;
}


const char * HTTPAuthDigest::GetServerOpaque()
{
    return this->digestHost->GetServerOpaque();
}


bool HTTPAuthDigest::VerifyAuthentication(const char * user, const char * password, const char * forURI)
{
    // If the serverNonce and serverOpaque field's had not been set for now, the HTTPAuthDigest
    // object is new and also doesn't received the values from a HTTPDigestHost object. That
    // means that the authentification has to fail now.
    if (!serverNonce || !digestHost) return false;

    // Now we need to make sure, that what ever the browser had been sent will be the same as
    // what the server defined before.
    /*if (strcmp(this->digestHost->GetServerOpaque(), opaque) != 0)
        return false;*/

    // Finally check the nc value....
    dword ncVal = strtoul(nc, nullptr, 16);
    if (this->serverNonce->GetNonceCount() != ncVal - 1) {
        this->serverNonce->Invalidate();
        this->serverNonce = nullptr;
        this->SetDynAuthData(nullptr, nullptr);
        return false;
    }

    size_t bufSize = 69; // Base-Len = 2x32 (2x MD5) + 5 (because of ':')
    bufSize += strlen(nonce) + strlen(cnonce) + strlen(qop) + 8; // + 8 because of the 8 chars long nonce-count value
    size_t tmp = strlen(user) + strlen(realm) + strlen(password) + 2; // +2 = 2x ':'
    if (tmp > bufSize) bufSize += tmp - bufSize;

    // Well - while the maximum length of a request method theoretically is fixed (9 chars for PROPPATCH), we already
    // addad a custom request (SYSCLIENT). So just for the case that another method will be added further, and that
    // method is longer, we get the length of the string for the current request method. However, the time needed
    // to make a strlen() here can be ignored. The 1 added after that is for the ':'.
    const char * reqMethodStr = HTTPLib::GetHTTPRequestMethodName(reqMethod);
    tmp = strlen(forURI) + strlen(reqMethodStr) + 1; 
    if (tmp > bufSize) bufSize += tmp - bufSize;
    ++bufSize; // Add space for closing zero.

    // The response will be calculated with the following data:
    //
    //  HA1       = MD5(username:realm:password)
    //  HA2       = MD5(requestMethod:uri)
    //  response  = MD5(HA1:nonce:nc:cnonce:qop:HA2)
    char HA1[33];
    char HA2[33];
    char * buf = static_cast<char *>(malloc(bufSize));

    size_t len = _snprintf(buf, bufSize, "%s:%s:%s", user, realm, password);
    HTTPDigestHost::MakeMD5String((byte *)buf, len, HA1);

    len = _snprintf(buf, bufSize, "%s:%s", reqMethodStr, forURI);
    HTTPDigestHost::MakeMD5String((byte *)buf, len, HA2);

    len = _snprintf(buf, bufSize, "%s:%s:%s:%s:%s:%s", HA1, nonce, nc, cnonce, qop, HA2);
    HTTPDigestHost::MakeMD5String((byte *)buf, len, HA1);
    free(buf);

    bool result = strcmp(response, HA1) == 0;

    // Only increment the serverNonce, if everything is fine, or the asynchroneous authentication will always fail.
    if (result) this->serverNonce->IncNonceCount();
    else this->SetDynAuthData(nullptr, nullptr); // Invalidate the password - just in case...
    
    return result;
}


void HTTPAuthDigest::ServerNonceInvalidated(HTTPDigestNonce * nonce)
{
    // Don't remove the item here from this list, will be removed automatically...
    if (this->serverNonce == nonce) this->serverNonce = nullptr;
}
