From fa75e8f0d70f2931b06d016390ee548669e7d342 Mon Sep 17 00:00:00 2001 From: rexy712 Date: Thu, 17 Oct 2019 15:18:03 -0700 Subject: [PATCH] Add ability to download files from the homeserver --- include/matrix/client.hpp | 17 +++ include/matrix/client_url_list.hpp | 2 + include/matrix/connection.hpp | 28 +++-- include/raii/binary.hpp | 2 +- include/raii/curler.hpp | 8 ++ src/matrix/client.cpp | 12 +-- src/matrix/client_url_list.cpp | 17 ++- src/matrix/connection.cpp | 168 ++++++++++++++--------------- src/raii/curler.cpp | 12 +++ 9 files changed, 162 insertions(+), 104 deletions(-) diff --git a/include/matrix/client.hpp b/include/matrix/client.hpp index 8a451c9..ea2a909 100644 --- a/include/matrix/client.hpp +++ b/include/matrix/client.hpp @@ -131,7 +131,24 @@ namespace matrix{ bool create_thumbnail(uploaded_image& info)const; bool create_thumbnail(uploaded_video& video)const; + + raii::binary download_file(const raii::string_base& url); + template + bool download_file(const raii::string_base& url, DLHandler&& dl){ + _get_curl_setup(m_ses->urls.file_download(m_ses->homeserver, url)); + m_curl.setwritefun(_download_dispatch); + m_curl.setwritedata(&dl); + if(!_perform_curl()) + return false; + return true; + } + template + void download_file(const raii::string_base& server, const raii::string_base& media_id, DLHandler&& dl); private: + template + static size_t _download_dispatch(char* ptr, size_t size, size_t nmemb, void* userdata){ + return reinterpret_cast(userdata)(ptr, size, nmemb, nullptr); + } uploaded_file _upload_file(const file_details& file, const raii::curl_llist& header)const; }; } diff --git a/include/matrix/client_url_list.hpp b/include/matrix/client_url_list.hpp index 88584cb..f19aeff 100644 --- a/include/matrix/client_url_list.hpp +++ b/include/matrix/client_url_list.hpp @@ -52,6 +52,7 @@ namespace matrix{ const raii::string& whoami(void)const; const raii::string& displayname(void)const; const raii::string& profile_picture(void)const; + raii::string file_download(const raii::string_base& homeserver, const raii::string_base& mediaid); raii::string file_thumbnail(const raii::string_base& homeserver, const raii::string_base& fileurl, int width, int height, const raii::string_base& method)const; raii::string logout(const raii::string_base& homeserver, const raii::string_base& access_token)const; raii::string sync(const raii::string_base& homeserver, const raii::string_base& access_token, const raii::string_base& next_batch, const raii::string_base& timeout)const; @@ -67,6 +68,7 @@ namespace matrix{ private: void _initial_populate(const raii::string_base& homeserver); + static raii::string get_server_media_string(const raii::string_base& url); static constexpr const char* s_proto = "https://"; static constexpr const char* s_revision = "r0"; }; diff --git a/include/matrix/connection.hpp b/include/matrix/connection.hpp index e4cf82c..ead864c 100644 --- a/include/matrix/connection.hpp +++ b/include/matrix/connection.hpp @@ -66,13 +66,29 @@ namespace matrix{ long http_status(void)const; protected: + struct put_data{ + const char* data; + size_t len; + }; + static size_t _reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata); + static size_t _binary_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata); + void _get_curl_setup(const raii::string_base& url)const; + void _post_curl_setup(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; + static size_t _put_read_curl_callback(char* buffer, size_t size, size_t nmemb, void* userdata); + void _put_curl_setup(put_data& data, const raii::string_base& url, const raii::curl_llist& header)const; + + protected: + using binary_callback_fun = size_t(*)(char*, size_t, size_t, void*); + using reply_callback_fun = size_t(*)(char*, size_t, size_t, void*); + void _set_curl_useragent(const raii::string_base& useragent); - raii::string _get_curl(const raii::string_base& url)const; - raii::binary _get_curl_binary(const raii::string_base& url)const; - raii::string _post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; - raii::binary _post_curl_binary(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; - raii::string _put_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; - raii::binary _put_curl_binary(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const; + bool _perform_curl(void)const; + raii::string _get_curl(const raii::string_base& url, reply_callback_fun fun = _reply_curl_callback)const; + raii::binary _get_curl_binary(const raii::string_base& url, binary_callback_fun fun = _binary_reply_curl_callback)const; + raii::string _post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header, reply_callback_fun = _reply_curl_callback)const; + raii::binary _post_curl_binary(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header, binary_callback_fun = _binary_reply_curl_callback)const; + raii::string _put_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header, reply_callback_fun = _reply_curl_callback)const; + raii::binary _put_curl_binary(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header, binary_callback_fun = _binary_reply_curl_callback)const; raii::rjp_string _post_and_find(const raii::string_base& data, const raii::string_base& url, const raii::curl_llist& header, const raii::string_base& target)const; raii::rjp_string _get_and_find(const raii::string_base& url, const raii::string_base& search)const; raii::rjp_string _curl_reply_search(const raii::string_base& reply, const raii::string_base& search)const; diff --git a/include/raii/binary.hpp b/include/raii/binary.hpp index 3e0260c..9afbbc2 100644 --- a/include/raii/binary.hpp +++ b/include/raii/binary.hpp @@ -39,7 +39,7 @@ namespace raii{ protected: constexpr binary_base(void) = default; constexpr binary_base(char* data, size_t size): - m_data(data), m_size(size){} + m_data(data), m_cap(size){} template binary_base(const binary_base& b): m_data(Allocator::copy(b.m_data, b.m_cap)), diff --git a/include/raii/curler.hpp b/include/raii/curler.hpp index 81ba504..7cdedf3 100644 --- a/include/raii/curler.hpp +++ b/include/raii/curler.hpp @@ -31,6 +31,10 @@ namespace raii{ { private: CURL* m_curl; + public: + using write_function = size_t(*)(char*,size_t,size_t,void*); + using read_function = size_t(*)(char*,size_t,size_t,void*); + public: curler(void); curler(const curler& c); @@ -58,6 +62,10 @@ namespace raii{ curler& setpostdata(const char* s, curl_off_t len = -1); curler& setpostdata(const string_base& s); curler& forcessl(long version = CURL_SSLVERSION_DEFAULT); + curler& setwritefun(write_function); + curler& setwritedata(void*); + curler& setreadfun(read_function); + curler& setreaddata(void*); void reset(void); decltype(curl_easy_perform(m_curl)) perform(void); diff --git a/src/matrix/client.cpp b/src/matrix/client.cpp index 3646912..c468650 100644 --- a/src/matrix/client.cpp +++ b/src/matrix/client.cpp @@ -156,6 +156,9 @@ namespace matrix{ bool client::create_thumbnail(uploaded_video& info)const{ return create_thumbnail(static_cast(info)); } + raii::binary client::download_file(const raii::string_base& url){ + return _get_curl_binary(m_ses->urls.file_download(m_ses->homeserver, url)); + } /******************************* Internal functions @@ -175,13 +178,6 @@ namespace matrix{ return to_copy; } - static size_t _post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){ - raii::string* data = reinterpret_cast(userdata); - size_t oldlen = data->length(); - data->resize(data->length() + (size*nmemb)); - memcpy(data->get()+oldlen, ptr, size*nmemb); - return size*nmemb; - } uploaded_file client::_upload_file(const file_details& file, const raii::curl_llist& header)const{ raii::string fileurl; uploaded_file retval = {}; @@ -194,7 +190,7 @@ namespace matrix{ m_curl.setopt(CURLOPT_INFILESIZE_LARGE, (curl_off_t)upload_data.len); m_curl.seturl(m_ses->urls.file_upload()); m_curl.setheader(header); - m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); + m_curl.setopt(CURLOPT_WRITEFUNCTION, _reply_curl_callback); m_curl.setopt(CURLOPT_WRITEDATA, &fileurl); CURLcode cres = m_curl.perform(); m_curl.setopt(CURLOPT_READDATA, NULL); diff --git a/src/matrix/client_url_list.cpp b/src/matrix/client_url_list.cpp index 04bd1e1..f1f7954 100644 --- a/src/matrix/client_url_list.cpp +++ b/src/matrix/client_url_list.cpp @@ -78,11 +78,14 @@ namespace matrix{ const raii::string& client_url_list::profile_picture(void)const{ return m_profile_picture; } + raii::string client_url_list::file_download(const raii::string_base& homeserver, const raii::string_base& fileurl){ + raii::string media = get_server_media_string(fileurl); + if(!media) return {}; + return raii::string(s_proto + homeserver + "/_matrix/media/r0/download/" + media); + } raii::string client_url_list::file_thumbnail(const raii::string_base& homeserver, const raii::string_base& fileurl, int width, int height, const raii::string_base& method)const{ - if(strncmp(fileurl.get(), "mxc://", 6)) - return {}; - - raii::string media(fileurl.get()+6, fileurl.length()-6); + raii::string media = get_server_media_string(fileurl); + if(!media) return {}; return raii::string(s_proto + homeserver + "/_matrix/media/r0/thumbnail/" + media + "?width=" + raii::itostr(width) + "&height=" + raii::itostr(height) + "&method=" + method); } raii::string client_url_list::logout(const raii::string_base& homeserver, const raii::string_base& access_token)const{ @@ -102,5 +105,11 @@ namespace matrix{ void client_url_list::_initial_populate(const raii::string_base& homeserver){ m_alias_lookup = s_proto + homeserver + "/_matrix/client/r0/directory/room/"; } + raii::string client_url_list::get_server_media_string(const raii::string_base& url){ + if(!url || strncmp(url.get(), "mxc://", 6)) + return {}; + + return raii::string(url.get()+6, url.length()-6); + } } diff --git a/src/matrix/connection.cpp b/src/matrix/connection.cpp index 9c22fc1..672338a 100644 --- a/src/matrix/connection.cpp +++ b/src/matrix/connection.cpp @@ -44,75 +44,29 @@ namespace matrix{ long connection::http_status(void)const{ return m_curl.last_status(); } - void connection::_set_curl_useragent(const raii::string_base& useragent){ - m_curl.setuseragent(useragent); - } - static size_t _post_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){ + + size_t connection::_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){ raii::string* data = reinterpret_cast(userdata); data->append(ptr, size*nmemb); return size*nmemb; } - static size_t _binary_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){ + size_t connection::_binary_reply_curl_callback(char* ptr, size_t size, size_t nmemb, void* userdata){ raii::binary* data = reinterpret_cast(userdata); data->append(ptr, size*nmemb); return size*nmemb; } - static void _get_curl_setup(const raii::string_base& url, raii::curler& curl){ - curl.getreq(); - curl.seturl(url); - curl.setheader(raii::curl_llist{}); + void connection::_get_curl_setup(const raii::string_base& url)const{ + m_curl.getreq(); + m_curl.seturl(url); + m_curl.setheader(raii::curl_llist{}); } - raii::string connection::_get_curl(const raii::string_base& url)const{ - raii::string reply; - _get_curl_setup(url, m_curl); - m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); - m_curl.setopt(CURLOPT_WRITEDATA, &reply); - CURLcode res = m_curl.perform(); - if(res != CURLE_OK) - return {}; - return reply; + void connection::_post_curl_setup(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const{ + m_curl.postreq(); + m_curl.setpostdata(postdata.get(), postdata.length()); + m_curl.seturl(url); + m_curl.setheader(header); } - raii::binary connection::_get_curl_binary(const raii::string_base& url)const{ - raii::binary reply; - _get_curl_setup(url, m_curl); - m_curl.setopt(CURLOPT_WRITEFUNCTION, _binary_reply_curl_callback); - m_curl.setopt(CURLOPT_WRITEDATA, &reply); - CURLcode res = m_curl.perform(); - if(res != CURLE_OK) - return {}; - return reply; - } - static void _post_curl_setup(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header, raii::curler& curl){ - curl.postreq(); - curl.setpostdata(postdata.get(), postdata.length()); - curl.seturl(url); - curl.setheader(header); - } - raii::string connection::_post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const{ - raii::string reply; - _post_curl_setup(postdata, url, header, m_curl); - m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); - m_curl.setopt(CURLOPT_WRITEDATA, &reply); - CURLcode res = m_curl.perform(); - if(res != CURLE_OK) - return {}; - return reply; - } - raii::binary connection::_post_curl_binary(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header)const{ - raii::binary reply; - _post_curl_setup(postdata, url, header, m_curl); - m_curl.setopt(CURLOPT_WRITEFUNCTION, _binary_reply_curl_callback); - m_curl.setopt(CURLOPT_WRITEDATA, &reply); - CURLcode res = m_curl.perform(); - if(res != CURLE_OK) - return {}; - return reply; - } - struct put_data{ - const char* data; - size_t len; - }; - static size_t _put_read_curl_callback(char* buffer, size_t size, size_t nmemb, void* userdata){ + size_t connection::_put_read_curl_callback(char* buffer, size_t size, size_t nmemb, void* userdata){ put_data* src = reinterpret_cast(userdata); size_t curl_size = size*nmemb; size_t to_copy = std::min(curl_size, src->len); @@ -121,39 +75,83 @@ namespace matrix{ src->data += to_copy; return to_copy; } - static void _put_curl_setup(put_data& data, const raii::string_base& url, const raii::curl_llist& header, raii::curler& curl){ - curl.putreq(); - curl.setopt(CURLOPT_POSTFIELDS, data.data); - curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)data.len); - curl.seturl(url); - curl.setheader(header); - curl.setopt(CURLOPT_READFUNCTION, _put_read_curl_callback); - curl.setopt(CURLOPT_READDATA, &data); - curl.setopt(CURLOPT_INFILESIZE, (curl_off_t)data.len); + void connection::_put_curl_setup(put_data& data, const raii::string_base& url, const raii::curl_llist& header)const{ + m_curl.putreq(); + m_curl.setopt(CURLOPT_POSTFIELDS, data.data); + m_curl.setopt(CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)data.len); + m_curl.seturl(url); + m_curl.setheader(header); + m_curl.setreadfun(_put_read_curl_callback); + m_curl.setreaddata(&data); + m_curl.setopt(CURLOPT_INFILESIZE, (curl_off_t)data.len); } - raii::string connection::_put_curl(const raii::string_base& putdata, const raii::string_base& url, const raii::curl_llist& header)const{ - raii::string reply; - put_data data{putdata.get(), putdata.length()}; - m_curl.setopt(CURLOPT_WRITEFUNCTION, _post_reply_curl_callback); - m_curl.setopt(CURLOPT_WRITEDATA, &reply); - _put_curl_setup(data, url, header, m_curl); + + void connection::_set_curl_useragent(const raii::string_base& useragent){ + m_curl.setuseragent(useragent); + } + bool connection::_perform_curl(void)const{ CURLcode res = m_curl.perform(); - m_curl.setopt(CURLOPT_READDATA, NULL); - m_curl.setopt(CURLOPT_READFUNCTION, NULL); - if(res != CURLE_OK) + return (res == CURLE_OK); + } + raii::string connection::_get_curl(const raii::string_base& url, reply_callback_fun fun)const{ + raii::string reply; + _get_curl_setup(url); + m_curl.setwritefun(fun); + m_curl.setwritedata(&reply); + if(!_perform_curl()) return {}; return reply; } - raii::binary connection::_put_curl_binary(const raii::string_base& putdata, const raii::string_base& url, const raii::curl_llist& header)const{ + raii::binary connection::_get_curl_binary(const raii::string_base& url, binary_callback_fun fun)const{ + raii::binary reply; + _get_curl_setup(url); + m_curl.setwritefun(fun); + m_curl.setwritedata(&reply); + if(!_perform_curl()) + return {}; + return reply; + } + raii::string connection::_post_curl(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header, reply_callback_fun fun)const{ + raii::string reply; + _post_curl_setup(postdata, url, header); + m_curl.setwritefun(fun); + m_curl.setwritedata(&reply); + if(!_perform_curl()) + return {}; + return reply; + } + raii::binary connection::_post_curl_binary(const raii::string_base& postdata, const raii::string_base& url, const raii::curl_llist& header, binary_callback_fun fun)const{ + raii::binary reply; + _post_curl_setup(postdata, url, header); + m_curl.setwritefun(fun); + m_curl.setwritedata(&reply); + if(!_perform_curl()) + return {}; + return reply; + } + raii::string connection::_put_curl(const raii::string_base& putdata, const raii::string_base& url, const raii::curl_llist& header, reply_callback_fun fun)const{ + raii::string reply; + put_data data{putdata.get(), putdata.length()}; + m_curl.setwritefun(fun); + m_curl.setwritedata(&reply); + _put_curl_setup(data, url, header); + bool succ = _perform_curl(); + m_curl.setreaddata(NULL); + m_curl.setreadfun(NULL); + if(!succ) + return {}; + return reply; + } + raii::binary connection::_put_curl_binary(const raii::string_base& putdata, const raii::string_base& url, const raii::curl_llist& header, binary_callback_fun fun)const{ raii::binary reply; put_data data{putdata.get(), putdata.length()}; - m_curl.setopt(CURLOPT_WRITEFUNCTION, _binary_reply_curl_callback); - m_curl.setopt(CURLOPT_WRITEDATA, &reply); - _put_curl_setup(data, url, header, m_curl); - CURLcode res = m_curl.perform(); - m_curl.setopt(CURLOPT_READDATA, NULL); - m_curl.setopt(CURLOPT_READFUNCTION, NULL); - if(res != CURLE_OK) + m_curl.setwritefun(fun); + m_curl.setwritedata(&reply); + _put_curl_setup(data, url, header); + bool succ = _perform_curl(); + m_curl.setreaddata(NULL); + m_curl.setreadfun(NULL); + if(!succ) return {}; return reply; } diff --git a/src/raii/curler.cpp b/src/raii/curler.cpp index 56223ee..4868ea2 100644 --- a/src/raii/curler.cpp +++ b/src/raii/curler.cpp @@ -92,6 +92,18 @@ namespace raii{ setopt(CURLOPT_SSLVERSION, version); return *this; } + curler& curler::setwritefun(write_function w){ + return setopt(CURLOPT_WRITEFUNCTION, w); + } + curler& curler::setwritedata(void* data){ + return setopt(CURLOPT_WRITEDATA, data); + } + curler& curler::setreadfun(read_function r){ + return setopt(CURLOPT_READFUNCTION, r); + } + curler& curler::setreaddata(void* data){ + return setopt(CURLOPT_READDATA, data); + } void curler::reset(void){ curl_easy_reset(m_curl);