diff --git a/src/input/InputPlugin.cxx b/src/input/InputPlugin.cxx index 4175f8d93..f77dbbae5 100644 --- a/src/input/InputPlugin.cxx +++ b/src/input/InputPlugin.cxx @@ -21,15 +21,54 @@ #include "util/StringCompare.hxx" #include +#include +#include bool InputPlugin::SupportsUri(const char *uri) const noexcept { - assert(prefixes != nullptr); - - for (auto i = prefixes; *i != nullptr; ++i) - if (StringStartsWithIgnoreCase(uri, *i)) - return true; - + assert(prefixes || protocols); + if (prefixes != nullptr) { + for (auto i = prefixes; *i != nullptr; ++i) + if (StringStartsWithIgnoreCase(uri, *i)) + return true; + } else { + for (auto schema : protocols()) { + if (StringStartsWithIgnoreCase(uri, schema.c_str())){ + return true; + } + } + } return false; } + +// Note: The whitelist has to be ordered alphabetically +constexpr static const char *whitelist[] = { + "ftp", + "ftps", + "gopher", + "http", + "https", + "mmsh", + "mmst", + "rtmp", + "rtmpe", + "rtmps", + "rtmpt", + "rtmpte", + "rtmpts", + "rtp", + "scp", + "sftp", + "smb", + "srtp", +}; + +bool +protocol_is_whitelisted(const char *proto) { + auto begin = std::begin(whitelist); + auto end = std::end(whitelist); + return std::binary_search(begin, end, proto, [](const char* a, const char* b) { + return strcasecmp(a,b) < 0; + }); +} \ No newline at end of file diff --git a/src/input/InputPlugin.hxx b/src/input/InputPlugin.hxx index 51b44c061..831b17e61 100644 --- a/src/input/InputPlugin.hxx +++ b/src/input/InputPlugin.hxx @@ -22,6 +22,9 @@ #include "Ptr.hxx" #include "util/Compiler.h" +#include +#include +#include struct ConfigBlock; class Mutex; @@ -62,6 +65,11 @@ struct InputPlugin { */ InputStreamPtr (*open)(const char *uri, Mutex &mutex); + /** + * return a set of supported protocols + */ + std::set (*protocols)(); + /** * Prepare a #RemoteTagScanner. The operation must be started * using RemoteTagScanner::Start(). Returns nullptr if the @@ -76,6 +84,25 @@ struct InputPlugin { gcc_pure bool SupportsUri(const char *uri) const noexcept; + + template + void ForeachSupportedUri(F lambda) const noexcept { + assert(prefixes || protocols); + + if (prefixes != nullptr) { + for (auto schema = prefixes; *schema != nullptr; ++schema) { + lambda(*schema); + } + } + if (protocols != nullptr) { + for (auto schema : protocols()) { + lambda(schema.c_str()); + } + } + } }; +bool +protocol_is_whitelisted(const char *proto); + #endif diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx index 4151c920e..3132f1e9f 100644 --- a/src/input/plugins/AlsaInputPlugin.cxx +++ b/src/input/plugins/AlsaInputPlugin.cxx @@ -486,4 +486,5 @@ const struct InputPlugin input_plugin_alsa = { alsa_input_init, nullptr, alsa_input_open, + nullptr }; diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx index 0079e55a9..6c43d08d7 100644 --- a/src/input/plugins/CdioParanoiaInputPlugin.cxx +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -357,4 +357,5 @@ const InputPlugin input_plugin_cdio_paranoia = { input_cdio_init, nullptr, input_cdio_open, + nullptr }; diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index b16f1eb84..6f2ca3ef4 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -471,16 +471,25 @@ input_curl_open(const char *url, Mutex &mutex) return CurlInputStream::Open(url, {}, mutex); } -static constexpr const char *curl_prefixes[] = { - "http://", - "https://", - nullptr -}; +static std::set +input_curl_protocols() { + std::set protocols; + auto version_info = curl_version_info(CURLVERSION_FIRST); + for (auto proto_ptr = version_info->protocols; *proto_ptr != nullptr; proto_ptr++) { + if (protocol_is_whitelisted(*proto_ptr)) { + std::string schema(*proto_ptr); + schema.append("://"); + protocols.emplace(schema); + } + } + return protocols; +} const struct InputPlugin input_plugin_curl = { "curl", - curl_prefixes, + nullptr, input_curl_init, input_curl_finish, input_curl_open, + input_curl_protocols }; diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx index 198f4bd17..eee10c975 100644 --- a/src/input/plugins/FfmpegInputPlugin.cxx +++ b/src/input/plugins/FfmpegInputPlugin.cxx @@ -25,8 +25,13 @@ #include "lib/ffmpeg/Init.hxx" #include "lib/ffmpeg/Error.hxx" #include "../InputStream.hxx" -#include "../InputPlugin.hxx" #include "PluginUnavailable.hxx" +#include "util/StringAPI.hxx" +#include "../InputPlugin.hxx" +#include +#include +#include +#include class FfmpegInputStream final : public InputStream { Ffmpeg::IOContext io; @@ -71,6 +76,22 @@ input_ffmpeg_init(EventLoop &, const ConfigBlock &) throw PluginUnavailable("No protocol"); } +static std::set +input_ffmpeg_protocols() { + void *opaque = nullptr; + const char* protocol; + std::set protocols; + while ((protocol = avio_enum_protocols(&opaque, 0))) { + if (protocol_is_whitelisted(protocol)) { + std::string schema(protocol); + schema.append("://"); + protocols.emplace(schema); + } + } + + return protocols; +} + static InputStreamPtr input_ffmpeg_open(const char *uri, Mutex &mutex) @@ -111,20 +132,11 @@ FfmpegInputStream::Seek(offset_type new_offset) offset = result; } -static constexpr const char *ffmpeg_prefixes[] = { - "gopher://", - "rtp://", - "rtsp://", - "rtmp://", - "rtmpt://", - "rtmps://", - nullptr -}; - const InputPlugin input_plugin_ffmpeg = { "ffmpeg", - ffmpeg_prefixes, + nullptr, input_ffmpeg_init, nullptr, input_ffmpeg_open, + input_ffmpeg_protocols }; diff --git a/src/input/plugins/MmsInputPlugin.cxx b/src/input/plugins/MmsInputPlugin.cxx index 0d7de8f88..0ee3fc2ec 100644 --- a/src/input/plugins/MmsInputPlugin.cxx +++ b/src/input/plugins/MmsInputPlugin.cxx @@ -110,4 +110,5 @@ const InputPlugin input_plugin_mms = { nullptr, nullptr, input_mms_open, + nullptr }; diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx index 8281c003d..b80f3f3ac 100644 --- a/src/input/plugins/NfsInputPlugin.cxx +++ b/src/input/plugins/NfsInputPlugin.cxx @@ -232,4 +232,5 @@ const InputPlugin input_plugin_nfs = { input_nfs_init, input_nfs_finish, input_nfs_open, + nullptr }; diff --git a/src/input/plugins/QobuzInputPlugin.cxx b/src/input/plugins/QobuzInputPlugin.cxx index 31f60cb24..c8470c54a 100644 --- a/src/input/plugins/QobuzInputPlugin.cxx +++ b/src/input/plugins/QobuzInputPlugin.cxx @@ -219,5 +219,6 @@ const InputPlugin qobuz_input_plugin = { InitQobuzInput, FinishQobuzInput, OpenQobuzInput, + nullptr, ScanQobuzTags, }; diff --git a/src/input/plugins/SmbclientInputPlugin.cxx b/src/input/plugins/SmbclientInputPlugin.cxx index b4b0785f8..9efac512a 100644 --- a/src/input/plugins/SmbclientInputPlugin.cxx +++ b/src/input/plugins/SmbclientInputPlugin.cxx @@ -162,4 +162,5 @@ const InputPlugin input_plugin_smbclient = { input_smbclient_init, nullptr, input_smbclient_open, + nullptr }; diff --git a/src/input/plugins/TidalInputPlugin.cxx b/src/input/plugins/TidalInputPlugin.cxx index 95025954b..81bff2ebf 100644 --- a/src/input/plugins/TidalInputPlugin.cxx +++ b/src/input/plugins/TidalInputPlugin.cxx @@ -249,5 +249,6 @@ const InputPlugin tidal_input_plugin = { InitTidalInput, FinishTidalInput, OpenTidalInput, + nullptr, ScanTidalTags, }; diff --git a/src/ls.cxx b/src/ls.cxx index 8bc6330f4..7b79c9606 100644 --- a/src/ls.cxx +++ b/src/ls.cxx @@ -22,28 +22,41 @@ #include "input/Registry.hxx" #include "input/InputPlugin.hxx" #include "client/Response.hxx" -#include "util/ASCII.hxx" #include "util/UriUtil.hxx" #include +#include + void print_supported_uri_schemes_to_fp(FILE *fp) { #ifdef HAVE_UN fprintf(fp, " file://"); #endif + std::set protocols; input_plugins_for_each(plugin) - for (auto i = plugin->prefixes; *i != nullptr; ++i) - fprintf(fp, " %s", *i); + plugin->ForeachSupportedUri([&](const char* uri) { + protocols.emplace(uri); + }); + + for (auto protocol : protocols) { + fprintf(fp, " %s", protocol.c_str()); + } fprintf(fp,"\n"); } void print_supported_uri_schemes(Response &r) { + std::set protocols; input_plugins_for_each_enabled(plugin) - for (auto i = plugin->prefixes; *i != nullptr; ++i) - r.Format("handler: %s\n", *i); + plugin->ForeachSupportedUri([&](const char* uri) { + protocols.emplace(uri); + }); + + for (auto protocol : protocols) { + r.Format("handler: %s\n", protocol.c_str()); + } } bool @@ -52,9 +65,7 @@ uri_supported_scheme(const char *uri) noexcept assert(uri_has_scheme(uri)); input_plugins_for_each_enabled(plugin) - for (auto i = plugin->prefixes; *i != nullptr; ++i) - if (StringStartsWithCaseASCII(uri, *i)) - return true; + return plugin->SupportsUri(uri); return false; }