libstore: check that transfer headers don't change during retries
etag changing implies with high probability that the content of the
resource changed. immutable url changing implies that the immutable
url we got previously was wrong, which is probably a server bug. if
the encoding changes our decoding will break completely, so that is
also very illegal. one notable change we still allow is etags going
away completely, mostly since this does not imply any data changes.
Change-Id: I0220ceddc3fd732cd1b3bb39b40021cc631baadc
This commit is contained in:
parent
7c716b9716
commit
212a14bb1f
|
@ -159,6 +159,14 @@ struct curlFileTransfer : public FileTransfer
|
||||||
failEx(std::make_exception_ptr(std::forward<T>(e)));
|
failEx(std::make_exception_ptr(std::forward<T>(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[noreturn]]
|
||||||
|
void throwChangedTarget(std::string_view what, std::string_view from, std::string_view to)
|
||||||
|
{
|
||||||
|
throw FileTransferError(
|
||||||
|
Misc, {}, "uri %s changed %s from %s to %s during transfer", uri, what, from, to
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void maybeFinishSetup()
|
void maybeFinishSetup()
|
||||||
{
|
{
|
||||||
if (phase > retrySetup) {
|
if (phase > retrySetup) {
|
||||||
|
@ -169,14 +177,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
curl_easy_getinfo(req, CURLINFO_EFFECTIVE_URL, &effectiveUriCStr);
|
curl_easy_getinfo(req, CURLINFO_EFFECTIVE_URL, &effectiveUriCStr);
|
||||||
if (effectiveUriCStr) {
|
if (effectiveUriCStr) {
|
||||||
if (!result.effectiveUri.empty() && result.effectiveUri != effectiveUriCStr) {
|
if (!result.effectiveUri.empty() && result.effectiveUri != effectiveUriCStr) {
|
||||||
throw FileTransferError(
|
throwChangedTarget("final destination", result.effectiveUri, effectiveUriCStr);
|
||||||
Misc,
|
|
||||||
{},
|
|
||||||
"uri %s changed final destination from %s to %s during transfer",
|
|
||||||
uri,
|
|
||||||
result.effectiveUri,
|
|
||||||
effectiveUriCStr
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
result.effectiveUri = effectiveUriCStr;
|
result.effectiveUri = effectiveUriCStr;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +187,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
phase = transferring;
|
phase = transferring;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::exception_ptr writeException;
|
std::exception_ptr callbackException;
|
||||||
|
|
||||||
size_t writeCallback(void * contents, size_t size, size_t nmemb)
|
size_t writeCallback(void * contents, size_t size, size_t nmemb)
|
||||||
{
|
{
|
||||||
|
@ -206,7 +207,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
|
|
||||||
return realSize;
|
return realSize;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
writeException = std::current_exception();
|
callbackException = std::current_exception();
|
||||||
return CURL_WRITEFUNC_ERROR;
|
return CURL_WRITEFUNC_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,30 +218,39 @@ struct curlFileTransfer : public FileTransfer
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t headerCallback(void * contents, size_t size, size_t nmemb)
|
size_t headerCallback(void * contents, size_t size, size_t nmemb)
|
||||||
{
|
try {
|
||||||
size_t realSize = size * nmemb;
|
size_t realSize = size * nmemb;
|
||||||
std::string line(static_cast<char *>(contents), realSize);
|
std::string line(static_cast<char *>(contents), realSize);
|
||||||
printMsg(lvlVomit, "got header for '%s': %s", uri, trim(line));
|
printMsg(lvlVomit, "got header for '%s': %s", uri, trim(line));
|
||||||
|
|
||||||
static std::regex statusLine("HTTP/[^ ]+ +[0-9]+(.*)", std::regex::extended | std::regex::icase);
|
static std::regex statusLine("HTTP/[^ ]+ +[0-9]+(.*)", std::regex::extended | std::regex::icase);
|
||||||
if (std::smatch match; std::regex_match(line, match, statusLine)) {
|
if (std::smatch match; std::regex_match(line, match, statusLine)) {
|
||||||
result.etag = "";
|
|
||||||
downloadData.clear();
|
downloadData.clear();
|
||||||
bodySize = 0;
|
bodySize = 0;
|
||||||
statusMsg = trim(match.str(1));
|
statusMsg = trim(match.str(1));
|
||||||
acceptRanges = false;
|
acceptRanges = false;
|
||||||
encoding = "";
|
|
||||||
} else {
|
} else {
|
||||||
auto i = line.find(':');
|
auto i = line.find(':');
|
||||||
if (i != std::string::npos) {
|
if (i != std::string::npos) {
|
||||||
std::string name = toLower(trim(line.substr(0, i)));
|
std::string name = toLower(trim(line.substr(0, i)));
|
||||||
|
|
||||||
if (name == "etag") {
|
if (name == "etag") {
|
||||||
result.etag = trim(line.substr(i + 1));
|
// NOTE we don't check that the etag hasn't gone *missing*. technically
|
||||||
|
// this is not an error as long as we get the same data from the remote.
|
||||||
|
auto etag = trim(line.substr(i + 1));
|
||||||
|
if (!result.etag.empty() && result.etag != etag) {
|
||||||
|
throwChangedTarget("ETag", result.etag, etag);
|
||||||
|
}
|
||||||
|
result.etag = std::move(etag);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (name == "content-encoding")
|
else if (name == "content-encoding") {
|
||||||
encoding = trim(line.substr(i + 1));
|
auto encoding = trim(line.substr(i + 1));
|
||||||
|
if (!this->encoding.empty() && this->encoding != encoding) {
|
||||||
|
throwChangedTarget("encoding", this->encoding, encoding);
|
||||||
|
}
|
||||||
|
this->encoding = std::move(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
else if (name == "accept-ranges" && toLower(trim(line.substr(i + 1))) == "bytes")
|
else if (name == "accept-ranges" && toLower(trim(line.substr(i + 1))) == "bytes")
|
||||||
acceptRanges = true;
|
acceptRanges = true;
|
||||||
|
@ -248,14 +258,20 @@ struct curlFileTransfer : public FileTransfer
|
||||||
else if (name == "link" || name == "x-amz-meta-link") {
|
else if (name == "link" || name == "x-amz-meta-link") {
|
||||||
auto value = trim(line.substr(i + 1));
|
auto value = trim(line.substr(i + 1));
|
||||||
static std::regex linkRegex("<([^>]*)>; rel=\"immutable\"", std::regex::extended | std::regex::icase);
|
static std::regex linkRegex("<([^>]*)>; rel=\"immutable\"", std::regex::extended | std::regex::icase);
|
||||||
if (std::smatch match; std::regex_match(value, match, linkRegex))
|
if (std::smatch match; std::regex_match(value, match, linkRegex)) {
|
||||||
|
if (result.immutableUrl && result.immutableUrl != match.str(1)) {
|
||||||
|
throwChangedTarget("immutable url", *result.immutableUrl, match.str(1));
|
||||||
|
}
|
||||||
result.immutableUrl = match.str(1);
|
result.immutableUrl = match.str(1);
|
||||||
else
|
} else
|
||||||
debug("got invalid link header '%s'", value);
|
debug("got invalid link header '%s'", value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return realSize;
|
return realSize;
|
||||||
|
} catch (...) {
|
||||||
|
callbackException = std::current_exception();
|
||||||
|
return CURL_WRITEFUNC_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t headerCallbackWrapper(void * contents, size_t size, size_t nmemb, void * userp)
|
static size_t headerCallbackWrapper(void * contents, size_t size, size_t nmemb, void * userp)
|
||||||
|
@ -397,8 +413,8 @@ struct curlFileTransfer : public FileTransfer
|
||||||
downloadData = decompress(encoding, downloadData);
|
downloadData = decompress(encoding, downloadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writeException)
|
if (callbackException)
|
||||||
failEx(writeException);
|
failEx(callbackException);
|
||||||
|
|
||||||
else if (code == CURLE_OK && successfulStatuses.count(httpStatus))
|
else if (code == CURLE_OK && successfulStatuses.count(httpStatus))
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue