Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 121 additions & 6 deletions src/main/java/io/antmedia/rest/RestServiceBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,7 @@ protected Result updateStreamSource(String streamId, BroadcastUpdate updatedBroa
}

String rtspURL = connectionRes.getMessage();
String authparam = updatedBroadcast.getUsername() + ":" + updatedBroadcast.getPassword() + "@";
String rtspURLWithAuth = RTSP + authparam + rtspURL.substring(RTSP.length());
String rtspURLWithAuth = buildRtspUrlWithAuthPreservingPublicHost(updatedBroadcast.getIpAddr(), updatedBroadcast.getUsername(), updatedBroadcast.getPassword(), rtspURL);
logger.info("New Stream Source URL: {}", rtspURLWithAuth);
updatedBroadcast.setStreamUrl(rtspURLWithAuth);

Expand Down Expand Up @@ -923,8 +922,7 @@ public Result addIPCamera(Broadcast stream) {

if (connResult.isSuccess()) {

String authparam = stream.getUsername() + ":" + stream.getPassword() + "@";
String rtspURLWithAuth = RTSP + authparam + connResult.getMessage().substring(RTSP.length());
String rtspURLWithAuth = buildRtspUrlWithAuthPreservingPublicHost(stream.getIpAddr(), stream.getUsername(), stream.getPassword(), connResult.getMessage());
logger.info("rtsp url with auth: {}", rtspURLWithAuth);
stream.setStreamUrl(rtspURLWithAuth);
Date currentDate = new Date();
Expand Down Expand Up @@ -1000,6 +998,124 @@ public Result connectToCamera(String ipAddr, String username, String password) {

}

/**
* Build RTSP URL with credentials, preserving the public host/port entered by the user
* when ONVIF returns a private/local address. Backward compatible: if returned host is
* public or not clearly private, keep it as-is.
*/
protected String buildRtspUrlWithAuthPreservingPublicHost(String inputAddress, String username, String password, String returnedRtsp) {
String authparam = username + ":" + password + "@";
if (returnedRtsp == null || !returnedRtsp.startsWith(RTSP)) {
return RTSP + authparam + (returnedRtsp != null ? returnedRtsp : "");
}
String afterScheme = returnedRtsp.substring(RTSP.length());
String authority = afterScheme;
String pathAndQuery = "";
int slashIdx = afterScheme.indexOf('/');
if (slashIdx >= 0) {
authority = afterScheme.substring(0, slashIdx);
pathAndQuery = afterScheme.substring(slashIdx);
}
// strip possible creds in returned authority
int atIdx = authority.lastIndexOf('@');
if (atIdx >= 0) {
authority = authority.substring(atIdx + 1);
}
String returnedHost = authority;
int returnedPort = -1;
int colonIdx = authority.lastIndexOf(':');
if (colonIdx > -1) {
returnedHost = authority.substring(0, colonIdx);
try {
returnedPort = Integer.parseInt(authority.substring(colonIdx + 1));
}
catch (Exception e) {
returnedPort = -1;
}
}

String inputHost = null;
int inputPort = -1;
boolean inputIsRtsp = false;
if (inputAddress != null) {
inputIsRtsp = inputAddress.startsWith(RTSP) || inputAddress.startsWith("rtsps://");
String tmp = inputAddress;
int schemeIdx = tmp.indexOf("//");
if (schemeIdx >= 0) {
tmp = tmp.substring(schemeIdx + 2);
}
int at = tmp.lastIndexOf('@');
if (at >= 0) {
tmp = tmp.substring(at + 1);
}
int slash = tmp.indexOf('/');
if (slash >= 0) {
tmp = tmp.substring(0, slash);
}
int col = tmp.lastIndexOf(':');
if (col > -1) {
inputHost = tmp.substring(0, col);
try {
inputPort = Integer.parseInt(tmp.substring(col + 1));
}
catch (Exception e) {
inputPort = -1;
}
}
else {
inputHost = tmp;
}
}

boolean shouldRewrite = isPrivateOrLocalHost(returnedHost);

String targetHost = returnedHost;
int targetPort = returnedPort;
if (shouldRewrite && inputHost != null && !inputHost.isEmpty()) {
targetHost = inputHost;
if (inputIsRtsp && inputPort > -1) {
targetPort = inputPort;
}
}

StringBuilder sb = new StringBuilder(RTSP);
sb.append(authparam);
sb.append(targetHost);
if (targetPort > -1) {
sb.append(':').append(targetPort);
}
sb.append(pathAndQuery);
return sb.toString();
}

private static boolean isPrivateOrLocalHost(String host) {
if (host == null) {
return false;
}
String h = host.trim().toLowerCase();
if ("localhost".equals(h) || h.startsWith("127.")) {
return true;
}
// link-local and RFC1918 ranges
if (h.startsWith("10.")) return true;
if (h.startsWith("192.168.")) return true;
if (h.startsWith("169.254.")) return true;
if (h.startsWith("172.")) {
// 172.16.0.0 – 172.31.255.255
try {
String[] parts = h.split("\\.");
if (parts.length > 1) {
int second = Integer.parseInt(parts[1]);
return second >= 16 && second <= 31;
}
}
catch (Exception e) {
return false;
}
}
return false;
}


/**
* Parse the string to check it's a valid url
Expand Down Expand Up @@ -1570,8 +1686,7 @@ else if (Objects.equals(broadcast.getType(), AntMediaApplicationAdapter.IP_CAMER

if (result.isSuccess())
{
String authparam = broadcast.getUsername() + ":" + broadcast.getPassword() + "@";
String rtspURLWithAuth = RTSP + authparam + result.getMessage().substring(RTSP.length());
String rtspURLWithAuth = buildRtspUrlWithAuthPreservingPublicHost(broadcast.getIpAddr(), broadcast.getUsername(), broadcast.getPassword(), result.getMessage());
logger.info("rtsp url with auth: {}", rtspURLWithAuth);
broadcast.setStreamUrl(rtspURLWithAuth);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.antmedia.test.rest;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

import io.antmedia.rest.RestServiceBase;

public class RestServiceBaseRtspRewriteTest {

static class TestableRestServiceBase extends RestServiceBase {
public String callBuild(String inputAddress, String username, String password, String returnedRtsp) {
return buildRtspUrlWithAuthPreservingPublicHost(inputAddress, username, password, returnedRtsp);
}
}

@Test
public void testRewritePrivateHostWithInputPort() {
TestableRestServiceBase svc = new TestableRestServiceBase();
String input = "http://public.example.com:8554/onvif/device_service";
String returnedRtsp = "rtsp://192.168.1.100:554/stream1";
String out = svc.callBuild(input, "user", "pass", returnedRtsp);
assertEquals("rtsp://user:[email protected]:554/stream1", out);
}

@Test
public void testRewritePrivateHostPreserveReturnedPortWhenInputNoPort() {
TestableRestServiceBase svc = new TestableRestServiceBase();
String input = "http://public.example.com";
String returnedRtsp = "rtsp://192.168.0.10:8554/live.sdp";
String out = svc.callBuild(input, "u", "p", returnedRtsp);
assertEquals("rtsp://u:[email protected]:8554/live.sdp", out);
}

@Test
public void testPreserveWhenReturnedIsPublic() {
TestableRestServiceBase svc = new TestableRestServiceBase();
String input = "http://public.example.com:8554";
String returnedRtsp = "rtsp://203.0.113.10:8554/channel=1";
String out = svc.callBuild(input, "a", "b", returnedRtsp);
assertEquals("rtsp://a:[email protected]:8554/channel=1", out);
}

@Test
public void testStripReturnedCredentials() {
TestableRestServiceBase svc = new TestableRestServiceBase();
String input = "http://example.net";
String returnedRtsp = "rtsp://cam:[email protected]:554/path/to/stream";
String out = svc.callBuild(input, "newU", "newP", returnedRtsp);
assertEquals("rtsp://newU:[email protected]:554/path/to/stream", out);
}

@Test
public void testParseInputWithSchemeCredsAndPath() {
TestableRestServiceBase svc = new TestableRestServiceBase();
String input = "https://inpUser:[email protected]:7441/onvif";
String returnedRtsp = "rtsp://192.168.2.20/stream"; // no port -> keep returned no-port
String out = svc.callBuild(input, "U", "P", returnedRtsp);
assertEquals("rtsp://U:[email protected]/stream", out);
}

@Test
public void testNullReturnedRtsp() {
TestableRestServiceBase svc = new TestableRestServiceBase();
String out = svc.callBuild("http://host", "u", "p", null);
assertEquals("rtsp://u:p@", out);
}
}


Loading