/* Copyright © 2012-19 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" #include "twitter.h" #include "utility.h" OAuthParameters mir_twitter::BuildSignedOAuthParameters( const OAuthParameters& requestParameters, const std::wstring& url, const std::wstring& httpMethod, const OAuthParameters *postData, const std::wstring& consumerKey, const std::wstring& consumerSecret, const std::wstring& requestToken = L"", const std::wstring& requestTokenSecret = L"", const std::wstring& pin = L"") { wstring timestamp = OAuthCreateTimestamp(); wstring nonce = OAuthCreateNonce(); // create oauth requestParameters OAuthParameters oauthParameters; oauthParameters[L"oauth_timestamp"] = timestamp; oauthParameters[L"oauth_nonce"] = nonce; oauthParameters[L"oauth_version"] = L"1.0"; oauthParameters[L"oauth_signature_method"] = L"HMAC-SHA1"; oauthParameters[L"oauth_consumer_key"] = consumerKey; oauthParameters[L"oauth_callback"] = L"oob"; // add the request token if found if (!requestToken.empty()) oauthParameters[L"oauth_token"] = requestToken; // add the authorization pin if found if (!pin.empty()) { oauthParameters[L"oauth_verifier"] = pin; } // create a parameter list containing both oauth and original parameters // this will be used to create the parameter signature OAuthParameters allParameters = requestParameters; if (Compare(httpMethod, L"POST", false) && postData) allParameters.insert(postData->begin(), postData->end()); allParameters.insert(oauthParameters.begin(), oauthParameters.end()); // prepare a signature base, a carefully formatted string containing // all of the necessary information needed to generate a valid signature wstring normalUrl = OAuthNormalizeUrl(url); wstring normalizedParameters = OAuthNormalizeRequestParameters(allParameters); wstring signatureBase = OAuthConcatenateRequestElements(httpMethod, normalUrl, normalizedParameters); // obtain a signature and add it to header requestParameters wstring signature = OAuthCreateSignature(signatureBase, consumerSecret, requestTokenSecret); oauthParameters[L"oauth_signature"] = signature; return oauthParameters; } wstring mir_twitter::UrlGetQuery(const wstring& url) { map brokenURL = CrackURL(url); wstring query = brokenURL[L"extraInfo"]; wstring::size_type q = query.find_first_of(L'?'); if (q != wstring::npos) query = query.substr(q + 1); wstring::size_type h = query.find_first_of(L'#'); if (h != wstring::npos) query = query.substr(0, h); 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 twitter website wstring mir_twitter::OAuthWebRequestSubmit( const wstring& url, const wstring& httpMethod, const OAuthParameters *postData, const wstring& consumerKey, const wstring& consumerSecret, const wstring& oauthToken, const wstring& oauthTokenSecret, const wstring& pin) { wstring query = UrlGetQuery(url); OAuthParameters originalParameters = ParseQueryString(query); OAuthParameters oauthSignedParameters = BuildSignedOAuthParameters( originalParameters, url, httpMethod, postData, consumerKey, consumerSecret, oauthToken, oauthTokenSecret, pin); return OAuthWebRequestSubmit(oauthSignedParameters, url); } wstring mir_twitter::OAuthWebRequestSubmit(const OAuthParameters ¶meters, const wstring&) { wstring oauthHeader = L"OAuth "; for (auto &it : parameters) { if (it != *parameters.begin()) oauthHeader += L","; wstring pair; pair += it.first + L"=\"" + it.second + L"\""; oauthHeader += pair; } return oauthHeader; } // parameters must already be URL encoded before calling BuildQueryString std::wstring mir_twitter::BuildQueryString(const OAuthParameters ¶meters) { wstring query; for (auto &it : parameters) { if (it != *parameters.begin()) query += L"&"; wstring pair; pair += it.first + L"=" + it.second + L""; query += pair; } return query; } wstring mir_twitter::OAuthConcatenateRequestElements(const wstring& httpMethod, wstring url, const wstring& parameters) { wstring escapedUrl = UrlEncode(url); wstring escapedParameters = UrlEncode(parameters); wstring ret = httpMethod + L"&" + escapedUrl + L"&" + escapedParameters; return ret; } // CrackURL.. just basically pulls apart a url into a map of wstrings: // scheme, domain, port, path, extraInfo, explicitPort // explicitPort will be 0 or 1, 0 if there was no actual port in the url, // and 1 if it was explicitely specified. // eg "http://twitter.com/blah.htm" will give: // http, twitter.com, 80, blah.htm, "", 0 // "https://twitter.com:989/blah.htm?boom" will give: // https, twitter.com, 989, blah.htm?boom, ?boom, 1 map mir_twitter::CrackURL(wstring url) { wstring scheme1, domain1, port1, path1, extraInfo, explicitPort; vector urlToks, urlToks2, extraInfoToks; Split(url, urlToks, L':', false); scheme1 = urlToks[0]; if (urlToks.size() == 2) { // if there is only 1 ":" in the url if (Compare(scheme1, L"http", false)) { port1 = L"80"; } else { port1 = L"443"; } Split(urlToks[1], urlToks2, L'/', false); domain1 = urlToks2[0]; explicitPort = L"0"; } else if (urlToks.size() == 3) { // if there are 2 ":"s in the URL, ie a port is explicitly set domain1 = urlToks[1].substr(2, urlToks[1].size()); Split(urlToks[2], urlToks2, L'/', false); port1 = urlToks2[0]; explicitPort = L"1"; } else ppro_->debugLogW(L"**CRACK - not a proper URL? doesn't have a colon. URL is %s", url.c_str()); for (size_t i = 1; i < urlToks2.size(); ++i) { if (i > 1) { path1 += L"/"; } path1 += urlToks2[i]; } wstring::size_type foundHash = path1.find(L"#"); wstring::size_type foundQ = path1.find(L"?"); if ((foundHash != wstring::npos) || (foundQ != wstring::npos)) { // if we've found a # or a ?... if (foundHash == wstring::npos) { // if we didn't find a #, we must have found a ? extraInfo = path1.substr(foundQ); } else if (foundQ == wstring::npos) { // if we didn't find a ?, we must have found a # extraInfo = path1.substr(foundHash); } else { // we found both a # and a ?, whichever came first we grab the sub string from there if (foundQ < foundHash) { extraInfo = path1.substr(foundQ); } else { extraInfo = path1.substr(foundHash); } } } else { // we have no # or ? in the path... extraInfo = L""; } map result; result[L"scheme"] = scheme1; result[L"domain"] = domain1; result[L"port"] = port1; result[L"path"] = path1; result[L"extraInfo"] = extraInfo; result[L"explicitPort"] = explicitPort; return result; } wstring mir_twitter::OAuthNormalizeUrl(const wstring& url) { wstring normalUrl = url; map brokenURL = CrackURL(url); wchar_t port[10] = {}; // The port number must only be included if it is non-standard if (Compare(brokenURL[L"scheme"], L"http", false) && !(Compare(brokenURL[L"port"], L"80", false)) || (Compare(brokenURL[L"scheme"], L"https", false) && !(Compare(brokenURL[L"port"], L"443", false)))) { mir_snwprintf(port, _countof(port), L":%s", brokenURL[L"port"].c_str()); } // InternetCrackUrl includes ? and # elements in the path, // which we need to strip off wstring pathOnly = brokenURL[L"path"]; wstring::size_type q = pathOnly.find_first_of(L"#?"); if (q != wstring::npos) pathOnly = pathOnly.substr(0, q); return brokenURL[L"scheme"] + L"://" + brokenURL[L"domain"] + port + L"/" + pathOnly; } wstring mir_twitter::OAuthNormalizeRequestParameters(const OAuthParameters& requestParameters) { list sorted; for (OAuthParameters::const_iterator it = requestParameters.begin(); it != requestParameters.end(); ++it) { wstring param = it->first + L"=" + it->second; sorted.push_back(param); } sorted.sort(); wstring params; for (list::iterator it = sorted.begin(); it != sorted.end(); ++it) { if (params.size() > 0) params += L"&"; params += *it; } return params; } OAuthParameters mir_twitter::ParseQueryString(const wstring& url) { OAuthParameters ret; vector queryParams; Split(url, queryParams, L'&', false); for (size_t i = 0; i < queryParams.size(); ++i) { vector paramElements; Split(queryParams[i], paramElements, L'=', true); _ASSERTE(paramElements.size() == 2); if (paramElements.size() == 2) ret[paramElements[0]] = paramElements[1]; } return ret; } wstring mir_twitter::OAuthCreateNonce() { wchar_t ALPHANUMERIC[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; wstring nonce; for (int i = 0; i <= 16; ++i) nonce += ALPHANUMERIC[rand() % (_countof(ALPHANUMERIC) - 1)]; // don't count null terminator in array return nonce; } wstring mir_twitter::OAuthCreateTimestamp() { __time64_t utcNow; _time64(&utcNow); _ASSERTE(utcNow != -1); wchar_t buf[100] = {}; mir_snwprintf(buf, _countof(buf), L"%I64u", utcNow); return buf; } wstring mir_twitter::OAuthCreateSignature(const wstring& signatureBase, const wstring& consumerSecret, const wstring& requestTokenSecret) { // URL encode key elements wstring escapedConsumerSecret = UrlEncode(consumerSecret); wstring escapedTokenSecret = UrlEncode(requestTokenSecret); wstring key = escapedConsumerSecret + L"&" + escapedTokenSecret; string keyBytes = WideToUTF8(key); BYTE digest[MIR_SHA1_HASH_SIZE]; unsigned int len; string data = WideToUTF8(signatureBase); HMAC(EVP_sha1(), keyBytes.c_str(), keyBytes.size(), (PBYTE)data.c_str(), data.size(), digest, &len); ptrA encoded(mir_base64_encode(digest, sizeof(digest))); return UrlEncode((wchar_t*)_A2T(encoded)); }