From 86bfede948db03181cdf2989ee33b96905366f1b Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Mon, 22 Apr 2024 23:21:13 +0200 Subject: [PATCH] libstore: use curl functions for reading headers don't reimplement header parsing. this was only really needed due to the ancient github bug we no longer care about, everything else we have done in custom code can also be done using curl itself. doing this also fixes possible sources of header smuggling (because the header function didn't unfold headers and we'd trim them before parsing, which would've made us read contents of one header as a fully formed header in itself). this is a slight behavior change because we now honor only the first instance of a given header where previous behavior was to honor either the last or a combination of all of them (accept-ranges was logical-or'd by accident). Change-Id: I93cb93ddb91ab98c8991f846014926f6ef039fdb --- src/libstore/filetransfer.cc | 70 +++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index adbeb58ca..f6717fc23 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -51,6 +51,7 @@ struct curlFileTransfer : public FileTransfer Callback callback; CURL * req = 0; bool active = false; // whether the handle has been added to the multi object + bool headersProcessed = false; std::string statusMsg; unsigned int attempt = 0; @@ -151,9 +152,33 @@ struct curlFileTransfer : public FileTransfer std::exception_ptr writeException; + std::optional getHeader(const char * name) + { + curl_header * result; + auto e = curl_easy_header(req, name, 0, CURLH_HEADER, -1, &result); + if (e == CURLHE_OK) { + return result->value; + } else if (e == CURLHE_MISSING || e == CURLHE_NOHEADERS) { + return std::nullopt; + } else { + throw nix::Error("unexpected error from curl_easy_header(): %i", e); + } + } + size_t writeCallback(void * contents, size_t size, size_t nmemb) { try { + if (!headersProcessed) { + if (auto h = getHeader("content-encoding")) { + encoding = std::move(*h); + } + if (auto h = getHeader("accept-ranges"); h && *h == "bytes") { + acceptRanges = true; + } + + headersProcessed = true; + } + size_t realSize = size * nmemb; result.bodySize += realSize; @@ -196,31 +221,7 @@ struct curlFileTransfer : public FileTransfer statusMsg = trim(match.str(1)); acceptRanges = false; encoding = ""; - } else { - - auto i = line.find(':'); - if (i != std::string::npos) { - std::string name = toLower(trim(line.substr(0, i))); - - if (name == "etag") { - result.etag = trim(line.substr(i + 1)); - } - - else if (name == "content-encoding") - encoding = trim(line.substr(i + 1)); - - else if (name == "accept-ranges" && toLower(trim(line.substr(i + 1))) == "bytes") - acceptRanges = true; - - else if (name == "link" || name == "x-amz-meta-link") { - auto value = trim(line.substr(i + 1)); - static std::regex linkRegex("<([^>]*)>; rel=\"immutable\"", std::regex::extended | std::regex::icase); - if (std::smatch match; std::regex_match(value, match, linkRegex)) - result.immutableUrl = match.str(1); - else - debug("got invalid link header '%s'", value); - } - } + headersProcessed = false; } return realSize; } @@ -364,6 +365,25 @@ struct curlFileTransfer : public FileTransfer } } + auto link = getHeader("link"); + if (!link) { + link = getHeader("x-amz-meta-link"); + } + if (link) { + static std::regex linkRegex( + "<([^>]*)>; rel=\"immutable\"", std::regex::extended | std::regex::icase + ); + if (std::smatch match; std::regex_match(*link, match, linkRegex)) { + result.immutableUrl = match.str(1); + } else { + debug("got invalid link header '%s'", *link); + } + } + + if (auto etag = getHeader("etag")) { + result.etag = std::move(*etag); + } + if (writeException) failEx(writeException);