You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
3.5 KiB
C++

// SPDX-License-Identifier: BSD-2-Clause
// author: Max Kellermann <max.kellermann@gmail.com>
#include "Adapter.hxx"
#include "Easy.hxx"
#include "Handler.hxx"
#include "util/CharUtil.hxx"
#include "util/StringSplit.hxx"
#include "util/StringStrip.hxx"
#include <algorithm>
#include <cassert>
using std::string_view_literals::operator""sv;
void
CurlResponseHandlerAdapter::Install(CurlEasy &easy)
{
assert(state == State::UNINITIALISED);
error_buffer[0] = 0;
easy.SetErrorBuffer(error_buffer);
easy.SetHeaderFunction(_HeaderFunction, this);
easy.SetWriteFunction(WriteFunction, this);
curl = easy.Get();
state = State::HEADERS;
}
void
CurlResponseHandlerAdapter::FinishHeaders()
{
assert(state >= State::HEADERS);
if (state != State::HEADERS)
return;
state = State::BODY;
long status = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
handler.OnHeaders(status, std::move(headers));
}
void
CurlResponseHandlerAdapter::FinishBody()
{
FinishHeaders();
if (state != State::BODY)
return;
state = State::CLOSED;
handler.OnEnd();
}
void
CurlResponseHandlerAdapter::Done(CURLcode result) noexcept
{
if (postponed_error) {
state = State::CLOSED;
handler.OnError(std::move(postponed_error));
return;
}
try {
if (result != CURLE_OK) {
StripRight(error_buffer);
const char *msg = error_buffer;
if (*msg == 0)
msg = "CURL failed";
throw Curl::MakeError(result, msg);
}
FinishBody();
} catch (...) {
state = State::CLOSED;
handler.OnError(std::current_exception());
}
}
[[gnu::pure]]
static bool
IsResponseBoundaryHeader(std::string_view s) noexcept
{
return s.starts_with("HTTP/"sv) ||
/* the proprietary "ICY 200 OK" is emitted by
Shoutcast */
s.starts_with("ICY 2"sv);
}
inline void
CurlResponseHandlerAdapter::HeaderFunction(std::string_view s) noexcept
{
if (state > State::HEADERS)
return;
if (IsResponseBoundaryHeader(s)) {
/* this is the boundary to a new response, for example
after a redirect */
headers.clear();
return;
}
auto [_name, value] = Split(StripRight(s), ':');
if (_name.empty() || value.data() == nullptr)
return;
std::string name{_name};
std::transform(name.begin(), name.end(), name.begin(),
static_cast<char(*)(char)>(ToLowerASCII));
headers.emplace(std::move(name), StripLeft(value));
}
std::size_t
CurlResponseHandlerAdapter::_HeaderFunction(char *ptr, std::size_t size,
std::size_t nmemb,
void *stream) noexcept
{
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
size *= nmemb;
c.HeaderFunction({ptr, size});
return size;
}
inline std::size_t
CurlResponseHandlerAdapter::DataReceived(const void *ptr,
std::size_t received_size) noexcept
{
assert(received_size > 0);
try {
FinishHeaders();
handler.OnData({(const std::byte *)ptr, received_size});
return received_size;
} catch (CurlResponseHandler::Pause) {
return CURL_WRITEFUNC_PAUSE;
} catch (...) {
/* from inside this libCURL callback function, we
can't do much, so we remember the exception to be
handled later by Done(), and return 0, causing the
response to be aborted with CURLE_WRITE_ERROR */
postponed_error = std::current_exception();
return 0;
}
}
std::size_t
CurlResponseHandlerAdapter::WriteFunction(char *ptr, std::size_t size,
std::size_t nmemb,
void *stream) noexcept
{
CurlResponseHandlerAdapter &c = *(CurlResponseHandlerAdapter *)stream;
size *= nmemb;
if (size == 0)
return 0;
return c.DataReceived(ptr, size);
}