From 152a1650f62a60f748c393d5b2ca8c4360fc87b6 Mon Sep 17 00:00:00 2001 From: claincly Date: Thu, 6 Jan 2022 14:34:55 +0000 Subject: [PATCH] Prefers DIGEST when RTSP servers sends both BASIC and DIGEST auth info. Issue: google/ExoPlayer#9800 Added test for RTSP authentication. PiperOrigin-RevId: 420048821 --- .../media3/exoplayer/rtsp/RtspClient.java | 19 ++-- .../media3/exoplayer/rtsp/RtspClientTest.java | 12 +-- .../exoplayer/rtsp/RtspMediaPeriodTest.java | 87 ++++++++++++++++++- .../exoplayer/rtsp/RtspPlaybackTest.java | 2 +- .../media3/exoplayer/rtsp/RtspServer.java | 4 +- .../media3/exoplayer/rtsp/RtspTestUtils.java | 9 ++ 6 files changed, 118 insertions(+), 15 deletions(-) diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java index 7db4c9c197d..ea81401e3ad 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java @@ -554,14 +554,23 @@ private void handleRtspResponse(List message) { case 401: if (rtspAuthUserInfo != null && !receivedAuthorizationRequest) { // Unauthorized. - @Nullable - String wwwAuthenticateHeader = response.headers.get(RtspHeaders.WWW_AUTHENTICATE); - if (wwwAuthenticateHeader == null) { + ImmutableList wwwAuthenticateHeaders = + response.headers.values(RtspHeaders.WWW_AUTHENTICATE); + if (wwwAuthenticateHeaders.isEmpty()) { throw ParserException.createForMalformedManifest( "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(); receivedAuthorizationRequest = true; return; diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java index 2e99b36aeb6..e754c7863ca 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java @@ -92,7 +92,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { return RtspTestUtils.newDescribeResponseWithSdpMessage( SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); } @@ -167,7 +167,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { return RtspTestUtils.newDescribeResponseWithSdpMessage( SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); } @@ -209,7 +209,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { if (!requestedUri.getPath().contains("redirect")) { return new RtspResponse( 301, @@ -263,7 +263,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { return RtspTestUtils.newDescribeResponseWithSdpMessage( SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); } @@ -310,7 +310,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { clientHasSentDescribeRequest.set(true); return RtspTestUtils.RTSP_ERROR_METHOD_NOT_ALLOWED; } @@ -356,7 +356,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { // This session description misses required the o, t and s tags. return RtspTestUtils.newDescribeResponseWithSdpMessage( /* sessionDescription= */ "v=0\r\n", rtpPacketStreamDumps, requestedUri); diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java index fdbe133d391..3ff2ab162e1 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspMediaPeriodTest.java @@ -62,7 +62,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { return RtspTestUtils.newDescribeResponseWithSdpMessage( "v=0\r\n" + "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n" @@ -106,4 +106,89 @@ public void onContinueLoadingRequested(MediaPeriod source) { 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); + } } diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java index 1caded00398..0c93acc61ff 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspPlaybackTest.java @@ -209,7 +209,7 @@ public RtspResponse getOptionsResponse() { } @Override - public RtspResponse getDescribeResponse(Uri requestedUri) { + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { return RtspTestUtils.newDescribeResponseWithSdpMessage( SESSION_DESCRIPTION, rtpPacketStreamDumps, requestedUri); } diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspServer.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspServer.java index d6408fe6c14..7ba284121ff 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspServer.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspServer.java @@ -45,7 +45,7 @@ public interface ResponseProvider { RtspResponse getOptionsResponse(); /** 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; } @@ -143,7 +143,7 @@ private void handleRtspMessage(List message) { break; case METHOD_DESCRIBE: - sendResponse(responseProvider.getDescribeResponse(request.uri), cSeq); + sendResponse(responseProvider.getDescribeResponse(request.uri, request.headers), cSeq); break; case METHOD_SETUP: diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspTestUtils.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspTestUtils.java index 5ade3ba8d7a..5fdab98739b 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspTestUtils.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspTestUtils.java @@ -30,6 +30,7 @@ /* package */ final class RtspTestUtils { 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"; /** RTSP error Method Not Allowed (RFC2326 Section 7.1.1). */ @@ -72,6 +73,14 @@ public static Uri getTestUri(int 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 rtpPacketStreamDumps) { ArrayList rtpInfos = new ArrayList<>(rtpPacketStreamDumps.size()); for (RtpPacketStreamDump rtpPacketStreamDump : rtpPacketStreamDumps) {