diff --git a/Makefile.am b/Makefile.am index 8159c54e4..d05a920a8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1277,8 +1277,10 @@ if ENABLE_CURL libinput_a_SOURCES += \ src/input/IcyInputStream.cxx src/input/IcyInputStream.hxx \ src/input/plugins/CurlInputPlugin.cxx src/input/plugins/CurlInputPlugin.hxx \ + src/lib/curl/Version.cxx src/lib/curl/Version.hxx \ src/lib/curl/Global.cxx src/lib/curl/Global.hxx \ - src/lib/curl/Request.hxx \ + src/lib/curl/Request.cxx src/lib/curl/Request.hxx \ + src/lib/curl/Handler.hxx \ src/lib/curl/Easy.hxx \ src/lib/curl/Multi.hxx \ src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index b76de8ec6..5c202973f 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -22,6 +22,7 @@ #include "lib/curl/Easy.hxx" #include "lib/curl/Global.hxx" #include "lib/curl/Request.hxx" +#include "lib/curl/Handler.hxx" #include "../AsyncInputStream.hxx" #include "../IcyInputStream.hxx" #include "../InputPlugin.hxx" @@ -59,17 +60,13 @@ static const size_t CURL_MAX_BUFFERED = 512 * 1024; */ static const size_t CURL_RESUME_AT = 384 * 1024; -struct CurlInputStream final : public AsyncInputStream, CurlRequest { +struct CurlInputStream final : public AsyncInputStream, CurlResponseHandler { /* some buffers which were passed to libcurl, which we have too free */ char range[32]; struct curl_slist *request_headers; - /** the curl handles */ - CurlEasy easy; - - /** error message provided by libcurl */ - char error_buffer[CURL_ERROR_SIZE]; + CurlRequest *request = nullptr; /** parser for icy-metadata */ IcyInputStream *icy; @@ -79,7 +76,6 @@ struct CurlInputStream final : public AsyncInputStream, CurlRequest { CURL_MAX_BUFFERED, CURL_RESUME_AT), request_headers(nullptr), - easy(nullptr), icy(new IcyInputStream(this)) { } @@ -113,37 +109,18 @@ struct CurlInputStream final : public AsyncInputStream, CurlRequest { */ void SeekInternal(offset_type new_offset); - /** - * Called when a new response begins. This is used to discard - * headers from previous responses (for example authentication - * and redirects). - */ - void ResponseBoundary(); - - void HeaderReceived(const char *name, std::string &&value); - - size_t DataReceived(const void *ptr, size_t size); - - /** - * A HTTP request is finished. - * - * Runs in the I/O thread. The caller must not hold locks. - */ - void RequestDone(CURLcode result, long status); - - /* virtual methods from CurlRequest */ - void Done(CURLcode result) override; + /* virtual methods from CurlResponseHandler */ + void OnHeaders(unsigned status, + std::multimap &&headers) override; + void OnData(ConstBuffer data) override; + void OnEnd() override; + void OnError(std::exception_ptr e) override; /* virtual methods from AsyncInputStream */ virtual void DoResume() override; virtual void DoSeek(offset_type new_offset) override; }; -/** - * libcurl version number encoded in a 24 bit integer. - */ -static unsigned curl_version_num; - /** libcurl should accept "ICY 200 OK" */ static struct curl_slist *http_200_aliases; @@ -163,17 +140,7 @@ CurlInputStream::DoResume() assert(io_thread_inside()); mutex.unlock(); - - curl_easy_pause(easy.Get(), CURLPAUSE_CONT); - - if (curl_version_num < 0x072000) - /* libcurl older than 7.32.0 does not update - its sockets after curl_easy_pause(); force - libcurl to do it now */ - curl_global->ResumeSockets(); - - curl_global->InvalidateSockets(); - + request->Resume(); mutex.lock(); } @@ -182,12 +149,11 @@ CurlInputStream::FreeEasy() { assert(io_thread_inside()); - if (!easy) + if (request == nullptr) return; - curl_global->Remove(this); - - easy = nullptr; + delete request; + request = nullptr; curl_slist_free_all(request_headers); request_headers = nullptr; @@ -200,44 +166,115 @@ CurlInputStream::FreeEasyIndirect() FreeEasy(); curl_global->InvalidateSockets(); }); - - assert(!easy); } -inline void -CurlInputStream::RequestDone(CURLcode result, long status) +void +CurlInputStream::OnHeaders(unsigned status, + std::multimap &&headers) { assert(io_thread_inside()); assert(!postponed_exception); - FreeEasy(); - AsyncInputStream::SetClosed(); + if (status < 200 || status >= 300) + throw FormatRuntimeError("got HTTP status %ld", status); const std::lock_guard protect(mutex); - if (result != CURLE_OK) { - postponed_exception = std::make_exception_ptr(FormatRuntimeError("curl failed: %s", - error_buffer)); - } else if (status < 200 || status >= 300) { - postponed_exception = std::make_exception_ptr(FormatRuntimeError("got HTTP status %ld", - status)); + if (IsSeekPending()) { + /* don't update metadata while seeking */ + SeekDone(); + return; } + if (!icy->IsEnabled() && + headers.find("accept-ranges") != headers.end()) + /* a stream with icy-metadata is not seekable */ + seekable = true; + + auto i = headers.find("content-length"); + if (i != headers.end()) + size = offset + ParseUint64(i->second.c_str()); + + i = headers.find("content-type"); + if (i != headers.end()) + SetMimeType(std::move(i->second)); + + i = headers.find("icy-name"); + if (i == headers.end()) { + i = headers.find("ice-name"); + if (i == headers.end()) + i = headers.find("x-audiocast-name"); + } + + if (i != headers.end()) { + TagBuilder tag_builder; + tag_builder.AddItem(TAG_NAME, i->second.c_str()); + + SetTag(tag_builder.CommitNew()); + } + + if (!icy->IsEnabled()) { + i = headers.find("icy-metaint"); + + if (i != headers.end()) { + size_t icy_metaint = ParseUint64(i->second.c_str()); +#ifndef WIN32 + /* Windows doesn't know "%z" */ + FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint); +#endif + + if (icy_metaint > 0) { + icy->Enable(icy_metaint); + + /* a stream with icy-metadata is not + seekable */ + seekable = false; + } + } + } + + SetReady(); +} + +void +CurlInputStream::OnData(ConstBuffer data) +{ + assert(data.size > 0); + + const std::lock_guard protect(mutex); + + if (IsSeekPending()) + SeekDone(); + + if (data.size > GetBufferSpace()) { + AsyncInputStream::Pause(); + throw CurlRequest::Pause(); + } + + AppendToBuffer(data.data, data.size); +} + +void +CurlInputStream::OnEnd() +{ + cond.broadcast(); + + AsyncInputStream::SetClosed(); +} + +void +CurlInputStream::OnError(std::exception_ptr e) +{ + postponed_exception = std::move(e); + if (IsSeekPending()) SeekDone(); else if (!IsReady()) SetReady(); else cond.broadcast(); -} -void -CurlInputStream::Done(CURLcode result) -{ - long status = 0; - curl_easy_getinfo(easy.Get(), CURLINFO_RESPONSE_CODE, &status); - - RequestDone(result, status); + AsyncInputStream::SetClosed(); } /* @@ -258,8 +295,6 @@ input_curl_init(const ConfigBlock &block) if (version_info->features & CURL_VERSION_SSL) FormatDebug(curl_domain, "with %s", version_info->ssl_version); - - curl_version_num = version_info->version_num; } http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK"); @@ -309,179 +344,37 @@ CurlInputStream::~CurlInputStream() FreeEasyIndirect(); } -inline void -CurlInputStream::ResponseBoundary() -{ - /* undo all effects of HeaderReceived() because the previous - response was not applicable for this stream */ - - if (IsSeekPending()) - /* don't update metadata while seeking */ - return; - - seekable = false; - size = UNKNOWN_SIZE; - ClearMimeType(); - ClearTag(); - - // TODO: reset the IcyInputStream? -} - -inline void -CurlInputStream::HeaderReceived(const char *name, std::string &&value) -{ - if (IsSeekPending()) - /* don't update metadata while seeking */ - return; - - if (StringEqualsCaseASCII(name, "accept-ranges")) { - /* a stream with icy-metadata is not seekable */ - if (!icy->IsEnabled()) - seekable = true; - } else if (StringEqualsCaseASCII(name, "content-length")) { - size = offset + ParseUint64(value.c_str()); - } else if (StringEqualsCaseASCII(name, "content-type")) { - SetMimeType(std::move(value)); - } else if (StringEqualsCaseASCII(name, "icy-name") || - StringEqualsCaseASCII(name, "ice-name") || - StringEqualsCaseASCII(name, "x-audiocast-name")) { - TagBuilder tag_builder; - tag_builder.AddItem(TAG_NAME, value.c_str()); - - SetTag(tag_builder.CommitNew()); - } else if (StringEqualsCaseASCII(name, "icy-metaint")) { - if (icy->IsEnabled()) - return; - - size_t icy_metaint = ParseUint64(value.c_str()); -#ifndef WIN32 - /* Windows doesn't know "%z" */ - FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint); -#endif - - if (icy_metaint > 0) { - icy->Enable(icy_metaint); - - /* a stream with icy-metadata is not - seekable */ - seekable = false; - } - } -} - -/** called by curl when new data is available */ -static size_t -input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - CurlInputStream &c = *(CurlInputStream *)stream; - - size *= nmemb; - - const char *header = (const char *)ptr; - if (size > 5 && memcmp(header, "HTTP/", 5) == 0) { - c.ResponseBoundary(); - return size; - } - - const char *end = header + size; - - char name[64]; - - const char *value = (const char *)memchr(header, ':', size); - if (value == nullptr || (size_t)(value - header) >= sizeof(name)) - return size; - - memcpy(name, header, value - header); - name[value - header] = 0; - - /* skip the colon */ - - ++value; - - /* strip the value */ - - value = StripLeft(value, end); - end = StripRight(value, end); - - c.HeaderReceived(name, std::string(value, end)); - return size; -} - -inline size_t -CurlInputStream::DataReceived(const void *ptr, size_t received_size) -{ - assert(received_size > 0); - - const std::lock_guard protect(mutex); - - if (IsSeekPending()) - SeekDone(); - - if (received_size > GetBufferSpace()) { - AsyncInputStream::Pause(); - return CURL_WRITEFUNC_PAUSE; - } - - AppendToBuffer(ptr, received_size); - return received_size; -} - -/** called by curl when new data is available */ -static size_t -input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) -{ - CurlInputStream &c = *(CurlInputStream *)stream; - - size *= nmemb; - if (size == 0) - return 0; - - return c.DataReceived(ptr, size); -} - void CurlInputStream::InitEasy() { - easy = CurlEasy(); + request = new CurlRequest(*curl_global, GetURI(), *this); - easy.SetOption(CURLOPT_USERAGENT, "Music Player Daemon " VERSION); - easy.SetOption(CURLOPT_HEADERFUNCTION, input_curl_headerfunction); - easy.SetOption(CURLOPT_WRITEHEADER, this); - easy.SetOption(CURLOPT_WRITEFUNCTION, input_curl_writefunction); - easy.SetOption(CURLOPT_WRITEDATA, this); - easy.SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases); - easy.SetOption(CURLOPT_FOLLOWLOCATION, 1l); - easy.SetOption(CURLOPT_NETRC, 1l); - easy.SetOption(CURLOPT_MAXREDIRS, 5l); - easy.SetOption(CURLOPT_FAILONERROR, 1l); - easy.SetOption(CURLOPT_ERRORBUFFER, error_buffer); - easy.SetOption(CURLOPT_NOPROGRESS, 1l); - easy.SetOption(CURLOPT_NOSIGNAL, 1l); - easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l); + request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases); + request->SetOption(CURLOPT_FOLLOWLOCATION, 1l); + request->SetOption(CURLOPT_MAXREDIRS, 5l); + request->SetOption(CURLOPT_FAILONERROR, 1l); if (proxy != nullptr) - easy.SetOption(CURLOPT_PROXY, proxy); + request->SetOption(CURLOPT_PROXY, proxy); if (proxy_port > 0) - easy.SetOption(CURLOPT_PROXYPORT, (long)proxy_port); + request->SetOption(CURLOPT_PROXYPORT, (long)proxy_port); if (proxy_user != nullptr && proxy_password != nullptr) { char proxy_auth_str[1024]; snprintf(proxy_auth_str, sizeof(proxy_auth_str), "%s:%s", proxy_user, proxy_password); - easy.SetOption(CURLOPT_PROXYUSERPWD, proxy_auth_str); + request->SetOption(CURLOPT_PROXYUSERPWD, proxy_auth_str); } - easy.SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l); - easy.SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l); - - easy.SetOption(CURLOPT_URL, GetURI()); + request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l); + request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l); request_headers = nullptr; request_headers = curl_slist_append(request_headers, - "Icy-Metadata: 1"); - easy.SetOption(CURLOPT_HTTPHEADER, request_headers); + "Icy-Metadata: 1"); + request->SetOption(CURLOPT_HTTPHEADER, request_headers); } void @@ -504,10 +397,8 @@ CurlInputStream::SeekInternal(offset_type new_offset) if (offset > 0) { sprintf(range, "%lld-", (long long)offset); - easy.SetOption(CURLOPT_RANGE, range); + request->SetOption(CURLOPT_RANGE, range); } - - curl_global->Add(easy.Get(), *this); } void @@ -528,9 +419,8 @@ CurlInputStream::Open(const char *url, Mutex &mutex, Cond &cond) CurlInputStream *c = new CurlInputStream(url, mutex, cond); try { - c->InitEasy(); BlockingCall(io_thread_get(), [c](){ - curl_global->Add(c->easy.Get(), *c); + c->InitEasy(); }); } catch (...) { delete c; diff --git a/src/lib/curl/Global.cxx b/src/lib/curl/Global.cxx index eb25172b3..c0311b779 100644 --- a/src/lib/curl/Global.cxx +++ b/src/lib/curl/Global.cxx @@ -179,6 +179,8 @@ CurlGlobal::Remove(CURL *easy) assert(easy != nullptr); curl_multi_remove_handle(multi.Get(), easy); + + InvalidateSockets(); } static CurlRequest * diff --git a/src/lib/curl/Handler.hxx b/src/lib/curl/Handler.hxx new file mode 100644 index 000000000..99d0b11af --- /dev/null +++ b/src/lib/curl/Handler.hxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008-2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CURL_HANDLER_HXX +#define CURL_HANDLER_HXX + +#include "util/ConstBuffer.hxx" + +#include +#include +#include + +class CurlResponseHandler { +public: + virtual void OnHeaders(unsigned status, + std::multimap &&headers) = 0; + virtual void OnData(ConstBuffer data) = 0; + virtual void OnEnd() = 0; + virtual void OnError(std::exception_ptr e) = 0; +}; + +#endif diff --git a/src/lib/curl/Request.cxx b/src/lib/curl/Request.cxx new file mode 100644 index 000000000..4b197221e --- /dev/null +++ b/src/lib/curl/Request.cxx @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2008-2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "Request.hxx" +#include "Global.hxx" +#include "Version.hxx" +#include "Handler.hxx" +#include "util/RuntimeError.hxx" +#include "util/StringUtil.hxx" +#include "util/StringView.hxx" +#include "util/CharUtil.hxx" + +#include + +#include + +#include +#include + +CurlRequest::CurlRequest(CurlGlobal &_global, const char *url, + CurlResponseHandler &_handler) + :global(_global), handler(_handler) +{ + error_buffer[0] = 0; + + easy.SetOption(CURLOPT_PRIVATE, (void *)this); + easy.SetOption(CURLOPT_USERAGENT, "Music Player Daemon " VERSION); + easy.SetOption(CURLOPT_HEADERFUNCTION, _HeaderFunction); + easy.SetOption(CURLOPT_WRITEHEADER, this); + easy.SetOption(CURLOPT_WRITEFUNCTION, WriteFunction); + easy.SetOption(CURLOPT_WRITEDATA, this); + easy.SetOption(CURLOPT_NETRC, 1l); + easy.SetOption(CURLOPT_ERRORBUFFER, error_buffer); + easy.SetOption(CURLOPT_NOPROGRESS, 1l); + easy.SetOption(CURLOPT_NOSIGNAL, 1l); + easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l); + easy.SetOption(CURLOPT_URL, url); + + global.Add(easy.Get(), *this); +} + +CurlRequest::~CurlRequest() +{ + FreeEasy(); +} + +void +CurlRequest::FreeEasy() +{ + if (!easy) + return; + + global.Remove(easy.Get()); + easy = nullptr; +} + +void +CurlRequest::Resume() +{ + curl_easy_pause(easy.Get(), CURLPAUSE_CONT); + + if (IsCurlOlderThan(0x072000)) + /* libcurl older than 7.32.0 does not update + its sockets after curl_easy_pause(); force + libcurl to do it now */ + global.ResumeSockets(); + + global.InvalidateSockets(); +} + +bool +CurlRequest::FinishHeaders() +{ + if (state != State::HEADERS) + return true; + + state = State::BODY; + + long status = 0; + curl_easy_getinfo(easy.Get(), CURLINFO_RESPONSE_CODE, &status); + + try { + handler.OnHeaders(status, std::move(headers)); + return true; + } catch (...) { + state = State::CLOSED; + handler.OnError(std::current_exception()); + return false; + } +} + +void +CurlRequest::FinishBody() +{ + if (!FinishHeaders()) + return; + + if (state != State::BODY) + return; + + state = State::CLOSED; + handler.OnEnd(); +} + +void +CurlRequest::Done(CURLcode result) +{ + FreeEasy(); + + try { + if (result != CURLE_OK) { + StripRight(error_buffer); + const char *msg = error_buffer; + if (*msg == 0) + msg = curl_easy_strerror(result); + throw FormatRuntimeError("CURL failed: %s", msg); + } + } catch (...) { + state = State::CLOSED; + handler.OnError(std::current_exception()); + return; + } + + FinishBody(); +} + +inline void +CurlRequest::HeaderFunction(StringView s) +{ + if (state > State::HEADERS) + return; + + if (s.size > 5 && memcmp(s.data, "HTTP/", 5) == 0) { + /* this is the boundary to a new response, for example + after a redirect */ + headers.clear(); + return; + } + + const char *header = s.data; + const char *end = StripRight(header, header + s.size); + + const char *value = s.Find(':'); + if (value == nullptr) + return; + + std::string name(header, value); + std::transform(name.begin(), name.end(), name.begin(), ToLowerASCII); + + /* skip the colon */ + + ++value; + + /* strip the value */ + + value = StripLeft(value, end); + end = StripRight(value, end); + + headers.emplace(std::move(name), std::string(value, end)); +} + +size_t +CurlRequest::_HeaderFunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + CurlRequest &c = *(CurlRequest *)stream; + + size *= nmemb; + + c.HeaderFunction({(const char *)ptr, size}); + return size; +} + +inline size_t +CurlRequest::DataReceived(const void *ptr, size_t received_size) +{ + assert(received_size > 0); + + if (!FinishHeaders()) + return 0; + + try { + handler.OnData({ptr, received_size}); + return received_size; + } catch (Pause) { + return CURL_WRITEFUNC_PAUSE; + } catch (...) { + state = State::CLOSED; + handler.OnError(std::current_exception()); + return 0; + } + +} + +size_t +CurlRequest::WriteFunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + CurlRequest &c = *(CurlRequest *)stream; + + size *= nmemb; + if (size == 0) + return 0; + + return c.DataReceived(ptr, size); +} diff --git a/src/lib/curl/Request.hxx b/src/lib/curl/Request.hxx index 65cb21da0..2a5c55d0a 100644 --- a/src/lib/curl/Request.hxx +++ b/src/lib/curl/Request.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2016 Max Kellermann + * Copyright (C) 2008-2017 Max Kellermann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -30,14 +30,85 @@ #ifndef CURL_REQUEST_HXX #define CURL_REQUEST_HXX -#include +#include "Easy.hxx" + +#include +#include + +struct StringView; +class CurlGlobal; +class CurlResponseHandler; class CurlRequest { + CurlGlobal &global; + + CurlResponseHandler &handler; + + /** the curl handle */ + CurlEasy easy; + + enum class State { + HEADERS, + BODY, + CLOSED, + } state = State::HEADERS; + + std::multimap headers; + + /** error message provided by libcurl */ + char error_buffer[CURL_ERROR_SIZE]; + public: + CurlRequest(CurlGlobal &_global, const char *url, + CurlResponseHandler &_handler); + ~CurlRequest(); + + CurlRequest(const CurlRequest &) = delete; + CurlRequest &operator=(const CurlRequest &) = delete; + + CURL *Get() { + return easy.Get(); + } + + template + void SetOption(CURLoption option, T value) { + easy.SetOption(option, value); + } + + /** + * CurlResponseHandler::OnData() shall throw this to pause the + * stream. Call Resume() to resume the transfer. + */ + struct Pause {}; + + void Resume(); + /** * A HTTP request is finished. Called by #CurlGlobal. */ - virtual void Done(CURLcode result) = 0; + void Done(CURLcode result); + +private: + /** + * Frees the current "libcurl easy" handle, and everything + * associated with it. + */ + void FreeEasy(); + + bool FinishHeaders(); + void FinishBody(); + + size_t DataReceived(const void *ptr, size_t size); + + void HeaderFunction(StringView s); + + /** called by curl when new data is available */ + static size_t _HeaderFunction(void *ptr, size_t size, size_t nmemb, + void *stream); + + /** called by curl when new data is available */ + static size_t WriteFunction(void *ptr, size_t size, size_t nmemb, + void *stream); }; #endif diff --git a/src/lib/curl/Version.cxx b/src/lib/curl/Version.cxx new file mode 100644 index 000000000..46132b5fe --- /dev/null +++ b/src/lib/curl/Version.cxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Version.hxx" + +#include + +bool +IsCurlOlderThan(unsigned version_num) +{ + const auto *const info = curl_version_info(CURLVERSION_FIRST); + return info == nullptr || info->version_num < version_num; +} diff --git a/src/lib/curl/Version.hxx b/src/lib/curl/Version.hxx new file mode 100644 index 000000000..0b4f13707 --- /dev/null +++ b/src/lib/curl/Version.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CURL_VERSION_HXX +#define CURL_VERSION_HXX + +#include "Compiler.h" + +gcc_const +bool +IsCurlOlderThan(unsigned version_num); + +#endif