From 6fa7c457c0410c147498b83ee39a23abd8d50e65 Mon Sep 17 00:00:00 2001 From: keita Date: Tue, 16 Feb 2021 07:31:34 +0900 Subject: [PATCH] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88=E3=81=9A?= =?UTF-8?q?=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88=E3=80=82=E8=AA=8D=E8=A8=BC?= =?UTF-8?q?=E3=81=A8=E3=81=8A=E3=82=89=E3=82=93(#47)(48)(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 76 ++++++++++++++++ src/CMakeLists.txt | 11 +++ src/cocoatweet/CMakeLists.txt | 7 ++ src/cocoatweet/api/api.cc | 12 +++ src/cocoatweet/api/api.h | 18 ++++ src/cocoatweet/api/groupInterface.h | 14 +++ src/cocoatweet/api/interface.cc | 9 ++ src/cocoatweet/api/interface.h | 19 ++++ src/cocoatweet/api/status/status.cc | 16 ++++ src/cocoatweet/api/status/status.h | 17 ++++ src/cocoatweet/api/status/update.cc | 94 ++++++++++++++++++++ src/cocoatweet/api/status/update.h | 20 +++++ src/cocoatweet/oauth/key.h | 34 ++++++++ src/cocoatweet/oauth/oauth.cc | 131 ++++++++++++++++++++++++++++ src/cocoatweet/oauth/oauth.h | 30 +++++++ src/main.cc | 12 +++ 16 files changed, 520 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 src/CMakeLists.txt create mode 100644 src/cocoatweet/CMakeLists.txt create mode 100644 src/cocoatweet/api/api.cc create mode 100644 src/cocoatweet/api/api.h create mode 100644 src/cocoatweet/api/groupInterface.h create mode 100644 src/cocoatweet/api/interface.cc create mode 100644 src/cocoatweet/api/interface.h create mode 100644 src/cocoatweet/api/status/status.cc create mode 100644 src/cocoatweet/api/status/status.h create mode 100644 src/cocoatweet/api/status/update.cc create mode 100644 src/cocoatweet/api/status/update.h create mode 100644 src/cocoatweet/oauth/key.h create mode 100644 src/cocoatweet/oauth/oauth.cc create mode 100644 src/cocoatweet/oauth/oauth.h create mode 100644 src/main.cc diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..843d19d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.10) + +project(CocoaTweet CXX C) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") + +# Disable in-source build +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +set(CMAKE_DISABLE_SOURCE_CHANGES ON) + +# Set default build type +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# Set compiler flags +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 17) +set(CXX_STANDARD_REQUIRED ON) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -DDEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -march=native -DNDEBUG") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-O1,--sort-common,--as-needed,-z,relro") + +if(CMAKE_GENERATOR STREQUAL "Ninja") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") + endif() +endif() + +# Sanitizers +if(ENABLE_SANITIZERS) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + message(STATUS "Enabling GCC's address sanitizer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + message(STATUS "Enabling Clang's address sanitizer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + endif() +endif() + +# Code coverage +if(ENABLE_CODE_COVERAGE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") +endif() + +# Required libraries +find_package(Boost 1.61.0 COMPONENTS unit_test_framework REQUIRED) +find_package(PkgConfig REQUIRED) +find_package(OpenSSL REQUIRED) +if(NOT OPENSSL_FOUND) + message(FATAL_ERROR "Fail to find OpenSSL") # exit +endif() +message(STATUS "OPENSSL_INCLUDE_DIR: ${OPENSSL_INCLUDE_DIR}") +include_directories(${OPENSSL_INCLUDE_DIR}) + +find_package(CURL REQUIRED) +if(NOT CURL_FOUND) + message(FATAL_ERROR "Fail to find OpenSSL") # exit +endif() +message(FATAL_ERROR, "Fail to find libcurl") +include_directories(${CURL_INCLUDE_DIRS}) + +enable_testing() + +include_directories( + ${PROJECT_SOURCE_DIR}/src + ${Boost_INCLUDE_DIRS} +) + +add_subdirectory(src) +#add_subdirectory(test) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..2b348df --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,11 @@ +add_subdirectory(cocoatweet) + +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}) +add_executable(${PROJECT_NAME} main.cc) +target_link_libraries(${PROJECT_NAME} + lib-cocoatweet + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} + OpenSSL::SSL + OpenSSL::Crypto + ${CURL_LIBRARIES} +) diff --git a/src/cocoatweet/CMakeLists.txt b/src/cocoatweet/CMakeLists.txt new file mode 100644 index 0000000..4505a85 --- /dev/null +++ b/src/cocoatweet/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCES ./*.cc) +add_library(lib-cocoatweet ${SOURCES}) +target_link_libraries(lib-cocoatweet PUBLIC + Boost::boost +) +target_include_directories(lib-cocoatweet PUBLIC ${PROJECT_SOURCE_DIR}/src) +set_target_properties(lib-cocoatweet PROPERTIES OUTPUT_NAME cocoatweet) diff --git a/src/cocoatweet/api/api.cc b/src/cocoatweet/api/api.cc new file mode 100644 index 0000000..f765ad1 --- /dev/null +++ b/src/cocoatweet/api/api.cc @@ -0,0 +1,12 @@ +#include + +namespace CocoaTweet::API{ + API::API(CocoaTweet::OAuth::Key _key){ + oauth_ = std::make_shared(_key); + status_ = Statuses::Status(oauth_); + } + + Statuses::Status API::status() const{ + return status_; + } +} \ No newline at end of file diff --git a/src/cocoatweet/api/api.h b/src/cocoatweet/api/api.h new file mode 100644 index 0000000..e0dff24 --- /dev/null +++ b/src/cocoatweet/api/api.h @@ -0,0 +1,18 @@ +#ifndef COCOATWEET_API_H_ +#define COCOATWEET_API_H_ + +#include "cocoatweet/api/status/status.h" +#include "cocoatweet/oauth/oauth.h" + +namespace CocoaTweet::API{ +class API{ + public: + API(CocoaTweet::OAuth::Key _key); + Statuses::Status status() const; + private: + Statuses::Status status_; + std::shared_ptr oauth_; +}; +} + +#endif diff --git a/src/cocoatweet/api/groupInterface.h b/src/cocoatweet/api/groupInterface.h new file mode 100644 index 0000000..38ddd79 --- /dev/null +++ b/src/cocoatweet/api/groupInterface.h @@ -0,0 +1,14 @@ +#ifndef COCOATWEET_API_GROUPINTERFACE_H_ +#define COCOATWEET_API_GROUPINTERFACE_H_ + +#include +#include "cocoatweet/oauth/oauth.h" + +namespace CocoaTweet::API{ +class groupInterface{ +protected: + std::weak_ptr oauth_; +}; +} + +#endif diff --git a/src/cocoatweet/api/interface.cc b/src/cocoatweet/api/interface.cc new file mode 100644 index 0000000..4e87bf5 --- /dev/null +++ b/src/cocoatweet/api/interface.cc @@ -0,0 +1,9 @@ +#include + +namespace CocoaTweet::API{ + size_t Interface::curlCallback_(char* _ptr, size_t _size, size_t _nmemb, std::string* _stream){ + int realsize = _size * _nmemb; + _stream->append(_ptr, realsize); + return realsize; + } +} diff --git a/src/cocoatweet/api/interface.h b/src/cocoatweet/api/interface.h new file mode 100644 index 0000000..7680563 --- /dev/null +++ b/src/cocoatweet/api/interface.h @@ -0,0 +1,19 @@ +#ifndef COCOATWEET_API_INTERFACE_H_ +#define COCOATWEET_API_INTERFACE_H_ + +#include +#include "cocoatweet/oauth/oauth.h" + +namespace CocoaTweet::API{ +class Interface{ +public: + virtual void process(std::weak_ptr _oauth, std::function _callback) = 0; +protected: + std::weak_ptr oauth_; + std::map param_; + std::string url_; + static size_t curlCallback_(char* _ptr, size_t _size, size_t _nmemb, std::string* _stream); +}; +} + +#endif diff --git a/src/cocoatweet/api/status/status.cc b/src/cocoatweet/api/status/status.cc new file mode 100644 index 0000000..eaf9e90 --- /dev/null +++ b/src/cocoatweet/api/status/status.cc @@ -0,0 +1,16 @@ +#include + +#include "cocoatweet/api/status/status.h" +#include "cocoatweet/api/status/update.h" + +namespace CocoaTweet::API::Statuses{ + Status::Status(std::shared_ptr _oauth){ + oauth_ = _oauth; + } + + void Status::Update(const std::string& _status)const{ + CocoaTweet::API::Statuses::Update update; + update.status(_status); + update.process(oauth_, [](std::string _rcv){std::cout << _rcv << std::endl;}); + } +} diff --git a/src/cocoatweet/api/status/status.h b/src/cocoatweet/api/status/status.h new file mode 100644 index 0000000..7db7de5 --- /dev/null +++ b/src/cocoatweet/api/status/status.h @@ -0,0 +1,17 @@ +#ifndef COCOATWEET_API_STATUSED_H_ +#define COCOATWEET_API_STATUSED_H_ + +#include "cocoatweet/api/groupInterface.h" +#include "cocoatweet/oauth/oauth.h" + +namespace CocoaTweet::API::Statuses{ +class Status : public groupInterface{ + public: + Status() = default; + Status(std::shared_ptr _oauth); + void Update(const std::string& _status) const; + private: +}; +} + +#endif diff --git a/src/cocoatweet/api/status/update.cc b/src/cocoatweet/api/status/update.cc new file mode 100644 index 0000000..ad4fc82 --- /dev/null +++ b/src/cocoatweet/api/status/update.cc @@ -0,0 +1,94 @@ +#include "cocoatweet/api/status/update.h" +#include +#include +#include +#include +#include +extern "C" { +#include +} + +namespace CocoaTweet::API::Statuses{ + Update::Update(){ + url_ = "https://api.twitter.com/1.1/statuses/update.json"; + } + void Update::status(const std::string _status){ + status_ = _status; + param_.insert_or_assign("status", status_); + } + + void Update::process(std::weak_ptr _oauth, std::function _callback){ + // エンドポイントへのパラメータにOAuthパラメータを付加して署名作成 + auto oauth = _oauth.lock(); + param_.merge(oauth->oauthParam()); + auto signature = oauth->signature(param_, "POST", url_); + + // 作成した署名をエンドポイントへのパラメータ及びOAuthパラメータに登録 + param_.insert_or_assign("oauth_signature", signature["oauth_signature"]); + auto header = oauth->oauthParam(); + std::cout << "signature : " << signature["oauth_signature"] << std::endl; + header.insert_or_assign("oauth_signature", signature["oauth_signature"]); + + // リクエストボディの構築 + std::string requestBody = ""; + { + std::vector tmp; + for(const auto& [key, value] : param_){ + tmp.push_back(key + "=" + value); + } + std::stringstream os; + std::copy(tmp.begin(), tmp.end(), std::ostream_iterator(os, "&")); + requestBody = os.str(); + requestBody.erase(requestBody.size() - std::char_traits::length("&")); + } + std::cout << "Body : " << requestBody << std::endl; + + // ヘッダの構築 + std::string oauthHeader = "Authorization: OAuth "; + { + std::vector tmp; + for(const auto& [key, value] : header){ + tmp.push_back(key + "=" + value); + } + std::stringstream os; + std::copy(tmp.begin(), tmp.end(), std::ostream_iterator(os, ",")); + oauthHeader += os.str(); + oauthHeader.erase(oauthHeader.size() - std::char_traits::length(",")); + } + std::cout << "OAuth Header : " << oauthHeader << std::endl; + + + + + // do post + CURL *curl; + CURLcode res; + std::string rcv; + curl = curl_easy_init(); + std::cout << "URL : " << url_ << std::endl; + if (curl){ + curl_easy_setopt(curl, CURLOPT_URL, url_.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, requestBody); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, requestBody.length()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlCallback_); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (std::string*)&rcv); + curl_easy_setopt(curl, CURLOPT_PROXY, ""); + //Headerを保持するcurl_slist*を初期化 + struct curl_slist *headers = NULL; + //Authorizationをヘッダに追加 + headers = curl_slist_append(headers, oauthHeader.c_str()); + curl_easy_setopt(curl, CURLOPT_HEADER, headers); + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + } + if (res != CURLE_OK) { + std::cout << "curl error : " << res << std::endl; + exit (1); + } + + if(_callback){ + _callback(rcv); + } + } +} diff --git a/src/cocoatweet/api/status/update.h b/src/cocoatweet/api/status/update.h new file mode 100644 index 0000000..b3bdfd3 --- /dev/null +++ b/src/cocoatweet/api/status/update.h @@ -0,0 +1,20 @@ +#ifndef COCOATWEET_API_STATUSES_UPDATE_H_ +#define COCOATWEET_API_STATUSES_UPDATE_H_ + +#include + +#include "cocoatweet/api/interface.h" +//#include "cocoatweet/oauth/oauth.h" + +namespace CocoaTweet::API::Statuses{ +class Update :public Interface{ +public: + Update(); + void status(const std::string _status); + void process(std::weak_ptr _oauth, std::function _callback); +private: + std::string status_; +}; +} + +#endif diff --git a/src/cocoatweet/oauth/key.h b/src/cocoatweet/oauth/key.h new file mode 100644 index 0000000..e77dfb1 --- /dev/null +++ b/src/cocoatweet/oauth/key.h @@ -0,0 +1,34 @@ +#ifndef COCOATWEET_OAUTH_KEY_H_ +#define COCOATWEET_OAUTH_KEY_H_ + +#include +namespace CocoaTweet::OAuth{ +class Key{ +const std::string consumerKey_; +const std::string consumerSecret_; +const std::string accessToken_; +const std::string accessTokenSecret_; + +public: +Key() = default; +Key(const std::string& _consumerKey, const std::string& _consumerSecret, const std::string& _accessToken, const std::string& _accessTokenSecret):consumerKey_(_consumerKey), consumerSecret_(_consumerSecret), accessToken_(_accessToken), accessTokenSecret_(_accessTokenSecret){} +const std::string& consumerKey() const{return consumerKey_;} +const std::string& consumerSecret() const{return consumerSecret_;} +const std::string& accessToken() const{return accessToken_;} +const std::string& accessTokenSecret() const{return accessTokenSecret_;} +std::map noSecret()const{ + return std::map{ + {"oauth_consumer_key", consumerKey_}, + {"oauth_token", accessToken_} + }; +} +const std::map secret()const{ + return std::map{ + {"oauth_consumer_key", consumerSecret_}, + {"oauth_token", accessTokenSecret_} + }; +} +}; +} + +#endif diff --git a/src/cocoatweet/oauth/oauth.cc b/src/cocoatweet/oauth/oauth.cc new file mode 100644 index 0000000..49cf1a8 --- /dev/null +++ b/src/cocoatweet/oauth/oauth.cc @@ -0,0 +1,131 @@ +#include "oauth.h" + +#include +#include +#include +#include +#include +#include +#include + +extern "C"{ +#include +#include +} + +namespace CocoaTweet::OAuth{ + OAuth1::OAuth1(){ + + } + + OAuth1::OAuth1(const Key _key):key_(_key){ + + } + + std::map OAuth1::signature(const std::map& _param, const std::string& _method, const std::string& _url){ + std::vector tmp; + for(const auto& [key, value] : _param){ + tmp.push_back(key + "=" + value); + } + std::ostringstream os; + std::copy(tmp.begin(), tmp.end(), std::ostream_iterator(os, "&")); + std::string query = os.str(); + query.erase(query.size() - std::char_traits::length("&")); + + auto significateKey = key().consumerSecret() + "&" + key().accessTokenSecret(); + auto significateBase = _method + "&" + _url + "&" + query; + auto result = hmacSha1(significateKey, significateBase); + + std::cout << "significate key : " << significateKey << std::endl; + std::cout << "significate base : " << significateBase << std::endl; + std::cout << "hmac-sha1 : " << base64(result) << std::endl; + + auto ret = std::map{ + {"oauth_signature", base64(result)} + }; + return ret; + } + + const std::string OAuth1::nonce()const{ + std::random_device engine; + std::string nonceTable = "abcdefghijklmnopqrstuvwxyz0123456789"; + std::uniform_int_distribution dist(0, nonceTable.length() - 1); + std::string nonce; + + for (auto i = 0; i < 32; ++i) { + nonce += nonceTable[dist(engine)]; + } + + return nonce; + } + + + const std::string OAuth1::timestamp() const{ + return std::to_string(time(nullptr)); + } + + const std::string OAuth1::method() const{ + return SIGNATURE_METHOD_; + } + + const std::string OAuth1::version() const{ + return OAUTH_VERSION_; + } + + const Key OAuth1::key() const{ + return key_; + } + + std::map OAuth1::oauthParam() const{ + auto tmp = std::map{ + {"oauth_nonce", nonce()}, + {"oauth_signature_method", method()}, + {"oauth_timestamp", timestamp()}, + {"oauth_version", version()} + }; + tmp.merge(key().noSecret()); + + return tmp; + } + + const std::string OAuth1::base64(const std::string& _raw){ + auto base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::stringstream ss; + for(auto r : _raw){ + ss << std::bitset<8>(r); + } + + + if(_raw.length() % 3 == 1){ + ss << "0000"; + }else if(_raw.length() % 3 == 2){ + ss << "00"; + } + + auto bin = ss.str(); + std::string base64 = ""; + for(auto i = 0;i < bin.length() / 6;i++){ + base64 += base64Table[std::stoi(bin.substr(i * 6, 6), nullptr, 2)]; + } + + if(base64.length() % 4 == 3){ + base64 += "="; + }else if(base64.length() % 4 == 2){ + base64 += "=="; + }else if(base64.length() % 4 == 1){ + base64 += "==="; + } + + return base64; + } + + std::string OAuth1::hmacSha1(std::string _key, std::string _data){ + unsigned char result[255]; + unsigned int length = 255; + + HMAC(EVP_sha1(), reinterpret_cast(_key.c_str()), _key.length(), reinterpret_cast(_data.c_str()), _data.length(), result, &length); + + return std::string(reinterpret_cast(result)); + } + +} diff --git a/src/cocoatweet/oauth/oauth.h b/src/cocoatweet/oauth/oauth.h new file mode 100644 index 0000000..41a945b --- /dev/null +++ b/src/cocoatweet/oauth/oauth.h @@ -0,0 +1,30 @@ +#ifndef COCOATWEET_OAUTH_OAUTH_H_ +#define COCOATWEET_OAUTH_OAUTH_H_ + +#include +#include +#include "key.h" + +namespace CocoaTweet::OAuth{ +class OAuth1{ +public: +OAuth1(); +OAuth1(const Key _key); +std::map signature(const std::map& _param, const std::string& _method, const std::string& _url); +const std::string nonce()const; +const std::string timestamp()const; +const std::string method()const; +const std::string version()const; +const Key key()const; +std::map oauthParam()const; +std::string hmacSha1(std::string _key, std::string _data); +const std::string base64(const std::string& _raw); +private: +Key key_; +const std::string SIGNATURE_METHOD_ = "HMAC-SHA1"; +const std::string OAUTH_VERSION_ = "1.0"; + +}; +} + +#endif diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..6fbe503 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,12 @@ +#include "cocoatweet/oauth/key.h" +#include "cocoatweet/api/api.h" + +auto main()->int{ + auto consumerKey = "JRKUmkKFWiC3f7K6msLKaNNuP"; + auto consumerSecret = "dTGI49MHRqa7XIFiPjwJR27vwolzsRaRXKA48iFlwAv4LK9Vlm"; + auto accessToken = "2224351076-uF2XTmYeDdAfIsixuvfrwt8puLiPuwGe4w7RM8I"; + auto accessTokenSecret = "dpCctbxzMjQ9AjZ6V7Fs6TIQlpPJo7JEkmjMfSO7QCEpW"; + CocoaTweet::OAuth::Key key(consumerKey, consumerSecret, accessToken, accessTokenSecret); + CocoaTweet::API::API api(key); + api.status().Update("CocoaTwitterAPI"); +}