Skip to content

Commit

Permalink
Handle malformed URL in RTP-Info header.
Browse files Browse the repository at this point in the history
Some server will send partial URIs in the RTP-Info header, while the RTSP spec
requires absolute URLs.

Issue: #9346

#exofixit

PiperOrigin-RevId: 395452741
  • Loading branch information
claincly authored and icbaker committed Sep 8, 2021
1 parent c403de1 commit e6b5392
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 16 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
* RTSP:
* Handle when additional spaces are in SDP's RTPMAP atrribute
([#9379](https://github.com/google/ExoPlayer/issues/9379)).
* Handle partial URIs in RTP-Info headers
([#9346](https://github.com/google/ExoPlayer/issues/9346)).
* Extractors:
* ID3: Fix issue decoding ID3 tags containing UTF-16 encoded strings
([#9087](https://github.com/google/ExoPlayer/issues/9087)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ private void handleRtspResponse(List<String> message) {
ImmutableList<RtspTrackTiming> trackTimingList =
rtpInfoString == null
? ImmutableList.of()
: RtspTrackTiming.parseTrackTiming(rtpInfoString);
: RtspTrackTiming.parseTrackTiming(rtpInfoString, uri);
onPlayResponseReceived(new RtspPlayResponse(response.status, timing, trackTimingList));
break;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@
*/
package com.google.android.exoplayer2.source.rtsp;

import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;

import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;

Expand Down Expand Up @@ -49,11 +54,12 @@
* </pre>
*
* @param rtpInfoString The value of the RTP-Info header, with header name (RTP-Info) removed.
* @param sessionUri The session URI, must include an {@code rtsp} scheme.
* @return A list of parsed {@link RtspTrackTiming}.
* @throws ParserException If parsing failed.
*/
public static ImmutableList<RtspTrackTiming> parseTrackTiming(String rtpInfoString)
throws ParserException {
public static ImmutableList<RtspTrackTiming> parseTrackTiming(
String rtpInfoString, Uri sessionUri) throws ParserException {

ImmutableList.Builder<RtspTrackTiming> listBuilder = new ImmutableList.Builder<>();
for (String perTrackTimingString : Util.split(rtpInfoString, ",")) {
Expand All @@ -69,7 +75,7 @@ public static ImmutableList<RtspTrackTiming> parseTrackTiming(String rtpInfoStri

switch (attributeName) {
case "url":
uri = Uri.parse(attributeValue);
uri = resolveUri(/* urlString= */ attributeValue, sessionUri);
break;
case "seq":
sequenceNumber = Integer.parseInt(attributeValue);
Expand All @@ -96,6 +102,48 @@ public static ImmutableList<RtspTrackTiming> parseTrackTiming(String rtpInfoStri
return listBuilder.build();
}

/**
* Resolves the input string to always be an absolute URL with RTP-Info headers
*
* <p>Handles some servers do not send absolute URL in RTP-Info headers. This method takes in
* RTP-Info header's url string, and returns the correctly formatted {@link Uri url} for this
* track. The input url string could be
*
* <ul>
* <li>A correctly formatted URL, like "{@code rtsp://foo.bar/video}".
* <li>A correct URI that is missing the scheme, like "{@code foo.bar/video}".
* <li>A path to the resource, like "{@code video}" or "{@code /video}".
* </ul>
*
* @param urlString The URL included in the RTP-Info header, without the {@code url=} identifier.
* @param sessionUri The session URI, must include an {@code rtsp} scheme, or {@link
* IllegalArgumentException} is thrown.
* @return The formatted URL.
*/
@VisibleForTesting
/* package */ static Uri resolveUri(String urlString, Uri sessionUri) {
checkArgument(checkNotNull(sessionUri.getScheme()).equals("rtsp"));

Uri uri = Uri.parse(urlString);
if (uri.isAbsolute()) {
return uri;
}

// The urlString is at least missing the scheme.
uri = Uri.parse("rtsp://" + urlString);
String sessionUriString = sessionUri.toString();

String host = checkNotNull(uri.getHost());
if (host.equals(sessionUri.getHost())) {
// Handles the case that the urlString is only missing the scheme.
return uri;
}

return sessionUriString.endsWith("/")
? UriUtil.resolveToUri(sessionUriString, urlString)
: UriUtil.resolveToUri(sessionUriString + "/", urlString);
}

/**
* The timestamp of the next RTP packet, {@link C#TIME_UNSET} if not present.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void parseTiming_withSeqNumberAndRtpTime() throws Exception {
"url=rtsp://video.example.com/twister/video;seq=12312232;rtptime=78712811";

ImmutableList<RtspTrackTiming> trackTimingList =
RtspTrackTiming.parseTrackTiming(rtpInfoString);
RtspTrackTiming.parseTrackTiming(rtpInfoString, Uri.parse("rtsp://video.example.com"));

assertThat(trackTimingList).hasSize(1);
RtspTrackTiming trackTiming = trackTimingList.get(0);
Expand All @@ -50,7 +50,7 @@ public void parseTiming_withSeqNumberOnly() throws Exception {
"url=rtsp://foo.com/bar.avi/streamid=0;seq=45102,url=rtsp://foo.com/bar.avi/streamid=1;seq=30211";

ImmutableList<RtspTrackTiming> trackTimingList =
RtspTrackTiming.parseTrackTiming(rtpInfoString);
RtspTrackTiming.parseTrackTiming(rtpInfoString, Uri.parse("rtsp://foo.com"));

assertThat(trackTimingList).hasSize(2);
RtspTrackTiming trackTiming = trackTimingList.get(0);
Expand All @@ -67,27 +67,88 @@ public void parseTiming_withSeqNumberOnly() throws Exception {
public void parseTiming_withInvalidParameter_throws() {
String rtpInfoString = "url=rtsp://video.example.com/twister/video;seq=123abc";

assertThrows(ParserException.class, () -> RtspTrackTiming.parseTrackTiming(rtpInfoString));
}

@Test
public void parseTiming_withInvalidUrl_throws() {
String rtpInfoString = "url=video.example.com/twister/video;seq=36192348";

assertThrows(ParserException.class, () -> RtspTrackTiming.parseTrackTiming(rtpInfoString));
assertThrows(
ParserException.class,
() ->
RtspTrackTiming.parseTrackTiming(
rtpInfoString, Uri.parse("rtsp://video.example.com/twister")));
}

@Test
public void parseTiming_withNoParameter_throws() {
String rtpInfoString = "url=rtsp://video.example.com/twister/video";

assertThrows(ParserException.class, () -> RtspTrackTiming.parseTrackTiming(rtpInfoString));
assertThrows(
ParserException.class,
() ->
RtspTrackTiming.parseTrackTiming(
rtpInfoString, Uri.parse("rtsp://video.example.com/twister")));
}

@Test
public void parseTiming_withNoUrl_throws() {
String rtpInfoString = "seq=35421887";

assertThrows(ParserException.class, () -> RtspTrackTiming.parseTrackTiming(rtpInfoString));
assertThrows(
ParserException.class,
() ->
RtspTrackTiming.parseTrackTiming(
rtpInfoString, Uri.parse("rtsp://video.example.com/twister")));
}

@Test
public void resolveUri_withAbsoluteUri_succeeds() {
Uri uri =
RtspTrackTiming.resolveUri(
"rtsp://video.example.com/twister/video=1?a2bfc09887ce",
Uri.parse("rtsp://video.example.com/twister"));

assertThat(uri).isEqualTo(Uri.parse("rtsp://video.example.com/twister/video=1?a2bfc09887ce"));
}

@Test
public void resolveUri_withCompleteUriMissingScheme_succeeds() {
Uri uri =
RtspTrackTiming.resolveUri(
"video.example.com/twister/video=1", Uri.parse("rtsp://video.example.com/twister"));

assertThat(uri).isEqualTo(Uri.parse("rtsp://video.example.com/twister/video=1"));
}

@Test
public void resolveUri_withPartialUriMissingScheme_succeeds() {
Uri uri = RtspTrackTiming.resolveUri("video=1", Uri.parse("rtsp://video.example.com/twister"));

assertThat(uri).isEqualTo(Uri.parse("rtsp://video.example.com/twister/video=1"));
}

@Test
public void resolveUri_withMultipartPartialUriMissingScheme_succeeds() {
Uri uri =
RtspTrackTiming.resolveUri(
"container/video=1", Uri.parse("rtsp://video.example.com/twister"));

assertThat(uri).isEqualTo(Uri.parse("rtsp://video.example.com/twister/container/video=1"));
}

@Test
public void resolveUri_withPartialUriMissingSchemeWithIpBaseUri_succeeds() {
Uri uri = RtspTrackTiming.resolveUri("video=1", Uri.parse("rtsp://127.0.0.1:18888/test"));

assertThat(uri).isEqualTo(Uri.parse("rtsp://127.0.0.1:18888/test/video=1"));
}

@Test
public void resolveUri_withPartialUriMissingSchemeWithIpBaseUriWithSlash_succeeds() {
Uri uri = RtspTrackTiming.resolveUri("video=1", Uri.parse("rtsp://127.0.0.1:18888/test/"));

assertThat(uri).isEqualTo(Uri.parse("rtsp://127.0.0.1:18888/test/video=1"));
}

@Test
public void resolveUri_withSessionUriMissingScheme_throwsIllegalArgumentException() {
assertThrows(
IllegalArgumentException.class,
() -> RtspTrackTiming.resolveUri("video=1", Uri.parse("127.0.0.1:18888/test")));
}
}

0 comments on commit e6b5392

Please sign in to comment.