55119


C++を利用してTwitterクライアントプログラムを開発したいです。  
外部ライブラリはOpenSSL（暗号化）とcpprestsdk（HTTP通信）のみです。  
OAuth認証も自分で実装しようと思うのですが、うまくいきません。  
具体的にはシグネチャ？の作成とPOSTするデータ（パラメータ）の作成がうまく行っていないのかも...と思っています。

[Twicpps](https://soramimi.jp/twicpps/index.html)と[このサイト様](https://qiita.com/alpha_kai_NET/items/cdd3ec27a70a77e06c59)を参考にさせていただいているのですが、サイトだとHTTPリクエストヘッダに
_Authorization_
というヘッダを作り、そこにもOAuth認証情報を記載するとの事が書いてあるのですが、Twicppsのコードを見ているとそのようなことをしているようには見えません。  
パラメータの作成は行っていますが、urlとパラメータだけを乗っけてPOSTしているようなコードです。

~~以下のコード（.hpp）で通信はできるのですが、スターテスコード400で帰ってきて、レスポンスは以下のとおりです。~~
(訂正：ツイートはできましたが、Authorizationヘッダが必要な通信が実装できません)

[code]

    {"errors":[{"code":215,"message":"Bad Authentication data."}]}
    
[/code]

基本はTwicpps寄りで開発してきたのですが、 _Authorization_ をつけないと認証はできないようです。  
以下のコード全文を載せます。cppの方はmain関数からtweet関数を呼び出しているだけですので掲載しません。  
どこがおかしいのか指摘して頂けると光栄です。  
よろしくおねがいします。

OS:Windows10 Home  
IDE：Visual Studio 2019 Community  
OpenSSL:OpenSSL-Win32 1.0.2r  
cpprestsdk:cpprestsdk v141

[code]

    [Header.hpp]
    
    #pragma once
    
    #include <sstream>
    #include <fstream>
    #include <vector>
    #include <locale>
    #include <winstring.h>
    #include <algorithm>
    #include <iomanip>
    #include <queue>
    #include <time.h>
    #include <cpprest/http_client.h>
    #include <openssl/hmac.h>
    #include <openssl/ssl.h>
    
    #pragma comment(lib,"libeay32.lib") 
    
    using namespace std;
    using namespace web;
    using namespace web::http;
    using namespace web::http::client;
    
    #define REST_POST_PATH "C:\\ProgramData\\System72\\CAIOS\\CAIOS\\REST_POST_RESPONSE.json"
    #define REST_GET_PATH "C:\\ProgramData\\System72\\CAIOS\\CAIOS\\REST_GET_RESPONSE.json"
    #define TWITTER_URL "https://api.twitter.com/1.1/statuses/update.json"
    
    typedef enum {
        POST,
        GET
    } METHOD;
    
    enum CodePageID : unsigned int {
        ANSI = CP_ACP,  // ANSI
        OEM = CP_OEMCP, // OEM(依存)
        MAC = CP_MACCP, // MAC
        UTF7 = CP_UTF7, // UTF-7
        UTF8 = CP_UTF8  // UTF-8
    };
    
    struct Request {
        string url;
        string post;
    };
    
    struct TwitterAPI_Keys {
        string Consumer_Key = "ここにキーを代入";
        string Consumer_Sec = "ここにキーを代入";
        string Accesstoken = "ここにキーを代入";
        string Accesstoken_Aec = "ここにキーを代入";
    };
    
    namespace CAIOS {
    
        namespace String {
    
            static string UTF8_to_SJIS(string message) {
                int n;
                wchar_t ucs2[1000];
                char utf8[1000];
                n = MultiByteToWideChar(CP_UTF8, 0, message.c_str(), message.size(), ucs2, 1000);
                n = WideCharToMultiByte(CP_ACP, 0, ucs2, n, utf8, 1000, 0, 0);
                return std::string(utf8, n);
            }
    
            static string SJIS_to_UTF8(std::string const& message)      {
                int n;
                wchar_t ucs2[1000];
                char utf8[1000];
                n = MultiByteToWideChar(CP_ACP, 0, message.c_str(), message.size(), ucs2, 1000);
                n = WideCharToMultiByte(CP_UTF8, 0, ucs2, n, utf8, 1000, 0, 0);
                return std::string(utf8, n);
            }
    
            // string から wstring 変換
            static wstring StringToWString(const string& refSrc, unsigned int codePage = CodePageID::ANSI) {
                vector<wchar_t> buffer(MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, nullptr, 0));
                MultiByteToWideChar(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size());
                return wstring(buffer.begin(), buffer.end());
            }
    
            // wstring から string 変換
            static string WStringToString(const wstring& refSrc, unsigned int codePage = CodePageID::OEM) {
                vector<char> buffer(WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, nullptr, 0, nullptr, nullptr));
                WideCharToMultiByte(codePage, 0, refSrc.c_str(), -1, &buffer.front(), buffer.size(), nullptr, nullptr);
                return string(buffer.begin(), buffer.end());
            }
    
            static string EraseString(string str, string erase) {
                for (size_t c = str.find_first_of(erase); c != string::npos; c = c = str.find_first_of(erase)) {
                    str.erase(c, 1);
                }
                return str;
            }
        }
    
        namespace REST {
    
            static string GET(Request req) {
                try {
                    using namespace CAIOS::String;
    
                    http_client client(StringToWString(req.url));
    
                    cout << " -> HTTP request mode [POST]" << endl;
                    cout << " -> HTTP request to " << req.url << endl;
    
                    auto response = client.request(methods::GET).get();
                    auto str = response.extract_string();
    
                    cout << " -> Server returned returned status code " << response.status_code() << '.' << endl;
                    cout << " -> Content length is " << response.headers().content_length() << " bytes.\n" << endl;
    
                    ofstream ofs(REST_POST_PATH);
                    ofs << WStringToString(str.get().c_str()) << endl;
                    ofs.close();
    
                    return WStringToString(str.get().c_str());
                }
                catch (...) {
                    cout << "HTTP通信中に例外が発生しました　[GET]" << endl;
                }
            }
    
            static string POST(Request req) {
                try {
                    using namespace CAIOS::String;
    
                    http_client client(StringToWString(req.url));
    
                    http_request request(methods::POST);
    
                    cout << " -> HTTP request mode [POST]" << endl;
                    cout << " -> HTTP request to " << req.url << endl;
    
                    char body[3000];
                    sprintf_s(body, 3000, "Authorization: OAuth %s", req.post);
    
                    request.headers().add(L"Content-Type", L"application/x-www-form-urlencoded");
                    request.set_body((wchar_t*)body);
    
                    auto response = client.request(request).get();
                    auto str = response.extract_string();
    
                    cout << " -> Server returned returned status code " << response.status_code() << '.' << endl;
                    cout << " -> Content length is " << response.headers().content_length() << " bytes.\n" << endl;
    
                    ofstream ofs(REST_POST_PATH);
                    ofs << WStringToString(str.get().c_str()) << endl;
                    ofs.close();
    
                    return WStringToString(str.get().c_str());
                }
                catch (...) {
                    cout << "HTTP通信中に例外が発生しました　[POST]" << endl;
                }
            }
    
            static string URL_encode(string str) {
                const int NUM_BEGIN_UTF8 = 48;
                const int CAPITAL_BEGIN_UTF8 = 65;
                const int LOWER_BEGIN_UTF8 = 97;
    
                int charCode = -1;
                string encoded;
                stringstream out;
    
                for (int i = 0; str[i] != 0; i++) {
                    charCode = (int)(unsigned char)str[i];
    
                    //エンコードする必要の無い文字の判定
                    if ((NUM_BEGIN_UTF8 <= charCode && charCode <= NUM_BEGIN_UTF8 + 9)
                        || (CAPITAL_BEGIN_UTF8 <= charCode && charCode <= CAPITAL_BEGIN_UTF8 + 25)
                        || (LOWER_BEGIN_UTF8 <= charCode && charCode <= LOWER_BEGIN_UTF8 + 25)
                        || str[i] == '.' || str[i] == '_' || str[i] == '-' || str[i] == '~'){
                        out << str[i];
                    }
                    else {
                        out << '%' << hex << uppercase << charCode;
                    }
                }
                encoded = out.str();
                return encoded;
            }
        }
    
        namespace Twitter {
            namespace OAuth {
                static string CreateData(vector<string>const OAuth, METHOD method, int Start) {
                    string query;
                    for (int t = Start; t < OAuth.size(); t++) {
                        if (t != Start)query += "&";
                        query += OAuth[t];
                    }
                    return query;
                }
    
                static int split_url(const string url, vector<string>* OAuth) {
                    int num = url.find_first_of('?');
                    OAuth->push_back(url.substr(0, num));
                    OAuth->push_back(url.substr(num + 1));
                    return 0;
                }
    
                static string sha1(const string Key, const string Data) {
                    char* key = (char*)Key.c_str();
                    char* data = (char*)Data.c_str();
                    unsigned char res[SHA_DIGEST_LENGTH + 1];
                    size_t reslen;
    
                    HMAC(EVP_sha1(), key, strlen(key), reinterpret_cast<const unsigned char*>(data), strlen(data), res, &reslen);
    
                    return string(reinterpret_cast<char*>(res), reslen);
                }
    
                static int encode_base64(char* bufin, int len, char* bufout){
                    static unsigned char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
                    unsigned char* pin = (unsigned char*)bufin;
                    unsigned char* pout = (unsigned char*)bufout;
    
                    for (int i = 0; i < len - 2; i += 3){
                        *pout++ = base64[pin[0] >> 2];
                        *pout++ = base64[0x3F & ((pin[0] << 4) | (pin[1] >> 4))];
                        *pout++ = base64[0x3F & ((pin[1] << 2) | (pin[2] >> 6))];
                        *pout++ = base64[0x3F & pin[2]];
                        pin += 3;
                    }
                    if (len % 3 == 1){
                        *pout++ = base64[pin[0] >> 2];
                        *pout++ = base64[0x3F & (pin[0] << 4)];
                        *pout++ = '=';
                        *pout++ = '=';
                    }
                    else if (len % 3 == 2){
                        *pout++ = base64[pin[0] >> 2];
                        *pout++ = base64[0x3F & ((pin[0] << 4) | (pin[1] >> 4))];
                        *pout++ = base64[0x3F & (pin[1] << 2)];
                        *pout++ = '=';
                    }
                    *pout = '\0';
                    return pout - (unsigned char*)bufout;
                }
    
                static string CreateSignature(string ConsumerSecret, string AccessSecret, vector<string>const& OAuth, METHOD method, int Start = 1) {
                    string str, key, data, methods;
                    char out[256];
    
                    if (method == POST)methods = "POST";
                    else methods = "GET";
    
                    key = ConsumerSecret + "&" + AccessSecret;
    
                    data = methods + "&" + CAIOS::REST::URL_encode(OAuth[0]) + "&" + CreateData(OAuth, method, Start);
    
                    str = sha1(key, data);
    
                    CAIOS::Twitter::OAuth::encode_base64((char*)str.c_str(), sizeof(str) - 1, out);
    
                    return out;
                }
    
                static int IntOAuthParams(vector<string>* OAuth, METHOD method) {
                    TwitterAPI_Keys key;
    
                    auto CreateNonce = []() {
                        static const char* chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
                        const unsigned int max = 26 + 26 + 10 + 1;
                        char tmp[50];
                        srand((unsigned int)time(0));
                        int len = 15 + rand() % 16;
                        for (int i = 0; i < len; i++) {
                            tmp[i] = chars[rand() % max];
                        }
                        return std::string(tmp, len);
                    };
    
                    string oauth_nonce = "oauth_nonce";
                    oauth_nonce += "=";
                    oauth_nonce += CreateNonce();
                    OAuth->push_back(oauth_nonce);
    
                    string oauth_timestamp = "oauth_timestamp";
                    oauth_timestamp += "=";
                    oauth_timestamp += to_string((int)time(nullptr));
                    OAuth->push_back(oauth_timestamp);
    
                    string oauth_token = "oauth_token";
                    oauth_token += "=";
                    oauth_token += key.Accesstoken;
                    OAuth->push_back(oauth_token);
    
                    string oauth_consumer_key = "oauth_consumer_key";
                    oauth_consumer_key += "=";
                    oauth_consumer_key += key.Consumer_Key;
                    OAuth->push_back(oauth_consumer_key);
    
                    string oauth_signature_method = "oauth_signature_method";
                    oauth_signature_method += "=";
                    oauth_signature_method += "HMAC-SHA1";
                    OAuth->push_back(oauth_signature_method);
    
                    string oauth_version = "oauth_version";
                    oauth_version += "=";
                    oauth_version += "1.0";
                    OAuth->push_back(oauth_version);
    
                    sort(OAuth->begin() + 1, OAuth->end());
    
                    string oauth_signature = "oauth_signature";
                    oauth_signature += "=";
                    oauth_signature += CreateSignature(key.Consumer_Sec, key.Accesstoken_Aec, *OAuth, method);
                    OAuth->push_back(oauth_signature);
    
                    return 0;
                }
    
                static Request OAuthAuthentication(string url, METHOD method) {
                    vector<string> OAuth;
    
                    split_url(url, &OAuth);
    
                    CAIOS::Twitter::OAuth::IntOAuthParams(&OAuth, method);
    
                    Request req;
    
                    if (method == POST) {
                        req.url = OAuth[0];
                        req.post = CAIOS::Twitter::OAuth::CreateData(OAuth, method, 1);
                    }
                    else {
                        req.url = OAuth[0];
                        req.post = CAIOS::Twitter::OAuth::CreateData(OAuth, method, 1);
                    }
    
                    return req;
                }
            }
    
            static string tweet(string message) {
                message = CAIOS::String::SJIS_to_UTF8(message);
    
                string url = TWITTER_URL;
                url += "?status=" + CAIOS::REST::URL_encode(message);
    
                Request req = OAuth::OAuthAuthentication(url, POST);
                return CAIOS::REST::POST(req);
            }
        }
    }
    
[/code]

**補足**  
Authorizationヘッダが必要な場合、どのようにヘッダをつければいいのでしょうか。TwitterではAuthorizationヘッダが必要でない場合と必要な場合？の２つがあるようですが、このときは必要。このときは不要などのラインを教えてくださるとありがたいです。


CC BY-SA 4.0
2019-05-20T09:03:44.670
55111
2019-05-23T07:53:32.343
2019-05-23T07:53:32.343
question
c++ twitter visual-c++
C++でのTwitterクライアントプログラムのOAuth認証


HMAC-SHA1署名する対象のデータは、

[code]

    POST&https%3A%2F%2Fapi.twitter.com%2F1.1%2Fstatuses%2Fupdate.json&oauth_consumer_key=XXXXXX（略）
    
[/code]

のような文字列になりますが、ここが少し違います。

「`oauth_consumer_key=XXXXXX`」この部分はURLエンコードされている必要があります。正しくは、「`oauth_consumer_key%3DXXXXXX`」とします。

そのため、`CreateSignature`関数で`data`を組み立てる処理を修正します。

【修正前】

[code]

    data = methods + "&" + CAIOS::REST::URL_encode(OAuth[0]) + "&" + CreateData(OAuth, method, Start);
    
[/code]

【修正後】

[code]

    data = methods + "&" + CAIOS::REST::URL_encode(OAuth[0]) + "&" + CAIOS::REST::URL_encode(CreateData(OAuth, method, Start));
    
[/code]

* * *

`std::string`オブジェクトに対して`sizeof()`は間違いです。`size()`関数で長さを取得します。（ちなみに、この値は常に20のはずです。）

【修正前】

[code]

    CAIOS::Twitter::OAuth::encode_base64((char*)str.c_str(), sizeof(str) - 1, out);
    
[/code]

【修正後】

[code]

    CAIOS::Twitter::OAuth::encode_base64((char*)str.c_str(), str.size(), out);
    
[/code]

* * *

HMAC-SHA1署名後のBASE64文字列をURLエンコードする必要があります。

【修正前】

[code]

    return out;
    
[/code]

【修正後】

[code]

    return CAIOS::REST::URL_encode(out);
    
[/code]

* * *

以上の修正でデータは正しくなると思いますが、気になる点を。

HMAC-SHA1署名する関数で、「`HMAC(EVP_sha1(),
～～～（略）`」となっているところ、この関数は、20バイトのバイナリデータを返します。そのすぐ下で「`return
string(reinterpret_cast<char*>(res),
reslen);`」となっていますが、バイナリデータを`std::string`に格納するのは良くないです。`char
*out`や`std::vector<char> *out`などに格納するか、この関数内でBASE64化した文字列を作って返すのがいいでしょう。

あと、Authorizationヘッダについてですが、テキストのツイート（`x-www-form-
urlencoded`でPOST）するには、このヘッダはいりません。画像などをアップロード（`multipart/form-
data`でPOST）する際Authorizationヘッダが必要になります。

* * *

【追記】（画像アップロード機能）

メディア（画像など）添付のツイートをするには、先にファイルをアップロードします。

拙作twicppsでは、 <https://github.com/soramimi/twicpps/blob/master/src/tweet.cpp>
の92行目以降でファイルのPOSTの処理を行っています。APIは`https://upload.twitter.com/1.1/media/upload.json`です。前述の通り`multipart/form-
data`でのPOSTです。アップロードに成功するとJSONデータが返りますので、そこから`media_id`の値を取り出します。

アップロードが完了したら、本文のツイートですが、その際に`media_ids`というパラメータを追加してPOSTします。

私のプログラムではJSONパーサまで自作していますが、代わりにpicojsonなどを利用してみるのもいいかと思います。


CC BY-SA 4.0
2019-05-20T14:57:50.177
55119
2019-05-21T01:11:43.883
2019-05-21T01:11:43.883
3337
3337
55111
answer


あまりに迷走しているようなので、cpprestsdkだけを使ってTwitter Timelineを取得するサンプルを書いてみました。参考にしてください。

Visual
Studioを使用している場合は、プロジェクトにNugetパッケージ[cpprestsdk](https://www.nuget.org/packages/cpprestsdk)を追加するだけです。（OpenSSLやboostは必要ありません。）

[code]

    #include <locale>
    #include <cpprest/http_client.h>
    #include <cpprest/oauth1.h>
    
    static constexpr auto
        apikey = U(""),
        apisecretkey = U(""),
        accesstoken = U(""),
        accesstokensecret = U("");
    
    int wmain() {
        // コンソールに日本語を表示するための設定
        _wsetlocale(LC_ALL, L"");
    
        web::http::oauth1::experimental::oauth1_config oauth1_config{
            apikey,
            apisecretkey,
            U("https://api.twitter.com/oauth/request_token"),
            U("https://api.twitter.com/oauth/authorize"),
            U("https://api.twitter.com/oauth/access_token"),
            U(""),
            web::http::oauth1::experimental::oauth1_methods::hmac_sha1
        };
        oauth1_config.set_token({ accesstoken, accesstokensecret });
        web::http::client::http_client_config http_client_config;
        http_client_config.set_oauth1(oauth1_config);
        web::http::client::http_client api{ U("https://api.twitter.com/1.1/"), http_client_config };
    
        auto timeline = api.request(web::http::methods::GET, U("statuses/home_timeline.json")).get().extract_json().get();
        for (auto const& status : timeline.as_array())
            ucout << status.as_object().at(U("text")).as_string() << std::endl;
    
        return 0;
    }
    
[/code]


CC BY-SA 4.0
2019-05-23T00:01:36.083
55180
2019-05-23T00:01:36.083
4236
55111
answer