/*
Copyright © 2012-24 Miranda NG team
Copyright © 2009 Jim Porter
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see .
*/
#include "stdafx.h"
static CMStringA OAuthNormalizeUrl(const CMStringA &url)
{
	CMStringA normalUrl = url;
	int idx = normalUrl.Find('#');
	if (idx != -1)
		normalUrl.Truncate(idx);
	idx = normalUrl.Find('?');
	if (idx != -1)
		normalUrl.Truncate(idx);
	return normalUrl;
}
static int CompareCMString(const CMStringA *p1, const CMStringA *p2)
{
	return mir_strcmp(p1->c_str(), p2->c_str());
}
static CMStringA OAuthNormalizeRequestParameters(const CMStringA &requestParameters, bool bShort)
{
	OBJLIST params(10);
	Split(requestParameters, params, '&');
	LIST sorted(params.getCount(), CompareCMString);
	for (auto &it : params)
		sorted.insert(it);
	CMStringA res;
	for (auto &it : sorted) {
		if (!res.IsEmpty())
			res.AppendChar(bShort ? '&' : ',');
		if (!bShort) {
			it->Replace("=", "=\"");
			it->AppendChar('\"');
		}
		res += *it;
	}
	return res;
}
CMStringA CTwitterProto::BuildSignedOAuthParameters(const CMStringA &request, const CMStringA &url, const char *httpMethod, const char *postData)
{
	CMStringA timestamp(FORMAT, "%lld", _time64(0));
	CMStringA nonce = OAuthCreateNonce();
	// create oauth requestParameters
	auto *req = new AsyncHttpRequest(!mir_strcmp(httpMethod, "GET") ? REQUEST_GET : REQUEST_POST, url);
	req << CHAR_PARAM("oauth_timestamp", timestamp) << CHAR_PARAM("oauth_nonce", nonce) << CHAR_PARAM("oauth_version", "1.0")
		<< CHAR_PARAM("oauth_signature_method", "HMAC-SHA1") << CHAR_PARAM("oauth_consumer_key", OAUTH_CONSUMER_KEY) << CHAR_PARAM("oauth_callback", "oob");
	// add the request token if found
	if (!m_szAccessToken.IsEmpty())
		req << CHAR_PARAM("oauth_token", m_szAccessToken);
	// add the authorization pin if found
	if (!m_szPin.IsEmpty())
		req << CHAR_PARAM("oauth_verifier", m_szPin);
	// create a parameter list containing both oauth and original parameters
	// this will be used to create the parameter signature
	if (!request.IsEmpty()) {
		req->m_szParam.AppendChar('&');
		req->m_szParam.Append(request);
	}
	if (!mir_strlen(postData)) {
		req->m_szParam.AppendChar('&');
		req->m_szParam.Append(postData);
	}
	// prepare a signature base, a carefully formatted string containing 
	// all of the necessary information needed to generate a valid signature
	CMStringA normalUrl = OAuthNormalizeUrl(url);
	CMStringA normalizedParameters = OAuthNormalizeRequestParameters(req->m_szParam, true);
	CMStringA signatureBase = CMStringA(httpMethod) + "&" + mir_urlEncode(normalUrl) + "&" + mir_urlEncode(normalizedParameters);
	// obtain a signature and add it to header requestParameters
	CMStringA signature = OAuthCreateSignature(signatureBase, OAUTH_CONSUMER_SECRET, m_szAccessTokenSecret);
	req << CHAR_PARAM("oauth_signature", signature);
	CMStringA ret = OAuthNormalizeRequestParameters(req->m_szParam, false);
	delete req;
	return ret;
}
CMStringA CTwitterProto::UrlGetQuery(const CMStringA &url)
{
	CMStringA query = url;
	int idx = query.Find('?');
	if (idx == -1)
		return "";
	query.Delete(0, idx+1);
	idx = query.Find('#');
	if (idx != -1)
		query.Truncate(idx);
	return query;
}
// OAuthWebRequest used for all OAuth related queries
//
// consumerKey and consumerSecret - must be provided for every call, they identify the application
// oauthToken and oauthTokenSecret - need to be provided for every call, except for the first token request before authorizing
// pin - only used during authorization, when the user enters the PIN they received from the CTwitterProto website
CMStringA CTwitterProto::OAuthWebRequestSubmit(const CMStringA &url, const char *httpMethod, const char *postData)
{
	CMStringA query = UrlGetQuery(url);
	CMStringA oauthSignedParameters = BuildSignedOAuthParameters(query, url, httpMethod, postData);
	oauthSignedParameters.Insert(0, "OAuth ");
	return oauthSignedParameters;
}
static char ALPHANUMERIC[62+1] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
CMStringA CTwitterProto::OAuthCreateNonce()
{
	CMStringA nonce;
	for (int i = 0; i <= 16; ++i)
		nonce.AppendChar(ALPHANUMERIC[rand() % 62]); 
	return nonce;
}
CMStringA CTwitterProto::OAuthCreateSignature(const CMStringA &signatureBase, const CMStringA &consumerSecret, const CMStringA &requestTokenSecret)
{
	// URL encode key elements
	CMStringA key = mir_urlEncode(consumerSecret) + "&" + mir_urlEncode(requestTokenSecret);
	uint8_t digest[MIR_SHA1_HASH_SIZE];
	unsigned int len;
	HMAC(EVP_sha1(), key.c_str(), (int)key.GetLength(), (uint8_t*)signatureBase.c_str(), signatureBase.GetLength(), digest, &len);
	return CMStringA(ptrA(mir_base64_encode(digest, sizeof(digest))));
}
/////////////////////////////////////////////////////////////////////////////////////////
void CTwitterProto::Oauth2RequestToken(MHttpResponse *pResp, AsyncHttpRequest *)
{
	if (pResp->resultCode != 200) {
		OnLoggedFail();
		return;
	}
	// StringPairs response = ParseQueryString(resp.data);
	// szOauthToken = response[L"oauth_token"];
	// szOauthTokenSecret = response[L"oauth_token_secret"];
	if (m_szAccessToken.IsEmpty()) {
		ShowPopup("OAuth token not received, check your internet connection?", 1);
		debugLogA("**NegotiateConnection - OAuth tokens not received, stopping before we open the web browser..");
		return;
	}
	// write those bitches to the db foe latta
	setString(TWITTER_KEY_OAUTH_TOK, m_szAccessToken);
	setString(TWITTER_KEY_OAUTH_TOK_SEC, m_szAccessTokenSecret);
}
void CTwitterProto::RequestOauthToken(const char *szPin)
{
	auto *pReq = new AsyncHttpRequest(REQUEST_POST, "/oauth2/token", &CTwitterProto::Oauth2RequestToken);
	pReq << CHAR_PARAM("grant_type", "authorization_code") << CHAR_PARAM("code_verifier", "zzzzzzz") << CHAR_PARAM("client_id", OAUTH_CONSUMER_KEY)
		<< CHAR_PARAM("callback", "https://miranda-ng.org/oauth") << CHAR_PARAM("code", szPin);
	Push(pReq);
}
/////////////////////////////////////////////////////////////////////////////////////////
void CTwitterProto::Oauth2RequestAuth(MHttpResponse *pResp, AsyncHttpRequest *)
{
	if (pResp->resultCode != 200) {
		OnLoggedFail();
		return;
	}
}
void CTwitterProto::RequestOauthAuth()
{
	Utils_GetRandom(code_verifier, sizeof(code_verifier));
	uint8_t hash[32];
	mir_sha256_hash(code_verifier, sizeof(code_verifier), hash);
	code_challenge = ptrA(mir_base64_encode(hash, sizeof(hash)));
	auto *pReq = new AsyncHttpRequest(REQUEST_PATCH, "https://twitter.com/i/oauth2/authorize", &CTwitterProto::Oauth2RequestAuth);
	pReq->flags |= NLHRF_REDIRECT;
	pReq << CHAR_PARAM("client_id", OAUTH_CONSUMER_KEY) << CHAR_PARAM("scope", "tweet.read tweet.write users.read offline.access")
		<< CHAR_PARAM("response_type", "code") << CHAR_PARAM("callback", "https://oauth.miranda-ng.org") << CHAR_PARAM("state", "state")
		<< CHAR_PARAM("code_challenge_method", "s256") << CHAR_PARAM("code_challenge", code_challenge);
	Push(pReq);
}