C++ wrapper for the Mastodon API.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

http.cpp 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. /* This file is part of mastodon-cpp.
  2. * Copyright © 2018 tastytea <tastytea@tastytea.de>
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, version 3.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. #include <iostream>
  17. #include <functional> // std::bind
  18. #include <list>
  19. #include <cstring> // std::strncmp
  20. #include <exception>
  21. #include <curlpp/Options.hpp>
  22. #include <curlpp/Exception.hpp>
  23. #include <curlpp/Infos.hpp>
  24. #include "macros.hpp"
  25. #include "mastodon-cpp.hpp"
  26. using namespace Mastodon;
  27. namespace curlopts = curlpp::options;
  28. using std::cerr;
  29. API::http::http(const API &api, const string &instance,
  30. const string &access_token)
  31. : parent(api)
  32. , _instance(instance)
  33. , _access_token(access_token)
  34. , _cancel_stream(false)
  35. , _proxy("")
  36. , _proxy_userpw("")
  37. {
  38. curlpp::initialize();
  39. }
  40. API::http::~http()
  41. {
  42. curlpp::terminate();
  43. }
  44. const uint_fast16_t API::http::request(const method &meth,
  45. const string &path,
  46. string &answer)
  47. {
  48. return request(meth, path, curlpp::Forms(), answer);
  49. }
  50. const uint_fast16_t API::http::request(const method &meth,
  51. const string &path,
  52. const curlpp::Forms &formdata,
  53. string &answer)
  54. {
  55. using namespace std::placeholders; // _1, _2, _3
  56. uint_fast16_t ret = 0;
  57. ttdebug << "Path is: " << path << '\n';
  58. try
  59. {
  60. curlpp::Easy request;
  61. std::list<string> headers;
  62. request.setOpt<curlopts::Url>("https://" + _instance + path);
  63. ttdebug << "User-Agent: " << parent.get_useragent() << "\n";
  64. request.setOpt<curlopts::UserAgent>(parent.get_useragent());
  65. if (!_proxy.empty())
  66. {
  67. request.setOpt<curlopts::Proxy>(_proxy);
  68. if (!_proxy_userpw.empty())
  69. {
  70. request.setOpt<curlopts::ProxyUserPwd>(_proxy_userpw);
  71. }
  72. }
  73. if (!_access_token.empty())
  74. {
  75. headers.push_back("Authorization: Bearer " + _access_token);
  76. }
  77. if (meth != http::method::GET_STREAM)
  78. {
  79. headers.push_back("Connection: close");
  80. // Get headers from server
  81. request.setOpt<curlpp::options::Header>(true);
  82. }
  83. request.setOpt<curlopts::HttpHeader>(headers);
  84. request.setOpt<curlopts::FollowLocation>(true);
  85. request.setOpt<curlopts::WriteFunction>
  86. (std::bind(&http::callback_write, this, _1, _2, _3, &answer));
  87. request.setOpt<curlopts::ProgressFunction>
  88. (std::bind(&http::callback_progress, this, _1, _2, _3, _4));
  89. request.setOpt<curlopts::NoProgress>(0);
  90. if (!formdata.empty())
  91. {
  92. request.setOpt<curlopts::HttpPost>(formdata);
  93. }
  94. switch (meth)
  95. {
  96. case http::method::GET:
  97. break;
  98. case http::method::PATCH:
  99. request.setOpt<curlopts::CustomRequest>("PATCH");
  100. break;
  101. case http::method::POST:
  102. request.setOpt<curlopts::CustomRequest>("POST");
  103. break;
  104. case http::method::PUT:
  105. request.setOpt<curlopts::CustomRequest>("PUT");
  106. case http::method::DELETE:
  107. request.setOpt<curlopts::CustomRequest>("DELETE");
  108. default:
  109. break;
  110. }
  111. //request.setOpt<curlopts::Verbose>(true);
  112. answer.clear();
  113. request.perform();
  114. ret = curlpp::infos::ResponseCode::get(request);
  115. ttdebug << "Response code: " << ret << '\n';
  116. // Work around "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK"
  117. size_t pos = answer.find("\r\n\r\n", 25);
  118. _headers = answer.substr(0, pos);
  119. // Only return body
  120. answer = answer.substr(pos + 4);
  121. if (ret == 200 || ret == 302 || ret == 307)
  122. { // OK or Found or Temporary Redirect
  123. return 0;
  124. }
  125. else if (ret == 301 || ret == 308)
  126. { // Moved Permanently or Permanent Redirect
  127. // return new URL
  128. answer = curlpp::infos::EffectiveUrl::get(request);
  129. return 13;
  130. }
  131. else
  132. {
  133. return ret;
  134. }
  135. }
  136. catch (curlpp::RuntimeError &e)
  137. {
  138. // This error is thrown if http.cancel_stream() is used.
  139. if ((std::strncmp(e.what(), "Callback aborted", 16) == 0) ||
  140. (std::strncmp(e.what(), "Failed writing body", 19) == 0))
  141. {
  142. ttdebug << "Request was cancelled by user\n";
  143. return 14;
  144. }
  145. if (parent.exceptions())
  146. {
  147. std::rethrow_exception(std::current_exception());
  148. }
  149. else
  150. {
  151. ttdebug << "curlpp::RuntimeError: " << e.what() << std::endl;
  152. return 15;
  153. }
  154. }
  155. catch (curlpp::LogicError &e)
  156. {
  157. if (parent.exceptions())
  158. {
  159. std::rethrow_exception(std::current_exception());
  160. }
  161. ttdebug << "curlpp::LogicError: " << e.what() << std::endl;
  162. return 15;
  163. }
  164. return ret;
  165. }
  166. const void API::http::get_headers(string &headers) const
  167. {
  168. headers = _headers;
  169. }
  170. const size_t API::http::callback_write(char* data, size_t size, size_t nmemb,
  171. string *str)
  172. {
  173. std::lock_guard<std::mutex> lock(_mutex);
  174. str->append(data, size * nmemb);
  175. // ttdebug << "Received " << size * nmemb << " Bytes\n";
  176. return size * nmemb;
  177. };
  178. const size_t API::http::callback(char* data, size_t size, size_t nmemb,
  179. string *str)
  180. {
  181. return callback_write(data, size, nmemb, str);
  182. };
  183. double API::http::callback_progress(double /* dltotal */, double /* dlnow */,
  184. double /* ultotal */, double /* ulnow */)
  185. {
  186. if (_cancel_stream)
  187. {
  188. // This throws the runtime error: Callback aborted
  189. return 1;
  190. }
  191. return 0;
  192. };
  193. const void API::http::cancel_stream()
  194. {
  195. _cancel_stream = true;
  196. }
  197. const void API::http::abort_stream()
  198. {
  199. cancel_stream();
  200. }
  201. std::mutex &API::http::get_mutex()
  202. {
  203. return _mutex;
  204. }
  205. const void API::http::set_proxy(const string &proxy, const string &userpw)
  206. {
  207. _proxy = proxy;
  208. _proxy_userpw = userpw;
  209. }