C++とlibcurl(要OpenSSL)でTwitterのOAuth認証する

他の言語で実装されてるから移植するだけで簡単だと思ってた

普通に書いたらHTTP 100-Continueが表示された

POST 時にサーバがデータを受け入れ可能か確認するための一時応答。
(中略)
Android やら .net framework のライブラリがデフォルトで 100 を使う設定な一方、Twitter が対応していなかったのでこれに引っかかった人多数。
Twitter 相手に POST するときは無効にしておきましょう。

HTTP 100-continue - ..たれろぐ..

とりあえず無効にすることで回避できたと思った
chunk = curl_slist_append(chunk, "Expect:");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);

いくら待ってもレスポンスが返ってこない(´・ω・`)

何かコードが間違っているんじゃないかと小一時間悩んだが、わからなかった
そこで最初に戻ってHTTP 100-Continueが出た理由を調べた
(今思うと、最初にしておくべきだったと後悔)


これによるとcurlでは1024バイト以上のデータを送信する場合に「Expect: 100-continue」ヘッダを自動で付与するらしい(ただしこれはcurl-7.13以降の場合)*1。

curlでPOST送信する場合に注意しておいた方がいいこと(続・CentOS4系と5系のcurlの違い) - tokuhy’s fraction

1024バイト以上を送るときに勝手につけるらしい
でも送信データはそんなに大きくない……
いろいろ悩んだ結果、ライブラリのバグを疑いはじめていたが、さすがにないと思って
とりあえず公式のExample Resultを見ていたら、POST BODYが空なことに今更気付く


Request URL:
POST https://api.twitter.com/oauth/request_token

Request POST Body:
N/A

Authorization Header:
OAuth oauth_nonce="K7ny27JTpKVsTgdyLdDfmQQWVLERj2zAK5BslRsqyw", oauth_callback="http%3A%2F%2Fmyapp.com%3A3005%2Ftwitter%2Fprocess_callback", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1300228849", oauth_consumer_key="OqEqJeafRSF11jBMStrZz", oauth_signature="Pc%2BMLdv028fxCErFyi8KXFM%2BddU%3D", oauth_version="1.0"

POST oauth/request_token | Twitter Developers

そこで明示的にpostデータのサイズを0にしてみたらレスポンスが返ってくるようになった

libcurl公式にきちんと書かれてたけど、0バイト送るときCURLOPT_POSTFIELDS使わないし……

curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);


とりあえずoauth_tokenが取得できたのでよかった


参考文献(上記引用元は省略):
OAuthをPHPでイチから書いてみた(その1) | PRESSMAN*TechPRESSMAN*Tech
JavaでTwitterのOAuthを書いてみました - n3104の日記

サンプル

https://api.twitter.com/oauth/request_tokenにPOSTしてトークンを取得

/*VSの場合のみ。そのほかは適宜ライブラリを追加
  また、ファイルパスは要変更                     */
#ifdef _MSC_VER 
#ifndef NDEBUG
#pragma comment(lib,"curl/lib/libcurld.lib")
#pragma comment(lib,"ssleay32MTd.lib")
#pragma comment(lib,"libeay32MTd.lib")
#else
#pragma comment(lib,"curl/lib/libcurl.lib")
#pragma comment(lib,"ssleay32MT.lib")
#pragma comment(lib,"libeay32MT.lib")
#endif
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"wldap32.lib")
#pragma comment(lib,"Crypt32.Lib")
#endif

#include<string>
#include<map>

#include<curl\curl.h>
#include<openssl\ssl.h>

typedef std::map<std::string, std::string> Param;
std::string BuildHttpQuery(const Param &param, char join_key_and_value = '=',
                           char arg_separator = '&');


std::string HmacSha1(const char* data, const char *key) {
  unsigned char res[SHA_DIGEST_LENGTH + 1];
  size_t res_len;
  size_t key_len = strlen(key);
  size_t data_len = strlen(data);
  HMAC(EVP_sha1(), key, key_len, reinterpret_cast<const unsigned char*>(data), data_len, res, &res_len);
  return std::string(reinterpret_cast<char*>(res), res_len);
}

std::string Base64Encode(const char *src) {
  BIO *encoder = BIO_new(BIO_f_base64());
  BIO *bmem = BIO_new(BIO_s_mem());
  encoder = BIO_push(encoder, bmem);
  BIO_write(encoder, src, strlen(src));
  BIO_flush(encoder);
  BUF_MEM *bptr;
  BIO_get_mem_ptr(encoder, &bptr);
  std::string buf(bptr->data, bptr->length - 1);
  BIO_free_all(encoder);
  return buf;
}

std::string UrlEncode(const char *url) {
  char *p = curl_escape(url, 0);
  std::string ret(p);
  curl_free(p);
  return ret;
}
std::string CreateRondomString(int length) {
  std::string str;
  static const char *m = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  for (int i = 0; i < length; i++) {
    str += m[rand() % (sizeof(m) - 1)];
  }
  return str;
}
std::string BuildHttpQuery(const Param &param, char join_key_and_value,
                           char arg_separator) {
  std::string query;
  for (const auto &itr : param) {
    query += itr.first;
    query += join_key_and_value;
    query += UrlEncode(itr.second.c_str());
    query += arg_separator;
  }
  query.erase(query.size() - 1);
  return query;
}

std::string CreateAuthorizationHeader(const Param &param) {
  std::string oauth_str = "Authorization: OAuth ";
  for (auto& itr : param) {
    oauth_str += itr.first;
    oauth_str += "=\"";
    oauth_str += UrlEncode(itr.second.c_str());
    oauth_str += "\",";
  }
  oauth_str.erase(oauth_str.size() - 1, 1);
  return oauth_str;
}
std::string CreateOAuthSignature(const char *method, const char *url,
                                 const Param &param) {
  std::string signature = method;
  signature += '&';
  signature += UrlEncode(url);
  signature += '&';
  signature += UrlEncode(BuildHttpQuery(param).c_str());
  return signature;
}
static size_t CallBackWriteString(char *ptr, size_t size,
                                  size_t nmemb, std::string *stream) {
  size_t dataLength = size * nmemb;
  stream->append(ptr, dataLength);
  return dataLength;
}
std::string TwitterGetRequestToken() {
  const char *method = "POST";
  const char *requet_url = "https://api.twitter.com/oauth/request_token";

  const char *consumer_key = "コンシューマーキー";
  const char *consumer_key_secret = "コンシューマーキー シークレット";

  //nullの場合はoobがセットされ,PINコードの入力画面に飛ぶ
  const char *callback_url = nullptr;

  std::map<std::string, std::string> param;
  srand((unsigned)time(NULL));
  param["oauth_nonce"] = CreateRondomString(40);

  param["oauth_signature_method"] = "HMAC-SHA1";
  param["oauth_timestamp"] = std::to_string(time(NULL));
  param["oauth_consumer_key"] = consumer_key;
  param["oauth_version"] = "1.0";
  param["oauth_callback"] = callback_url/* && callback_url[0]*/ ? callback_url : "oob";

  std::string sig_target = CreateOAuthSignature(method, requet_url, param);
  std::string sig_key = UrlEncode(consumer_key_secret) + '&';
  param["oauth_signature"] = Base64Encode(HmacSha1(sig_target.c_str(), sig_key.c_str()).c_str());

  CURL *curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_POST, 1L);
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0);
  curl_easy_setopt(curl, CURLOPT_URL, requet_url);
  curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  //curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CallBackWriteString);
  std::string ret;
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);

  struct curl_slist *chunk = NULL;
  std::string oauth_str = CreateAuthorizationHeader(param);
  chunk = curl_slist_append(chunk, oauth_str.c_str());
  //chunk = curl_slist_append(chunk, "Expect:");
  chunk = curl_slist_append(chunk, "Content-type:");
  //chunk = curl_slist_append(chunk, "User-Agent: Twitter OAuth Test");
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
  CURLcode res = curl_easy_perform(curl);

  return ret;
}