Commit 40ecb6c1 by claincly Committed by Oliver Woodman

Prefers DIGEST when RTSP servers sends both BASIC and DIGEST auth info.

Issue: google/ExoPlayer#9800

Added test for RTSP authentication.

PiperOrigin-RevId: 420048821
parent dfcb906d
...@@ -87,6 +87,8 @@ ...@@ -87,6 +87,8 @@
* RTSP: * RTSP:
* Provide a client API to override the `SocketFactory` used for any server * Provide a client API to override the `SocketFactory` used for any server
connection ([#9606](https://github.com/google/ExoPlayer/pull/9606)). connection ([#9606](https://github.com/google/ExoPlayer/pull/9606)).
* Prefers DIGEST authentication method over BASIC if both are present.
([#9800](https://github.com/google/ExoPlayer/issues/9800)).
* Cast extension * Cast extension
* Fix bug that prevented `CastPlayer` from calling `onIsPlayingChanged` * Fix bug that prevented `CastPlayer` from calling `onIsPlayingChanged`
correctly. correctly.
......
...@@ -554,14 +554,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -554,14 +554,23 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
case 401: case 401:
if (rtspAuthUserInfo != null && !receivedAuthorizationRequest) { if (rtspAuthUserInfo != null && !receivedAuthorizationRequest) {
// Unauthorized. // Unauthorized.
@Nullable ImmutableList<String> wwwAuthenticateHeaders =
String wwwAuthenticateHeader = response.headers.get(RtspHeaders.WWW_AUTHENTICATE); response.headers.values(RtspHeaders.WWW_AUTHENTICATE);
if (wwwAuthenticateHeader == null) { if (wwwAuthenticateHeaders.isEmpty()) {
throw ParserException.createForMalformedManifest( throw ParserException.createForMalformedManifest(
"Missing WWW-Authenticate header in a 401 response.", /* cause= */ null); "Missing WWW-Authenticate header in a 401 response.", /* cause= */ null);
} }
rtspAuthenticationInfo =
RtspMessageUtil.parseWwwAuthenticateHeader(wwwAuthenticateHeader); for (int i = 0; i < wwwAuthenticateHeaders.size(); i++) {
rtspAuthenticationInfo =
RtspMessageUtil.parseWwwAuthenticateHeader(wwwAuthenticateHeaders.get(i));
if (rtspAuthenticationInfo.authenticationMechanism
== RtspAuthenticationInfo.DIGEST) {
// Prefers DIGEST when RTSP servers sends both BASIC and DIGEST auth info.
break;
}
}
messageSender.retryLastRequest(); messageSender.retryLastRequest();
receivedAuthorizationRequest = true; receivedAuthorizationRequest = true;
return; return;
......
...@@ -92,7 +92,7 @@ public final class RtspClientTest { ...@@ -92,7 +92,7 @@ public final class RtspClientTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
return RtspTestUtils.newDescribeResponseWithSdpMessage( return RtspTestUtils.newDescribeResponseWithSdpMessage(
SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri);
} }
...@@ -167,7 +167,7 @@ public final class RtspClientTest { ...@@ -167,7 +167,7 @@ public final class RtspClientTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
return RtspTestUtils.newDescribeResponseWithSdpMessage( return RtspTestUtils.newDescribeResponseWithSdpMessage(
SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri);
} }
...@@ -209,7 +209,7 @@ public final class RtspClientTest { ...@@ -209,7 +209,7 @@ public final class RtspClientTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
if (!requestedUri.getPath().contains("redirect")) { if (!requestedUri.getPath().contains("redirect")) {
return new RtspResponse( return new RtspResponse(
301, 301,
...@@ -263,7 +263,7 @@ public final class RtspClientTest { ...@@ -263,7 +263,7 @@ public final class RtspClientTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
return RtspTestUtils.newDescribeResponseWithSdpMessage( return RtspTestUtils.newDescribeResponseWithSdpMessage(
SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri);
} }
...@@ -310,7 +310,7 @@ public final class RtspClientTest { ...@@ -310,7 +310,7 @@ public final class RtspClientTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
clientHasSentDescribeRequest.set(true); clientHasSentDescribeRequest.set(true);
return RtspTestUtils.RTSP_ERROR_METHOD_NOT_ALLOWED; return RtspTestUtils.RTSP_ERROR_METHOD_NOT_ALLOWED;
} }
...@@ -356,7 +356,7 @@ public final class RtspClientTest { ...@@ -356,7 +356,7 @@ public final class RtspClientTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
// This session description misses required the o, t and s tags. // This session description misses required the o, t and s tags.
return RtspTestUtils.newDescribeResponseWithSdpMessage( return RtspTestUtils.newDescribeResponseWithSdpMessage(
/* sessionDescription= */ "v=0\r\n", rtpPacketStreamDumps, requestedUri); /* sessionDescription= */ "v=0\r\n", rtpPacketStreamDumps, requestedUri);
......
...@@ -62,7 +62,7 @@ public final class RtspMediaPeriodTest { ...@@ -62,7 +62,7 @@ public final class RtspMediaPeriodTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
return RtspTestUtils.newDescribeResponseWithSdpMessage( return RtspTestUtils.newDescribeResponseWithSdpMessage(
"v=0\r\n" "v=0\r\n"
+ "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n" + "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
...@@ -106,4 +106,89 @@ public final class RtspMediaPeriodTest { ...@@ -106,4 +106,89 @@ public final class RtspMediaPeriodTest {
assertThat(refreshedSourceDurationMs.get()).isEqualTo(50_460); assertThat(refreshedSourceDurationMs.get()).isEqualTo(50_460);
} }
@Test
public void prepareMediaPeriod_withWwwAuthentication_refreshesSourceInfoAndCallsOnPrepared()
throws Exception {
RtpPacketStreamDump rtpPacketStreamDump =
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/aac-dump.json");
rtspServer =
new RtspServer(
new RtspServer.ResponseProvider() {
@Override
public RtspResponse getOptionsResponse() {
return new RtspResponse(
/* status= */ 200,
new RtspHeaders.Builder().add(RtspHeaders.PUBLIC, "OPTIONS, DESCRIBE").build());
}
@Override
public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
String authorizationHeader = headers.get(RtspHeaders.AUTHORIZATION);
if (authorizationHeader == null) {
return new RtspResponse(
/* status= */ 401,
new RtspHeaders.Builder()
.add(RtspHeaders.CSEQ, headers.get(RtspHeaders.CSEQ))
.add(
RtspHeaders.WWW_AUTHENTICATE,
"Digest realm=\"LIVE555 Streaming Media\","
+ " nonce=\"0cdfe9719e7373b7d5bb2913e2115f3f\","
+ " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"")
.add(RtspHeaders.WWW_AUTHENTICATE, "BASIC realm=\"WallyWorld\"")
.build());
}
if (!authorizationHeader.contains("Digest")) {
return new RtspResponse(
401,
new RtspHeaders.Builder()
.add(RtspHeaders.CSEQ, headers.get(RtspHeaders.CSEQ))
.build());
}
return RtspTestUtils.newDescribeResponseWithSdpMessage(
"v=0\r\n"
+ "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
+ "s=Exoplayer test\r\n"
+ "t=0 0\r\n"
// The session is 50.46s long.
+ "a=range:npt=0-50.46\r\n",
ImmutableList.of(rtpPacketStreamDump),
requestedUri);
}
});
AtomicBoolean prepareCallbackCalled = new AtomicBoolean();
AtomicLong refreshedSourceDurationMs = new AtomicLong();
mediaPeriod =
new RtspMediaPeriod(
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
new TransferRtpDataChannelFactory(DEFAULT_TIMEOUT_MS),
RtspTestUtils.getTestUriWithUserInfo(
"username", "password", rtspServer.startAndGetPortNumber()),
/* listener= */ timing -> refreshedSourceDurationMs.set(timing.getDurationMs()),
/* userAgent= */ "ExoPlayer:RtspPeriodTest",
/* socketFactory= */ SocketFactory.getDefault(),
/* debugLoggingEnabled= */ false);
mediaPeriod.prepare(
new MediaPeriod.Callback() {
@Override
public void onPrepared(MediaPeriod mediaPeriod) {
prepareCallbackCalled.set(true);
}
@Override
public void onContinueLoadingRequested(MediaPeriod source) {
source.continueLoading(/* positionUs= */ 0);
}
},
/* positionUs= */ 0);
RobolectricUtil.runMainLooperUntil(prepareCallbackCalled::get);
mediaPeriod.release();
assertThat(refreshedSourceDurationMs.get()).isEqualTo(50_460);
}
} }
...@@ -209,7 +209,7 @@ public final class RtspPlaybackTest { ...@@ -209,7 +209,7 @@ public final class RtspPlaybackTest {
} }
@Override @Override
public RtspResponse getDescribeResponse(Uri requestedUri) { public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
return RtspTestUtils.newDescribeResponseWithSdpMessage( return RtspTestUtils.newDescribeResponseWithSdpMessage(
SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri);
} }
......
...@@ -45,7 +45,7 @@ public final class RtspServer implements Closeable { ...@@ -45,7 +45,7 @@ public final class RtspServer implements Closeable {
RtspResponse getOptionsResponse(); RtspResponse getOptionsResponse();
/** Returns an RTSP DESCRIBE {@link RtspResponse response}. */ /** Returns an RTSP DESCRIBE {@link RtspResponse response}. */
default RtspResponse getDescribeResponse(Uri requestedUri) { default RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
return RtspTestUtils.RTSP_ERROR_METHOD_NOT_ALLOWED; return RtspTestUtils.RTSP_ERROR_METHOD_NOT_ALLOWED;
} }
...@@ -143,7 +143,7 @@ public final class RtspServer implements Closeable { ...@@ -143,7 +143,7 @@ public final class RtspServer implements Closeable {
break; break;
case METHOD_DESCRIBE: case METHOD_DESCRIBE:
sendResponse(responseProvider.getDescribeResponse(request.uri), cSeq); sendResponse(responseProvider.getDescribeResponse(request.uri, request.headers), cSeq);
break; break;
case METHOD_SETUP: case METHOD_SETUP:
......
...@@ -28,6 +28,7 @@ import java.util.List; ...@@ -28,6 +28,7 @@ import java.util.List;
/* package */ final class RtspTestUtils { /* package */ final class RtspTestUtils {
private static final String TEST_BASE_URI = "rtsp://localhost:%d/test"; private static final String TEST_BASE_URI = "rtsp://localhost:%d/test";
private static final String TEST_BASE_URI_WITH_USER_INFO = "rtsp://%s:%s@localhost:%d/test";
private static final String RTP_TIME_FORMAT = "url=rtsp://localhost/test/%s;seq=%d;rtptime=%d"; private static final String RTP_TIME_FORMAT = "url=rtsp://localhost/test/%s;seq=%d;rtptime=%d";
/** RTSP error Method Not Allowed (RFC2326 Section 7.1.1). */ /** RTSP error Method Not Allowed (RFC2326 Section 7.1.1). */
...@@ -70,6 +71,14 @@ import java.util.List; ...@@ -70,6 +71,14 @@ import java.util.List;
return Uri.parse(Util.formatInvariant(TEST_BASE_URI, serverRtspPortNumber)); return Uri.parse(Util.formatInvariant(TEST_BASE_URI, serverRtspPortNumber));
} }
/** Returns the test RTSP {@link Uri} with user info. */
public static Uri getTestUriWithUserInfo(
String username, String password, int serverRtspPortNumber) {
return Uri.parse(
Util.formatInvariant(
TEST_BASE_URI_WITH_USER_INFO, username, password, serverRtspPortNumber));
}
public static String getRtpInfoForDumps(List<RtpPacketStreamDump> rtpPacketStreamDumps) { public static String getRtpInfoForDumps(List<RtpPacketStreamDump> rtpPacketStreamDumps) {
ArrayList<String> rtpInfos = new ArrayList<>(rtpPacketStreamDumps.size()); ArrayList<String> rtpInfos = new ArrayList<>(rtpPacketStreamDumps.size());
for (RtpPacketStreamDump rtpPacketStreamDump : rtpPacketStreamDumps) { for (RtpPacketStreamDump rtpPacketStreamDump : rtpPacketStreamDumps) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment