summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Kalnischkies <david@kalnischkies.de>2023-01-28 22:17:44 +0100
committerDavid Kalnischkies <david@kalnischkies.de>2023-03-03 17:51:05 +0100
commitacbfdf0533602a05de066aa86d1f756b5fe0f4a3 (patch)
tree508ceccdaff78234a7e8c3f5be93b0f6d0494a28
parentedcdc251c527141bddb502e799d9a3911a73841b (diff)
Detect trimmed changelogs and pick online instead
We only check the start of these lines to avoid hard coding the exact command and we pick 150 as maximum line length as the longest package name on my system is apparently 75 characters long. We could choose longer or shorter without much issue as over-length just means we mishandle the rest of the line as a new line and it should be really unlikely that a) lines are that long in this file and b) that such long lines contain one of our trigger sequences – but even if, all we do is start a download of an online file. Could be worse. This auto-detection can be avoided by setting Acquire::Changelogs::AlwaysOnline (or Origin specific sub options) to "true" if you always want the changelog from an online source. The reverse – setting it to "false" in the hope it would not get the changelog from an online source – was not and is still not possible. Closes: #1024457
-rw-r--r--apt-pkg/acquire-item.cc67
-rwxr-xr-xtest/integration/test-apt-get-changelog7
2 files changed, 63 insertions, 11 deletions
diff --git a/apt-pkg/acquire-item.cc b/apt-pkg/acquire-item.cc
index 384101de3..2014a50d5 100644
--- a/apt-pkg/acquire-item.cc
+++ b/apt-pkg/acquire-item.cc
@@ -3710,17 +3710,62 @@ std::string pkgAcqChangelog::URI(pkgCache::VerIterator const &Ver) /*{{{*/
pkgCache::PkgIterator const Pkg = Ver.ParentPkg();
if (Pkg->CurrentVer != 0 && Pkg.CurrentVer() == Ver)
{
- std::string const root = _config->FindDir("Dir");
- std::string const basename = root + std::string("usr/share/doc/") + Pkg.Name() + "/changelog";
- std::string const debianname = basename + ".Debian";
- if (FileExists(debianname))
- return "copy://" + debianname;
- else if (FileExists(debianname + ".gz"))
- return "store://" + debianname + ".gz";
- else if (FileExists(basename))
- return "copy://" + basename;
- else if (FileExists(basename + ".gz"))
- return "store://" + basename + ".gz";
+ auto const LocalFile = [](pkgCache::PkgIterator const &Pkg) -> std::string {
+ std::string const root = _config->FindDir("Dir");
+ std::string const basename = root + std::string("usr/share/doc/") + Pkg.Name() + "/changelog";
+ std::string const debianname = basename + ".Debian";
+ auto const exts = APT::Configuration::getCompressorExtensions(); // likely we encounter only .gz
+ for (auto file : { debianname, basename })
+ {
+ if (FileExists(file))
+ return "copy://" + file;
+ for (auto const& ext : exts)
+ {
+ auto const compressedfile = file + ext;
+ if (FileExists(compressedfile))
+ return "store://" + compressedfile;
+ }
+ }
+ return "";
+ }(Pkg);
+ if (not LocalFile.empty())
+ {
+ _error->PushToStack();
+ FileFd trimmed;
+ if (APT::String::Startswith(LocalFile, "copy://"))
+ trimmed.Open(LocalFile.substr(7), FileFd::ReadOnly, FileFd::None);
+ else
+ trimmed.Open(LocalFile.substr(8), FileFd::ReadOnly, FileFd::Extension);
+
+ bool trimmedFile = false;
+ if (trimmed.IsOpen())
+ {
+ /* We want to look at the last line… in a (likely) compressed file,
+ which means we more or less have to uncompress the entire file.
+ So we skip ahead the filesize minus our choosen line size in
+ the hope that changelogs don't grow by being compressed to
+ avoid doing this costly dance on at least a bit of the file. */
+ char buffer[150];
+ if (auto const filesize = trimmed.FileSize(); filesize > sizeof(buffer))
+ trimmed.Skip(filesize - sizeof(buffer));
+ std::string_view giveaways[] = {
+ "# To read the complete changelog use", // Debian
+ "# For older changelog entries, run", // Ubuntu
+ };
+ while (trimmed.ReadLine(buffer, sizeof(buffer)) != nullptr)
+ {
+ std::string_view const line{buffer};
+ if (std::any_of(std::begin(giveaways), std::end(giveaways), [=](auto const gw) { return line.compare(0, gw.size(), gw) == 0; }))
+ {
+ trimmedFile = true;
+ break;
+ }
+ }
+ }
+ _error->RevertToStack();
+ if (not trimmedFile)
+ return LocalFile;
+ }
}
}
diff --git a/test/integration/test-apt-get-changelog b/test/integration/test-apt-get-changelog
index b216f6f9a..c0eecba8b 100755
--- a/test/integration/test-apt-get-changelog
+++ b/test/integration/test-apt-get-changelog
@@ -127,6 +127,13 @@ testsuccessequal "'http://localhost:${APTHTTPPORT}/pool/main/a/awesome/awesome_4
testsuccessequal "'http://localhost:${APTHTTPPORT}/pool/main/a/awesome/awesome_42/changelog' awesome.changelog" apt changelog awesome --print-uris -o Acquire::Changelogs::AlwaysOnline=false -o Acquire::Changelogs::AlwaysOnline::Origin::Ubuntu=true
testsuccessequal "'copy://${TMPWORKINGDIRECTORY}/rootdir/usr/share/doc/awesome/changelog' awesome.changelog" apt changelog awesome --print-uris -o Acquire::Changelogs::AlwaysOnline=false -o Acquire::Changelogs::AlwaysOnline::Origin::Debian=true
+printf '\n# Older entries have been removed from this changelog.' >> rootdir/usr/share/doc/awesome/changelog
+testsuccessequal "'copy://${TMPWORKINGDIRECTORY}/rootdir/usr/share/doc/awesome/changelog' awesome.changelog" apt changelog awesome --print-uris -o Acquire::Changelogs::AlwaysOnline=false
+printf '\n# To read the complete changelog use `apt changelog awesome`.' >> rootdir/usr/share/doc/awesome/changelog
+testsuccessequal "'http://localhost:${APTHTTPPORT}/pool/main/a/awesome/awesome_42/changelog' awesome.changelog" apt changelog awesome --print-uris -o Acquire::Changelogs::AlwaysOnline=false
+printf '\n# No guarantees the trigger is the last line' >> rootdir/usr/share/doc/awesome/changelog
+testsuccessequal "'http://localhost:${APTHTTPPORT}/pool/main/a/awesome/awesome_42/changelog' awesome.changelog" apt changelog awesome --print-uris -o Acquire::Changelogs::AlwaysOnline=false
+
testsuccess apt changelog awesome -d
testfilestats 'awesome.changelog' '%U:%G:%a' '=' "${TEST_DEFAULT_USER}:${TEST_DEFAULT_GROUP}:644"
head -n 3 awesome.changelog > awesome.change