fix: escape media URLs in MPD (#1395)

Currently `media_info.media_file_url()` is not escaped when placed into
MPD for things like BaseURL. This for example breaks when trying to us a
file name that contains special characters like &. Since these are
supposed to be URLs let's URL encode them.

Fixes #1107

---------

Co-authored-by: Joey Parrish <joeyparrish@users.noreply.github.com>
This commit is contained in:
Cosmin Stejerean 2024-05-10 17:27:13 -07:00 committed by GitHub
parent b7e96f7d93
commit 98b44d01df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 30 additions and 3 deletions

View File

@ -51,6 +51,7 @@ target_link_libraries(mpd_builder
media_base media_base
mpd_media_info_proto mpd_media_info_proto
utils_clock utils_clock
libcurl
) )

View File

@ -167,7 +167,7 @@ std::optional<xml::XmlNode> MpdBuilder::GenerateMpd() {
// Add baseurls to MPD. // Add baseurls to MPD.
for (const std::string& base_url : base_urls_) { for (const std::string& base_url : base_urls_) {
XmlNode xml_base_url("BaseURL"); XmlNode xml_base_url("BaseURL");
xml_base_url.SetContent(base_url); xml_base_url.SetUrlEncodedContent(base_url);
if (!mpd.AddChild(std::move(xml_base_url))) if (!mpd.AddChild(std::move(xml_base_url)))
return std::nullopt; return std::nullopt;

View File

@ -18,6 +18,7 @@
#include <absl/strings/escaping.h> #include <absl/strings/escaping.h>
#include <absl/strings/numbers.h> #include <absl/strings/numbers.h>
#include <absl/strings/str_format.h> #include <absl/strings/str_format.h>
#include <curl/curl.h>
#include <libxml/tree.h> #include <libxml/tree.h>
#include <packager/macros/compiler.h> #include <packager/macros/compiler.h>
@ -53,6 +54,18 @@ const char kDTSCCodec[] = "dtsc";
const char kDTSECodec[] = "dtse"; const char kDTSECodec[] = "dtse";
const char kDTSXCodec[] = "dtsx"; const char kDTSXCodec[] = "dtsx";
std::string urlEncode(const std::string& input) {
// NOTE: According to the docs, "Since 7.82.0, the curl parameter is ignored".
CURL* curl = NULL;
char* output = curl_easy_escape(curl, input.c_str(), input.length());
if (output) {
std::string encodedUrl(output);
curl_free(output); // Free the output string when done
return encodedUrl;
}
return ""; // Return empty string if initialization fails
}
std::string RangeToString(const Range& range) { std::string RangeToString(const Range& range) {
return absl::StrFormat("%u-%u", range.begin(), range.end()); return absl::StrFormat("%u-%u", range.begin(), range.end());
} }
@ -220,11 +233,19 @@ void XmlNode::AddContent(const std::string& content) {
xmlNodeAddContent(impl_->node.get(), BAD_CAST content.c_str()); xmlNodeAddContent(impl_->node.get(), BAD_CAST content.c_str());
} }
void XmlNode::AddUrlEncodedContent(const std::string& content) {
AddContent(urlEncode(content));
}
void XmlNode::SetContent(const std::string& content) { void XmlNode::SetContent(const std::string& content) {
DCHECK(impl_->node); DCHECK(impl_->node);
xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str()); xmlNodeSetContent(impl_->node.get(), BAD_CAST content.c_str());
} }
void XmlNode::SetUrlEncodedContent(const std::string& content) {
SetContent(urlEncode(content));
}
std::set<std::string> XmlNode::ExtractReferencedNamespaces() const { std::set<std::string> XmlNode::ExtractReferencedNamespaces() const {
std::set<std::string> namespaces; std::set<std::string> namespaces;
TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces); TraverseNodesAndCollectNamespaces(impl_->node.get(), &namespaces);
@ -400,7 +421,7 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info,
if (media_info.has_media_file_url() && !use_single_segment_url_with_media) { if (media_info.has_media_file_url() && !use_single_segment_url_with_media) {
XmlNode base_url("BaseURL"); XmlNode base_url("BaseURL");
base_url.SetContent(media_info.media_file_url()); base_url.SetUrlEncodedContent(media_info.media_file_url());
RCHECK(AddChild(std::move(base_url))); RCHECK(AddChild(std::move(base_url)));
} }
@ -452,7 +473,8 @@ bool RepresentationXmlNode::AddVODOnlyInfo(const MediaInfo& media_info,
if (use_single_segment_url_with_media) { if (use_single_segment_url_with_media) {
XmlNode media_url("SegmentURL"); XmlNode media_url("SegmentURL");
RCHECK(media_url.SetStringAttribute("media", media_info.media_file_url())); RCHECK(media_url.SetStringAttribute(
"media", urlEncode(media_info.media_file_url())));
RCHECK(child.AddChild(std::move(media_url))); RCHECK(child.AddChild(std::move(media_url)));
} }

View File

@ -83,6 +83,8 @@ class XmlNode {
/// Similar to SetContent, but appends to the end of existing content. /// Similar to SetContent, but appends to the end of existing content.
void AddContent(const std::string& content); void AddContent(const std::string& content);
void AddUrlEncodedContent(const std::string& content);
/// Set the contents of an XML element using a string. /// Set the contents of an XML element using a string.
/// This cannot set child elements because <> will become &lt; and &rt; /// This cannot set child elements because <> will become &lt; and &rt;
/// This should be used to set the text for the element, e.g. setting /// This should be used to set the text for the element, e.g. setting
@ -91,6 +93,8 @@ class XmlNode {
/// be added to the element. /// be added to the element.
void SetContent(const std::string& content); void SetContent(const std::string& content);
void SetUrlEncodedContent(const std::string& content);
/// @return namespaces used in the node and its descendents. /// @return namespaces used in the node and its descendents.
std::set<std::string> ExtractReferencedNamespaces() const; std::set<std::string> ExtractReferencedNamespaces() const;