diff --git a/NEWS b/NEWS index 0736f1be7b..036a6e3563 100644 --- a/NEWS +++ b/NEWS @@ -32,9 +32,13 @@ ver 0.25 (not yet released) * require Meson 1.2 ver 0.24.15 (not yet released) +* protocol + - fix crash on "sticker delete" * playlist - - asx, rss, xspf: limit to 16 MB + - asx, pls, rss, xspf: limit to 16 MB - cue: fix problem playing CUE tracks in music directory root +* player + - fix noise with replay gain and cross-fade ver 0.24.14 (2026/08/13) * storage diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index c802331af8..3b916b509d 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -84,7 +84,12 @@ class DomainHandler { ? sticker_database.Delete(sticker_type, validated_uri) : sticker_database.DeleteValue(sticker_type, validated_uri, name); if (!ret) { - response.FmtError(ACK_ERROR_NO_EXIST, "no such sticker: {:?}", name); + if (name == nullptr) + response.FmtError(ACK_ERROR_NO_EXIST, + "no stickers found: {:?}", uri); + else + response.FmtError(ACK_ERROR_NO_EXIST, + "no such sticker: {:?}", name); return CommandResult::ERROR; } diff --git a/src/input/LimitedInputStream.cxx b/src/input/LimitedInputStream.cxx new file mode 100644 index 0000000000..617789fa4b --- /dev/null +++ b/src/input/LimitedInputStream.cxx @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#include "LimitedInputStream.hxx" + +#include +#include + +LimitedInputStream::LimitedInputStream(InputStreamPtr _input, + const offset_type max_size) + :ProxyInputStream(std::move(_input)), remaining(max_size) +{ + ProxyInputStream::Update(); + Check(); +} + +void +LimitedInputStream::CheckSize() +{ + if (size_checked || !IsReady()) + return; + + if (input->KnownSize() && input->GetRest() > remaining) + throw std::runtime_error("Input stream is too large"); + + size_checked = true; +} + +void +LimitedInputStream::Check() +{ + ProxyInputStream::Check(); + CheckSize(); +} + +std::size_t +LimitedInputStream::Read(std::unique_lock &lock, + std::span dest) +{ + assert(!dest.empty()); + + if (remaining < dest.size()) + dest = dest.first(static_cast(remaining) + 1); + + const std::size_t nbytes = input->Read(lock, dest); + if (nbytes > remaining) + throw std::runtime_error("Input stream is too large"); + + remaining -= nbytes; + CopyAttributes(); + return nbytes; +} diff --git a/src/input/LimitedInputStream.hxx b/src/input/LimitedInputStream.hxx new file mode 100644 index 0000000000..b71300ce60 --- /dev/null +++ b/src/input/LimitedInputStream.hxx @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#pragma once + +#include "ProxyInputStream.hxx" + +/** + * An #InputStream proxy which limits the amount of data that may be + * read from the underlying stream. + */ +class LimitedInputStream final : public ProxyInputStream { + offset_type remaining; + bool size_checked = false; + + void CheckSize(); + +public: + LimitedInputStream(InputStreamPtr _input, offset_type max_size); + + /* virtual methods from class InputStream */ + void Check() override; + std::size_t Read(std::unique_lock &lock, + std::span dest) override; +}; diff --git a/src/input/meson.build b/src/input/meson.build index ea161823c0..922fb1b355 100644 --- a/src/input/meson.build +++ b/src/input/meson.build @@ -23,6 +23,7 @@ input_basic = static_library( 'input_basic', 'AsyncInputStream.cxx', 'LastInputStream.cxx', + 'LimitedInputStream.cxx', 'MemoryInputStream.cxx', 'ProxyInputStream.cxx', 'RewindInputStream.cxx', diff --git a/src/output/Source.cxx b/src/output/Source.cxx index 7c3d557f17..d46a779bbb 100644 --- a/src/output/Source.cxx +++ b/src/output/Source.cxx @@ -185,13 +185,22 @@ AudioOutputSource::FilterChunk(const MusicChunk &chunk) case */ mix_ratio = 1.0f - mix_ratio; + /* use the post-replay-gain format for mixing; + the ReplayGainFilter may convert the sample + format (e.g. S16 to S24_P32 when software + volume is enabled) */ + const SampleFormat cross_fade_format = + replay_gain_filter + ? replay_gain_filter->GetOutAudioFormat().format + : in_audio_format.format; + void *dest = cross_fade_buffer.Get(other_data.size()); memcpy(dest, other_data.data(), other_data.size()); if (!pcm_mix(cross_fade_dither, dest, data.data(), data.size(), - in_audio_format.format, + cross_fade_format, mix_ratio)) throw FmtRuntimeError("Cannot cross-fade format {}", - in_audio_format.format); + cross_fade_format); data = {(const std::byte *)dest, other_data.size()}; } diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx index 81c05913fa..fa4b9a318c 100644 --- a/src/playlist/plugins/PlsPlaylistPlugin.cxx +++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx @@ -4,6 +4,7 @@ #include "PlsPlaylistPlugin.hxx" #include "../PlaylistPlugin.hxx" #include "../MemorySongEnumerator.hxx" +#include "input/LimitedInputStream.hxx" #include "input/TextInputStream.hxx" #include "input/InputStream.hxx" #include "song/DetachedSong.hxx" @@ -19,6 +20,9 @@ using std::string_view_literals::operator""sv; +/* PLS playlists are materialized before any songs are returned. */ +static constexpr offset_type PLS_PLAYLIST_MAX_SIZE = 16 * 1024 * 1024; + static bool FindPlaylistSection(TextInputStream &is) { @@ -133,7 +137,9 @@ ParsePls(TextInputStream &is, std::forward_list &songs) static bool ParsePls(InputStreamPtr &&is, std::forward_list &songs) { - TextInputStream tis(std::move(is)); + InputStreamPtr limited = std::make_unique + (std::move(is), PLS_PLAYLIST_MAX_SIZE); + TextInputStream tis(std::move(limited)); if (!ParsePls(tis, songs)) { is = tis.StealInputStream(); return false;