Skip to content

Commit

Permalink
Prefers DIGEST when RTSP servers sends both BASIC and DIGEST auth info.
Browse files Browse the repository at this point in the history
Issue: #9800

Added test for RTSP authentication.

PiperOrigin-RevId: 420048821
  • Loading branch information
claincly authored and ojw28 committed Jan 7, 2022
1 parent dfcb906 commit 40ecb6c
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 15 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
* RTSP:
* Provide a client API to override the `SocketFactory` used for any server
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
* Fix bug that prevented `CastPlayer` from calling `onIsPlayingChanged`
correctly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -554,14 +554,23 @@ private void handleRtspResponse(List<String> message) {
case 401:
if (rtspAuthUserInfo != null && !receivedAuthorizationRequest) {
// Unauthorized.
@Nullable
String wwwAuthenticateHeader = response.headers.get(RtspHeaders.WWW_AUTHENTICATE);
if (wwwAuthenticateHeader == null) {
ImmutableList<String> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -143,7 +143,7 @@ private void handleRtspMessage(List<String> message) {
break;

case METHOD_DESCRIBE:
sendResponse(responseProvider.getDescribeResponse(request.uri), cSeq);
sendResponse(responseProvider.getDescribeResponse(request.uri, request.headers), cSeq);
break;

case METHOD_SETUP:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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). */
Expand Down Expand Up @@ -70,6 +71,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<RtpPacketStreamDump> rtpPacketStreamDumps) {
ArrayList<String> rtpInfos = new ArrayList<>(rtpPacketStreamDumps.size());
for (RtpPacketStreamDump rtpPacketStreamDump : rtpPacketStreamDumps) {
Expand Down

0 comments on commit 40ecb6c

Please sign in to comment.