| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/media_router/common/media_source.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstdio> |
| #include <ostream> |
| #include <string> |
| |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "components/media_router/common/media_source.h" |
| #include "url/gurl.h" |
| |
| namespace media_router { |
| |
| namespace { |
| |
| // Prefixes used to format and detect various protocols' media source URNs. |
| // See: https://www.ietf.org/rfc/rfc3406.txt |
| constexpr char kAnyTabMediaUrn[] = "urn:x-org.chromium.media:source:tab:*"; |
| constexpr char kTabMediaUrnFormat[] = "urn:x-org.chromium.media:source:tab:%d"; |
| constexpr base::StringPiece kDesktopMediaUrnPrefix = |
| "urn:x-org.chromium.media:source:desktop:"; |
| // WARNING: If more desktop URN parameters are added in the future, the parsing |
| // code will have to be smarter! |
| constexpr base::StringPiece kDesktopMediaUrnAudioParam = "?with_audio=true"; |
| constexpr base::StringPiece kUnchosenDesktopMediaUrn = |
| "urn:x-org.chromium.media:source:desktop"; |
| |
| // List of non-http(s) schemes that are allowed in a Presentation URL. |
| constexpr std::array<const char* const, 5> kAllowedSchemes{ |
| {kCastPresentationUrlScheme, kCastDialPresentationUrlScheme, |
| kDialPresentationUrlScheme, kRemotePlaybackPresentationUrlScheme, "test"}}; |
| |
| bool IsSchemeAllowed(const GURL& url) { |
| return url.SchemeIsHTTPOrHTTPS() || |
| std::any_of( |
| kAllowedSchemes.begin(), kAllowedSchemes.end(), |
| [&url](const char* const scheme) { return url.SchemeIs(scheme); }); |
| } |
| |
| } // namespace |
| |
| bool IsLegacyCastPresentationUrl(const GURL& url) { |
| return base::StartsWith(url.spec(), kLegacyCastPresentationUrlPrefix, |
| base::CompareCase::INSENSITIVE_ASCII); |
| } |
| |
| bool IsValidPresentationUrl(const GURL& url) { |
| return url.is_valid() && IsSchemeAllowed(url); |
| } |
| |
| bool IsValidStandardPresentationSource(const std::string& media_source) { |
| const GURL source_url(media_source); |
| return source_url.is_valid() && source_url.SchemeIsHTTPOrHTTPS() && |
| !base::StartsWith(source_url.spec(), kLegacyCastPresentationUrlPrefix, |
| base::CompareCase::INSENSITIVE_ASCII); |
| } |
| |
| bool IsAutoJoinPresentationId(const std::string& presentation_id) { |
| return presentation_id == kAutoJoinPresentationId; |
| } |
| |
| MediaSource::MediaSource() = default; |
| |
| MediaSource::MediaSource(const MediaSource::Id& source_id) : id_(source_id) { |
| GURL url(source_id); |
| if (IsValidPresentationUrl(url)) |
| url_ = url; |
| } |
| |
| MediaSource::MediaSource(const GURL& presentation_url) |
| : id_(presentation_url.spec()), url_(presentation_url) {} |
| |
| MediaSource::~MediaSource() = default; |
| |
| // static |
| MediaSource MediaSource::ForLocalFile() { |
| // TODO(crbug.com/1090878): Use something more sane here. Fixing this |
| // requires tracking down other places where tab ID 0 is used to indicate |
| // local file casting. |
| // |
| // This probably isn't a source of bugs in practice, because tab IDs are |
| // generated by SessionIdGenerator, which appears to only produce positive |
| // values, but that fact isn't clearly documentated, and other parts of |
| // Chromium don't seem to rely on it, using -1 as the canonical invalid tab |
| // ID. |
| return MediaSource(base::StringPrintf(kTabMediaUrnFormat, 0)); |
| } |
| |
| // static |
| MediaSource MediaSource::ForAnyTab() { |
| return MediaSource(std::string(kAnyTabMediaUrn)); |
| } |
| |
| // static |
| MediaSource MediaSource::ForTab(int tab_id) { |
| // Ideally we shouldn't allow -1 as a tab ID, but in unit tests, a tab ID of |
| // -1 can show up when this function is called from |
| // CastHandler::StartObservingForSinks() because SessionTabHelper::IdForTab |
| // can return -1. |
| DCHECK_GE(tab_id, -1); |
| return MediaSource(base::StringPrintf(kTabMediaUrnFormat, tab_id)); |
| } |
| |
| // static |
| MediaSource MediaSource::ForDesktop( |
| const std::string& registered_desktop_stream_id, |
| bool with_audio) { |
| DCHECK(!registered_desktop_stream_id.empty()); |
| std::string id = |
| std::string(kDesktopMediaUrnPrefix) + registered_desktop_stream_id; |
| if (with_audio) { |
| id += std::string(kDesktopMediaUrnAudioParam); |
| } |
| return MediaSource(id); |
| } |
| |
| // static |
| MediaSource MediaSource::ForUnchosenDesktop() { |
| return MediaSource(std::string(kUnchosenDesktopMediaUrn)); |
| } |
| |
| // static |
| MediaSource MediaSource::ForPresentationUrl(const GURL& presentation_url) { |
| return MediaSource(presentation_url); |
| } |
| |
| bool MediaSource::IsTabMirroringSource() const { |
| return id() == kAnyTabMediaUrn || TabId() > 0; |
| } |
| |
| bool MediaSource::IsDesktopMirroringSource() const { |
| return id() == kUnchosenDesktopMediaUrn || |
| base::StartsWith(id(), kDesktopMediaUrnPrefix, |
| base::CompareCase::SENSITIVE); |
| } |
| |
| bool MediaSource::IsLocalFileSource() const { |
| // TODO(crbug.com/1090878): Keep this method is sync with ForLocalFile(). |
| return TabId() == 0; |
| } |
| |
| bool MediaSource::IsCastPresentationUrl() const { |
| return url_.SchemeIs(kCastPresentationUrlScheme) || |
| IsLegacyCastPresentationUrl(url_); |
| } |
| |
| int MediaSource::TabId() const { |
| int tab_id = -1; |
| sscanf(id_.c_str(), kTabMediaUrnFormat, &tab_id); |
| return tab_id; |
| } |
| |
| absl::optional<std::string> MediaSource::DesktopStreamId() const { |
| if (base::StartsWith(id_, kDesktopMediaUrnPrefix, |
| base::CompareCase::SENSITIVE)) { |
| const auto begin = id_.begin() + kDesktopMediaUrnPrefix.size(); |
| auto end = id_.end(); |
| if (base::EndsWith(id_, kDesktopMediaUrnAudioParam, |
| base::CompareCase::SENSITIVE)) { |
| end -= kDesktopMediaUrnAudioParam.size(); |
| } |
| return std::string(begin, end); |
| } |
| return absl::nullopt; |
| } |
| |
| bool MediaSource::IsDesktopSourceWithAudio() const { |
| return base::StartsWith(id_, kDesktopMediaUrnPrefix, |
| base::CompareCase::SENSITIVE) && |
| base::EndsWith(id_, kDesktopMediaUrnAudioParam, |
| base::CompareCase::SENSITIVE); |
| } |
| |
| bool MediaSource::IsDialSource() const { |
| return url_.SchemeIs(kCastDialPresentationUrlScheme); |
| } |
| |
| std::string MediaSource::AppNameFromDialSource() const { |
| return IsDialSource() ? url_.path() : ""; |
| } |
| |
| std::string MediaSource::TruncateForLogging(size_t max_length) const { |
| const std::string origin = url_.GetOrigin().spec(); |
| if (!origin.empty()) |
| return origin.substr(0, max_length); |
| // TODO(takumif): Keep the query string by redacting PII. The query string may |
| // contain info useful for debugging such as the required capabilities. |
| const size_t query_start_index = id_.find("?"); |
| const size_t length = |
| query_start_index == std::string::npos ? max_length : query_start_index; |
| return id_.substr(0, length); |
| } |
| |
| } // namespace media_router |