forked from j62/ctbrec
Add support for TLS and changing the context path
This commit is contained in:
parent
bb02b5fd9f
commit
f12a20a15e
|
@ -118,24 +118,28 @@ public class Player {
|
||||||
public void run() {
|
public void run() {
|
||||||
running = true;
|
running = true;
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
Config cfg = Config.getInstance();
|
||||||
try {
|
try {
|
||||||
if (Config.getInstance().getSettings().localRecording && rec != null) {
|
if (cfg.getSettings().localRecording && rec != null) {
|
||||||
File file = new File(Config.getInstance().getSettings().recordingsDir, rec.getPath());
|
File file = new File(cfg.getSettings().recordingsDir, rec.getPath());
|
||||||
String[] args = new String[] {
|
String[] args = new String[] {
|
||||||
Config.getInstance().getSettings().mediaPlayer,
|
cfg.getSettings().mediaPlayer,
|
||||||
file.getName()
|
file.getName()
|
||||||
};
|
};
|
||||||
playerProcess = rt.exec(args, OS.getEnvironment(), file.getParentFile());
|
playerProcess = rt.exec(args, OS.getEnvironment(), file.getParentFile());
|
||||||
} else {
|
} else {
|
||||||
if(Config.getInstance().getSettings().requireAuthentication) {
|
if(cfg.getSettings().requireAuthentication) {
|
||||||
URL u = new URL(url);
|
URL u = new URL(url);
|
||||||
String path = u.getPath();
|
String path = u.getPath();
|
||||||
byte[] key = Config.getInstance().getSettings().key;
|
if(!cfg.getContextPath().isEmpty()) {
|
||||||
|
path = path.substring(cfg.getContextPath().length());
|
||||||
|
}
|
||||||
|
byte[] key = cfg.getSettings().key;
|
||||||
String hmac = Hmac.calculate(path, key);
|
String hmac = Hmac.calculate(path, key);
|
||||||
url = url + "?hmac=" + hmac;
|
url = url + "?hmac=" + hmac;
|
||||||
}
|
}
|
||||||
LOG.debug("Playing {}", url);
|
LOG.debug("Playing {}", url);
|
||||||
playerProcess = rt.exec(Config.getInstance().getSettings().mediaPlayer + " " + url);
|
playerProcess = rt.exec(cfg.getSettings().mediaPlayer + " " + url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create threads, which read stdout and stderr of the player process. these are needed,
|
// create threads, which read stdout and stderr of the player process. these are needed,
|
||||||
|
|
|
@ -472,7 +472,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
File target = chooser.showSaveDialog(null);
|
File target = chooser.showSaveDialog(null);
|
||||||
if(target != null) {
|
if(target != null) {
|
||||||
config.getSettings().lastDownloadDir = target.getParent();
|
config.getSettings().lastDownloadDir = target.getParent();
|
||||||
String hlsBase = "http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/hls";
|
String hlsBase = config.getServerUrl() + "/hls";
|
||||||
URL url = new URL(hlsBase + recording.getPath() + "/playlist.m3u8");
|
URL url = new URL(hlsBase + recording.getPath() + "/playlist.m3u8");
|
||||||
LOG.info("Downloading {}", recording.getPath());
|
LOG.info("Downloading {}", recording.getPath());
|
||||||
|
|
||||||
|
@ -542,7 +542,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}.start();
|
}.start();
|
||||||
} else {
|
} else {
|
||||||
String hlsBase = "http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/hls";
|
String hlsBase = Config.getInstance().getServerUrl() + "/hls";
|
||||||
url = hlsBase + recording.getPath() + "/playlist.m3u8";
|
url = hlsBase + recording.getPath() + "/playlist.m3u8";
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -61,8 +61,10 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private TextField overviewUpdateIntervalInSecs;
|
private TextField overviewUpdateIntervalInSecs;
|
||||||
private TextField leaveSpaceOnDevice;
|
private TextField leaveSpaceOnDevice;
|
||||||
private TextField minimumLengthInSecs;
|
private TextField minimumLengthInSecs;
|
||||||
|
private TextField servletContext;
|
||||||
private CheckBox loadResolution;
|
private CheckBox loadResolution;
|
||||||
private CheckBox secureCommunication = new CheckBox();
|
private CheckBox useAuthentication = new CheckBox();
|
||||||
|
private CheckBox useTLS = new CheckBox();
|
||||||
private CheckBox chooseStreamQuality = new CheckBox();
|
private CheckBox chooseStreamQuality = new CheckBox();
|
||||||
private CheckBox multiplePlayers = new CheckBox();
|
private CheckBox multiplePlayers = new CheckBox();
|
||||||
private CheckBox updateThumbnails = new CheckBox();
|
private CheckBox updateThumbnails = new CheckBox();
|
||||||
|
@ -152,7 +154,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private Node createRecordLocationPanel() {
|
private Node createRecordLocationPanel() {
|
||||||
GridPane layout = createGridLayout();
|
GridPane layout = createGridLayout();
|
||||||
Label l = new Label("Record Location");
|
Label l = new Label("Record Location");
|
||||||
layout.add(l, 0, 0);
|
int row = 0;
|
||||||
|
layout.add(l, 0, row);
|
||||||
recordLocation = new ToggleGroup();
|
recordLocation = new ToggleGroup();
|
||||||
recordLocal = new RadioButton("Local");
|
recordLocal = new RadioButton("Local");
|
||||||
recordRemote = new RadioButton("Remote");
|
recordRemote = new RadioButton("Remote");
|
||||||
|
@ -160,8 +163,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
recordRemote.setToggleGroup(recordLocation);
|
recordRemote.setToggleGroup(recordLocation);
|
||||||
recordLocal.setSelected(Config.getInstance().getSettings().localRecording);
|
recordLocal.setSelected(Config.getInstance().getSettings().localRecording);
|
||||||
recordRemote.setSelected(!recordLocal.isSelected());
|
recordRemote.setSelected(!recordLocal.isSelected());
|
||||||
layout.add(recordLocal, 1, 0);
|
layout.add(recordLocal, 1, row);
|
||||||
layout.add(recordRemote, 2, 0);
|
layout.add(recordRemote, 2, row++);
|
||||||
recordLocation.selectedToggleProperty().addListener((e) -> {
|
recordLocation.selectedToggleProperty().addListener((e) -> {
|
||||||
Config.getInstance().getSettings().localRecording = recordLocal.isSelected();
|
Config.getInstance().getSettings().localRecording = recordLocal.isSelected();
|
||||||
setRecordingMode(recordLocal.isSelected());
|
setRecordingMode(recordLocal.isSelected());
|
||||||
|
@ -172,7 +175,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
GridPane.setMargin(recordLocal, new Insets(0, 0, CHECKBOX_MARGIN, 0));
|
GridPane.setMargin(recordLocal, new Insets(0, 0, CHECKBOX_MARGIN, 0));
|
||||||
GridPane.setMargin(recordRemote, new Insets(0, 0, CHECKBOX_MARGIN, 0));
|
GridPane.setMargin(recordRemote, new Insets(0, 0, CHECKBOX_MARGIN, 0));
|
||||||
|
|
||||||
layout.add(new Label("Server"), 0, 1);
|
layout.add(new Label("Server"), 0, row);
|
||||||
server = new TextField(Config.getInstance().getSettings().httpServer);
|
server = new TextField(Config.getInstance().getSettings().httpServer);
|
||||||
server.textProperty().addListener((ob, o, n) -> {
|
server.textProperty().addListener((ob, o, n) -> {
|
||||||
if(!server.getText().isEmpty()) {
|
if(!server.getText().isEmpty()) {
|
||||||
|
@ -183,9 +186,9 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
GridPane.setFillWidth(server, true);
|
GridPane.setFillWidth(server, true);
|
||||||
GridPane.setHgrow(server, Priority.ALWAYS);
|
GridPane.setHgrow(server, Priority.ALWAYS);
|
||||||
GridPane.setColumnSpan(server, 2);
|
GridPane.setColumnSpan(server, 2);
|
||||||
layout.add(server, 1, 1);
|
layout.add(server, 1, row++);
|
||||||
|
|
||||||
layout.add(new Label("Port"), 0, 2);
|
layout.add(new Label("Port"), 0, row);
|
||||||
port = new TextField(Integer.toString(Config.getInstance().getSettings().httpPort));
|
port = new TextField(Integer.toString(Config.getInstance().getSettings().httpPort));
|
||||||
port.textProperty().addListener((observable, oldValue, newValue) -> {
|
port.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if (!newValue.matches("\\d*")) {
|
if (!newValue.matches("\\d*")) {
|
||||||
|
@ -199,14 +202,27 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
GridPane.setFillWidth(port, true);
|
GridPane.setFillWidth(port, true);
|
||||||
GridPane.setHgrow(port, Priority.ALWAYS);
|
GridPane.setHgrow(port, Priority.ALWAYS);
|
||||||
GridPane.setColumnSpan(port, 2);
|
GridPane.setColumnSpan(port, 2);
|
||||||
layout.add(port, 1, 2);
|
layout.add(port, 1, row++);
|
||||||
|
|
||||||
|
layout.add(new Label("Path"), 0, row);
|
||||||
|
servletContext = new TextField(Config.getInstance().getSettings().servletContext);
|
||||||
|
servletContext.setPromptText("e.g. /ctbrec");
|
||||||
|
servletContext.setTooltip(new Tooltip("Leave empty, if you didn't change the servletContext in the server config"));
|
||||||
|
servletContext.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
Config.getInstance().getSettings().servletContext = servletContext.getText();
|
||||||
|
saveConfig();
|
||||||
|
});
|
||||||
|
GridPane.setFillWidth(servletContext, true);
|
||||||
|
GridPane.setHgrow(servletContext, Priority.ALWAYS);
|
||||||
|
GridPane.setColumnSpan(servletContext, 2);
|
||||||
|
layout.add(servletContext, 1, row++);
|
||||||
|
|
||||||
l = new Label("Require authentication");
|
l = new Label("Require authentication");
|
||||||
layout.add(l, 0, 3);
|
layout.add(l, 0, row);
|
||||||
secureCommunication.setSelected(Config.getInstance().getSettings().requireAuthentication);
|
useAuthentication.setSelected(Config.getInstance().getSettings().requireAuthentication);
|
||||||
secureCommunication.setOnAction((e) -> {
|
useAuthentication.setOnAction((e) -> {
|
||||||
Config.getInstance().getSettings().requireAuthentication = secureCommunication.isSelected();
|
Config.getInstance().getSettings().requireAuthentication = useAuthentication.isSelected();
|
||||||
if(secureCommunication.isSelected()) {
|
if(useAuthentication.isSelected()) {
|
||||||
byte[] key = Config.getInstance().getSettings().key;
|
byte[] key = Config.getInstance().getSettings().key;
|
||||||
if(key == null) {
|
if(key == null) {
|
||||||
key = Hmac.generateKey();
|
key = Hmac.generateKey();
|
||||||
|
@ -226,8 +242,19 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
GridPane.setMargin(l, new Insets(4, CHECKBOX_MARGIN, 0, 0));
|
GridPane.setMargin(l, new Insets(4, CHECKBOX_MARGIN, 0, 0));
|
||||||
GridPane.setMargin(secureCommunication, new Insets(4, 0, 0, 0));
|
GridPane.setMargin(useAuthentication, new Insets(4, 0, 0, 0));
|
||||||
layout.add(secureCommunication, 1, 3);
|
layout.add(useAuthentication, 1, row++);
|
||||||
|
|
||||||
|
l = new Label("Use Secure Communication (TLS)");
|
||||||
|
layout.add(l, 0, row);
|
||||||
|
useTLS.setSelected(Config.getInstance().getSettings().transportLayerSecurity);
|
||||||
|
useTLS.setOnAction((e) -> {
|
||||||
|
Config.getInstance().getSettings().transportLayerSecurity = useTLS.isSelected();
|
||||||
|
saveConfig();
|
||||||
|
});
|
||||||
|
GridPane.setMargin(l, new Insets(4, CHECKBOX_MARGIN, 0, 0));
|
||||||
|
GridPane.setMargin(useTLS, new Insets(4, 0, 0, 0));
|
||||||
|
layout.add(useTLS, 1, row++);
|
||||||
|
|
||||||
TitledPane recordLocation = new TitledPane("Record Location", layout);
|
TitledPane recordLocation = new TitledPane("Record Location", layout);
|
||||||
recordLocation.setCollapsible(false);
|
recordLocation.setCollapsible(false);
|
||||||
|
@ -582,7 +609,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private void setRecordingMode(boolean local) {
|
private void setRecordingMode(boolean local) {
|
||||||
server.setDisable(local);
|
server.setDisable(local);
|
||||||
port.setDisable(local);
|
port.setDisable(local);
|
||||||
secureCommunication.setDisable(local);
|
useAuthentication.setDisable(local);
|
||||||
|
useTLS.setDisable(local);
|
||||||
recordingsDirectory.setDisable(!local);
|
recordingsDirectory.setDisable(!local);
|
||||||
splitAfter.setDisable(!local);
|
splitAfter.setDisable(!local);
|
||||||
maxResolution.setDisable(!local);
|
maxResolution.setDisable(!local);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -163,4 +164,23 @@ public class Config {
|
||||||
return new File(getSettings().recordingsDir);
|
return new File(getSettings().recordingsDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getServerUrl() {
|
||||||
|
String scheme = getSettings().transportLayerSecurity ? "https" : "http";
|
||||||
|
//int port = getSettings().transportLayerSecurity ? getSettings().httpSecurePort : getSettings().httpPort;
|
||||||
|
int port = getSettings().httpPort;
|
||||||
|
String baseUrl = scheme + "://" + getSettings().httpServer + ":" + port + getContextPath();
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContextPath() {
|
||||||
|
String context = Optional.ofNullable(getSettings().servletContext).orElse("");
|
||||||
|
if (!context.startsWith("/") && !context.isEmpty()) {
|
||||||
|
context = '/' + context;
|
||||||
|
}
|
||||||
|
if (context.endsWith("/")) {
|
||||||
|
context = context.substring(0, context.length() - 1);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,7 @@ public class Settings {
|
||||||
public String flirt4freeUsername;
|
public String flirt4freeUsername;
|
||||||
public boolean generatePlaylist = true;
|
public boolean generatePlaylist = true;
|
||||||
public int httpPort = 8080;
|
public int httpPort = 8080;
|
||||||
|
public int httpSecurePort = 8443;
|
||||||
public String httpServer = "localhost";
|
public String httpServer = "localhost";
|
||||||
public int httpTimeout = 10000;
|
public int httpTimeout = 10000;
|
||||||
public String httpUserAgent = "Mozilla/5.0 Gecko/20100101 Firefox/62.0";
|
public String httpUserAgent = "Mozilla/5.0 Gecko/20100101 Firefox/62.0";
|
||||||
|
@ -100,12 +101,14 @@ public class Settings {
|
||||||
public String recordingsSortColumn = "";
|
public String recordingsSortColumn = "";
|
||||||
public String recordingsSortType = "";
|
public String recordingsSortType = "";
|
||||||
public boolean requireAuthentication = false;
|
public boolean requireAuthentication = false;
|
||||||
|
public String servletContext = "";
|
||||||
public boolean showPlayerStarting = false;
|
public boolean showPlayerStarting = false;
|
||||||
public boolean singlePlayer = true;
|
public boolean singlePlayer = true;
|
||||||
public int splitRecordings = 0;
|
public int splitRecordings = 0;
|
||||||
public String startTab = "Settings";
|
public String startTab = "Settings";
|
||||||
public String streamatePassword = "";
|
public String streamatePassword = "";
|
||||||
public String streamateUsername = "";
|
public String streamateUsername = "";
|
||||||
|
public boolean transportLayerSecurity = true;
|
||||||
public int thumbWidth = 180;
|
public int thumbWidth = 180;
|
||||||
public boolean updateThumbnails = true;
|
public boolean updateThumbnails = true;
|
||||||
public String username = ""; // chaturbate username TODO maybe rename this onetime
|
public String username = ""; // chaturbate username TODO maybe rename this onetime
|
||||||
|
|
|
@ -6,6 +6,11 @@ import java.io.IOException;
|
||||||
import java.net.Authenticator;
|
import java.net.Authenticator;
|
||||||
import java.net.PasswordAuthentication;
|
import java.net.PasswordAuthentication;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -13,6 +18,12 @@ import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -124,9 +135,45 @@ public abstract class HttpClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if transport layer security (TLS) is switched on, accept the self signed cert from the server
|
||||||
|
if (Config.getInstance().getSettings().transportLayerSecurity) {
|
||||||
|
acceptAllTlsCerts(builder);
|
||||||
|
}
|
||||||
|
|
||||||
client = builder.build();
|
client = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a very simple and insecure solution to accept the self-signed cert from
|
||||||
|
* the server. The side effect is, that certificates from other servers are neither checked!
|
||||||
|
* TODO Delegate to the default trustmanager, if it is not the self-signed cert
|
||||||
|
*/
|
||||||
|
private void acceptAllTlsCerts(Builder builder) {
|
||||||
|
X509TrustManager x509TrustManager = new X509TrustManager() {
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
X509Certificate[] x509Certificates = new X509Certificate[0];
|
||||||
|
return x509Certificates;
|
||||||
|
}
|
||||||
|
@Override public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {}
|
||||||
|
@Override public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final TrustManager[] trustManagers = new TrustManager[] { x509TrustManager };
|
||||||
|
final String PROTOCOL = "TLSv1.2";
|
||||||
|
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
|
||||||
|
KeyManager[] keyManagers = null;
|
||||||
|
SecureRandom secureRandom = new SecureRandom();
|
||||||
|
sslContext.init(keyManagers, trustManagers, secureRandom);
|
||||||
|
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||||
|
builder.sslSocketFactory(sslSocketFactory, x509TrustManager);
|
||||||
|
builder.hostnameVerifier((name, sslSession) -> true);
|
||||||
|
} catch (KeyManagementException | NoSuchAlgorithmException e) {
|
||||||
|
LOG.error("Couldn't install trust managers for TLS connections");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
persistCookies();
|
persistCookies();
|
||||||
client.connectionPool().evictAll();
|
client.connectionPool().evictAll();
|
||||||
|
|
|
@ -38,6 +38,7 @@ import okhttp3.Response;
|
||||||
public class RemoteRecorder implements Recorder {
|
public class RemoteRecorder implements Recorder {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(RemoteRecorder.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(RemoteRecorder.class);
|
||||||
|
|
||||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
private Moshi moshi = new Moshi.Builder()
|
private Moshi moshi = new Moshi.Builder()
|
||||||
.add(Instant.class, new InstantJsonAdapter())
|
.add(Instant.class, new InstantJsonAdapter())
|
||||||
|
@ -71,6 +72,10 @@ public class RemoteRecorder implements Recorder {
|
||||||
syncThread.start();
|
syncThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getRecordingEndpoint() {
|
||||||
|
return config.getServerUrl() + "/rec";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void startRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
public void startRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||||
sendRequest("start", model);
|
sendRequest("start", model);
|
||||||
|
@ -86,7 +91,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
LOG.debug("Sending request to recording server: {}", payload);
|
LOG.debug("Sending request to recording server: {}", payload);
|
||||||
RequestBody body = RequestBody.create(JSON, payload);
|
RequestBody body = RequestBody.create(JSON, payload);
|
||||||
Request.Builder builder = new Request.Builder()
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url(getRecordingEndpoint())
|
||||||
.post(body);
|
.post(body);
|
||||||
addHmacIfNeeded(payload, builder);
|
addHmacIfNeeded(payload, builder);
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
|
@ -172,7 +177,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
String msg = "{\"action\": \"space\"}";
|
String msg = "{\"action\": \"space\"}";
|
||||||
RequestBody body = RequestBody.create(JSON, msg);
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
Request.Builder builder = new Request.Builder()
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url(getRecordingEndpoint())
|
||||||
.post(body);
|
.post(body);
|
||||||
addHmacIfNeeded(msg, builder);
|
addHmacIfNeeded(msg, builder);
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
|
@ -196,7 +201,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
String msg = "{\"action\": \"list\"}";
|
String msg = "{\"action\": \"list\"}";
|
||||||
RequestBody body = RequestBody.create(JSON, msg);
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
Request.Builder builder = new Request.Builder()
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url(getRecordingEndpoint())
|
||||||
.post(body);
|
.post(body);
|
||||||
addHmacIfNeeded(msg, builder);
|
addHmacIfNeeded(msg, builder);
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
|
@ -231,7 +236,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
String msg = "{\"action\": \"listOnline\"}";
|
String msg = "{\"action\": \"listOnline\"}";
|
||||||
RequestBody body = RequestBody.create(JSON, msg);
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
Request.Builder builder = new Request.Builder()
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url(getRecordingEndpoint())
|
||||||
.post(body);
|
.post(body);
|
||||||
addHmacIfNeeded(msg, builder);
|
addHmacIfNeeded(msg, builder);
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
|
@ -265,7 +270,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
String msg = "{\"action\": \"recordings\"}";
|
String msg = "{\"action\": \"recordings\"}";
|
||||||
RequestBody body = RequestBody.create(JSON, msg);
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
Request.Builder builder = new Request.Builder()
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url(getRecordingEndpoint())
|
||||||
.post(body);
|
.post(body);
|
||||||
addHmacIfNeeded(msg, builder);
|
addHmacIfNeeded(msg, builder);
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
|
@ -344,7 +349,7 @@ public class RemoteRecorder implements Recorder {
|
||||||
String msg = recordingRequestAdapter.toJson(recReq);
|
String msg = recordingRequestAdapter.toJson(recReq);
|
||||||
RequestBody body = RequestBody.create(JSON, msg);
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
Request.Builder builder = new Request.Builder()
|
Request.Builder builder = new Request.Builder()
|
||||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
.url(getRecordingEndpoint())
|
||||||
.post(body);
|
.post(body);
|
||||||
addHmacIfNeeded(msg, builder);
|
addHmacIfNeeded(msg, builder);
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
|
@ -472,7 +477,8 @@ public class RemoteRecorder implements Recorder {
|
||||||
String msg = recordingRequestAdapter.toJson(recReq);
|
String msg = recordingRequestAdapter.toJson(recReq);
|
||||||
LOG.debug(msg);
|
LOG.debug(msg);
|
||||||
RequestBody body = RequestBody.create(JSON, msg);
|
RequestBody body = RequestBody.create(JSON, msg);
|
||||||
Request.Builder builder = new Request.Builder().url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
Request.Builder builder = new Request.Builder()
|
||||||
|
.url(getRecordingEndpoint())
|
||||||
.post(body);
|
.post(body);
|
||||||
addHmacIfNeeded(msg, builder);
|
addHmacIfNeeded(msg, builder);
|
||||||
Request request = builder.build();
|
Request request = builder.build();
|
||||||
|
|
|
@ -89,6 +89,9 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
||||||
URL u = new URL(segmentPlaylistUri);
|
URL u = new URL(segmentPlaylistUri);
|
||||||
String path = u.getPath();
|
String path = u.getPath();
|
||||||
byte[] key = Config.getInstance().getSettings().key;
|
byte[] key = Config.getInstance().getSettings().key;
|
||||||
|
if(!Config.getInstance().getContextPath().isEmpty()) {
|
||||||
|
path = path.substring(Config.getInstance().getContextPath().length());
|
||||||
|
}
|
||||||
String hmac = Hmac.calculate(path, key);
|
String hmac = Hmac.calculate(path, key);
|
||||||
segmentPlaylistUri = segmentPlaylistUri + "?hmac=" + hmac;
|
segmentPlaylistUri = segmentPlaylistUri + "?hmac=" + hmac;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
|
@ -13,14 +15,19 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
public class StaticFileServlet extends HttpServlet {
|
public class StaticFileServlet extends HttpServlet {
|
||||||
|
|
||||||
private String classPathRoot;
|
private String classPathRoot;
|
||||||
|
private Map<String, String> mimetypes = new HashMap<>();
|
||||||
|
|
||||||
public StaticFileServlet(String classPathRoot) {
|
public StaticFileServlet(String classPathRoot) {
|
||||||
this.classPathRoot = classPathRoot;
|
this.classPathRoot = classPathRoot;
|
||||||
|
mimetypes.put("css", "text/css");
|
||||||
|
mimetypes.put("js", "application/javascript");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
String request = req.getRequestURI();
|
String request = req.getRequestURI();
|
||||||
|
String contextPath = getServletContext().getContextPath();
|
||||||
|
request = request.substring(contextPath.length());
|
||||||
serveFile(request, resp);
|
serveFile(request, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +41,12 @@ public class StaticFileServlet extends HttpServlet {
|
||||||
if (resourceAsStream == null) {
|
if (resourceAsStream == null) {
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
resp.setContentType(URLConnection.guessContentTypeFromName(resource));
|
|
||||||
|
String mimetype = URLConnection.guessContentTypeFromName(resource);
|
||||||
|
if (mimetype == null) {
|
||||||
|
mimetype = guessMimeType(resource);
|
||||||
|
}
|
||||||
|
resp.setContentType(mimetype);
|
||||||
resp.setStatus(HttpServletResponse.SC_OK);
|
resp.setStatus(HttpServletResponse.SC_OK);
|
||||||
OutputStream out = resp.getOutputStream();
|
OutputStream out = resp.getOutputStream();
|
||||||
int length = 0;
|
int length = 0;
|
||||||
|
@ -43,4 +55,12 @@ public class StaticFileServlet extends HttpServlet {
|
||||||
out.write(buffer, 0, length);
|
out.write(buffer, 0, length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String guessMimeType(String resource) {
|
||||||
|
try {
|
||||||
|
String extension = resource.substring(resource.lastIndexOf('.') + 1);
|
||||||
|
return mimetypes.get(extension);
|
||||||
|
} catch(Exception e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ public abstract class AbstractCtbrecServlet extends HttpServlet {
|
||||||
String httpHeaderHmac = req.getHeader("CTBREC-HMAC");
|
String httpHeaderHmac = req.getHeader("CTBREC-HMAC");
|
||||||
String hmac = null;
|
String hmac = null;
|
||||||
String url = req.getRequestURI();
|
String url = req.getRequestURI();
|
||||||
|
url = url.substring(getServletContext().getContextPath().length());
|
||||||
|
|
||||||
if(reqParamHmac != null) {
|
if(reqParamHmac != null) {
|
||||||
hmac = reqParamHmac;
|
hmac = reqParamHmac;
|
||||||
|
|
|
@ -32,7 +32,8 @@ public class HlsServlet extends AbstractCtbrecServlet {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
String request = req.getRequestURI().substring(5);
|
String contextPath = getServletContext().getContextPath();
|
||||||
|
String request = req.getRequestURI().substring(contextPath.length() + 5);
|
||||||
File recordingsDir = new File(config.getSettings().recordingsDir);
|
File recordingsDir = new File(config.getSettings().recordingsDir);
|
||||||
File requestedFile = new File(recordingsDir, request);
|
File requestedFile = new File(recordingsDir, request);
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,10 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.BindException;
|
import java.net.BindException;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
|
@ -25,11 +27,12 @@ import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
import org.eclipse.jetty.server.handler.HandlerList;
|
import org.eclipse.jetty.server.handler.HandlerList;
|
||||||
|
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.util.security.Constraint;
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
import org.eclipse.jetty.util.security.Credential;
|
import org.eclipse.jetty.util.security.Credential;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -68,7 +71,7 @@ public class HttpServer {
|
||||||
logEnvironment();
|
logEnvironment();
|
||||||
createSites();
|
createSites();
|
||||||
System.setProperty("ctbrec.server.mode", "1");
|
System.setProperty("ctbrec.server.mode", "1");
|
||||||
if(System.getProperty("ctbrec.config") == null) {
|
if (System.getProperty("ctbrec.config") == null) {
|
||||||
System.setProperty("ctbrec.config", "server.json");
|
System.setProperty("ctbrec.config", "server.json");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -83,12 +86,12 @@ public class HttpServer {
|
||||||
registerAlertSystem();
|
registerAlertSystem();
|
||||||
|
|
||||||
config = Config.getInstance();
|
config = Config.getInstance();
|
||||||
if(config.getSettings().key != null) {
|
if (config.getSettings().key != null) {
|
||||||
LOG.info("HMAC authentication is enabled");
|
LOG.info("HMAC authentication is enabled");
|
||||||
}
|
}
|
||||||
recorder = new NextGenLocalRecorder(config, sites);
|
recorder = new NextGenLocalRecorder(config, sites);
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
if(site.isEnabled()) {
|
if (site.isEnabled()) {
|
||||||
site.init();
|
site.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,7 +108,7 @@ public class HttpServer {
|
||||||
sites.add(new Fc2Live());
|
sites.add(new Fc2Live());
|
||||||
sites.add(new Flirt4Free());
|
sites.add(new Flirt4Free());
|
||||||
sites.add(new LiveJasmin());
|
sites.add(new LiveJasmin());
|
||||||
//sites.add(new MyFreeCams());
|
// sites.add(new MyFreeCams());
|
||||||
sites.add(new Streamate());
|
sites.add(new Streamate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,10 +117,10 @@ public class HttpServer {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
LOG.info("Shutting down");
|
LOG.info("Shutting down");
|
||||||
if(onlineMonitor != null) {
|
if (onlineMonitor != null) {
|
||||||
onlineMonitor.shutdown();
|
onlineMonitor.shutdown();
|
||||||
}
|
}
|
||||||
if(recorder != null) {
|
if (recorder != null) {
|
||||||
recorder.shutdown();
|
recorder.shutdown();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -140,48 +143,62 @@ public class HttpServer {
|
||||||
|
|
||||||
HttpConfiguration config = new HttpConfiguration();
|
HttpConfiguration config = new HttpConfiguration();
|
||||||
config.setSendServerVersion(false);
|
config.setSendServerVersion(false);
|
||||||
ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(config));
|
config.setSecurePort(this.config.getSettings().httpSecurePort);
|
||||||
|
config.setSecureScheme("https");
|
||||||
|
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(config);
|
||||||
|
|
||||||
|
SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
||||||
|
URL keyStoreUrl = getClass().getResource("/keystore.pkcs12");
|
||||||
|
String keyStoreSrc = System.getProperty("keystore.file", keyStoreUrl.toExternalForm());
|
||||||
|
String keyStorePassword = System.getProperty("keystore.password", "ctbrecsucks");
|
||||||
|
sslContextFactory.setKeyStorePath(keyStoreSrc);
|
||||||
|
sslContextFactory.setKeyStorePassword(keyStorePassword);
|
||||||
|
sslContextFactory.setTrustStorePath(keyStoreSrc);
|
||||||
|
sslContextFactory.setTrustStorePassword(keyStorePassword);
|
||||||
|
|
||||||
|
// connector for http
|
||||||
|
ServerConnector http = new ServerConnector(server, httpConnectionFactory);
|
||||||
http.setPort(this.config.getSettings().httpPort);
|
http.setPort(this.config.getSettings().httpPort);
|
||||||
http.setIdleTimeout(this.config.getSettings().httpTimeout);
|
http.setIdleTimeout(this.config.getSettings().httpTimeout);
|
||||||
server.addConnector(http);
|
|
||||||
|
|
||||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
// connector for https (TLS)
|
||||||
context.setContextPath("/secured/*");
|
ServerConnector https = new ServerConnector(server, sslContextFactory, httpConnectionFactory);
|
||||||
server.setHandler(context);
|
https.setPort(this.config.getSettings().httpSecurePort);
|
||||||
|
https.setIdleTimeout(this.config.getSettings().httpTimeout);
|
||||||
|
|
||||||
ServletHandler handler = new ServletHandler();
|
String contextPath = Config.getInstance().getContextPath();
|
||||||
//server.setHandler(handler);
|
ServletContextHandler basicAuthContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
HandlerList handlers = new HandlerList();
|
basicAuthContext.setContextPath(contextPath + "/secured");
|
||||||
handlers.setHandlers(new Handler[] { context, handler });
|
|
||||||
server.setHandler(handlers);
|
ServletContextHandler defaultContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
|
defaultContext.setContextPath(contextPath);
|
||||||
|
|
||||||
RecorderServlet recorderServlet = new RecorderServlet(recorder, sites);
|
RecorderServlet recorderServlet = new RecorderServlet(recorder, sites);
|
||||||
ServletHolder holder = new ServletHolder(recorderServlet);
|
ServletHolder holder = new ServletHolder(recorderServlet);
|
||||||
handler.addServletWithMapping(holder, "/rec");
|
defaultContext.addServlet(holder, "/rec");
|
||||||
|
|
||||||
HlsServlet hlsServlet = new HlsServlet(this.config);
|
HlsServlet hlsServlet = new HlsServlet(this.config);
|
||||||
holder = new ServletHolder(hlsServlet);
|
holder = new ServletHolder(hlsServlet);
|
||||||
handler.addServletWithMapping(holder, "/hls/*");
|
defaultContext.addServlet(holder, "/hls/*");
|
||||||
|
|
||||||
|
|
||||||
if (this.config.getSettings().webinterface) {
|
if (this.config.getSettings().webinterface) {
|
||||||
LOG.info("Register static file servlet under {}", context.getContextPath());
|
|
||||||
StaticFileServlet staticFileServlet = new StaticFileServlet("/html");
|
StaticFileServlet staticFileServlet = new StaticFileServlet("/html");
|
||||||
holder = new ServletHolder(staticFileServlet);
|
holder = new ServletHolder(staticFileServlet);
|
||||||
handler.addServletWithMapping(holder, "/static/*");
|
String staticFileContext = "/static/*";
|
||||||
//context.addServlet(holder, "/");
|
defaultContext.addServlet(holder, staticFileContext);
|
||||||
|
LOG.info("Register static file servlet under {}", defaultContext.getContextPath()+staticFileContext);
|
||||||
|
|
||||||
// servlet to retrieve the HMAC secured by basic auth
|
// servlet to retrieve the HMAC secured by basic auth
|
||||||
String username = this.config.getSettings().webinterfaceUsername;
|
String username = this.config.getSettings().webinterfaceUsername;
|
||||||
String password = this.config.getSettings().webinterfacePassword;
|
String password = this.config.getSettings().webinterfacePassword;
|
||||||
context.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
|
basicAuthContext.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
|
||||||
context.addServlet(new ServletHolder(new HttpServlet() {
|
basicAuthContext.addServlet(new ServletHolder(new HttpServlet() {
|
||||||
@Override
|
@Override
|
||||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||||
if(Objects.equal(username, req.getRemoteUser())) {
|
if (Objects.equal(username, req.getRemoteUser())) {
|
||||||
resp.setStatus(HttpServletResponse.SC_OK);
|
resp.setStatus(HttpServletResponse.SC_OK);
|
||||||
resp.setContentType("application/json");
|
resp.setContentType("application/json");
|
||||||
byte[] hmac = HttpServer.this.config.getSettings().key;
|
byte[] hmac = Optional.ofNullable(HttpServer.this.config.getSettings().key).orElse(new byte[0]);
|
||||||
JSONObject response = new JSONObject();
|
JSONObject response = new JSONObject();
|
||||||
response.put("hmac", new String(hmac, "utf-8"));
|
response.put("hmac", new String(hmac, "utf-8"));
|
||||||
resp.getOutputStream().println(response.toString());
|
resp.getOutputStream().println(response.toString());
|
||||||
|
@ -190,12 +207,25 @@ public class HttpServer {
|
||||||
}), "/hmac");
|
}), "/hmac");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.addConnector(http);
|
||||||
|
HandlerList handlers = new HandlerList();
|
||||||
|
if (this.config.getSettings().transportLayerSecurity) {
|
||||||
|
server.addConnector(https);
|
||||||
|
handlers.setHandlers(new Handler[] { new SecuredRedirectHandler(), basicAuthContext, defaultContext });
|
||||||
|
} else {
|
||||||
|
handlers.setHandlers(new Handler[] { basicAuthContext, defaultContext });
|
||||||
|
}
|
||||||
|
server.setHandler(handlers);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
server.start();
|
server.start();
|
||||||
server.join();
|
server.join();
|
||||||
} catch (BindException e) {
|
} catch (BindException e) {
|
||||||
LOG.error("Port {} is already in use", http.getPort(), e);
|
LOG.error("Port {} is already in use", http.getPort(), e);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.error("Server start failed", e);
|
||||||
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +268,8 @@ public class HttpServer {
|
||||||
LOG.debug("Java:\t{} {} {}", System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.version"));
|
LOG.debug("Java:\t{} {} {}", System.getProperty("java.vendor"), System.getProperty("java.vm.name"), System.getProperty("java.version"));
|
||||||
try {
|
try {
|
||||||
LOG.debug("ctbrec server {}", getVersion().toString());
|
LOG.debug("ctbrec server {}", getVersion().toString());
|
||||||
} catch (IOException e) {}
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Version getVersion() throws IOException {
|
private Version getVersion() throws IOException {
|
||||||
|
|
|
@ -11,27 +11,27 @@
|
||||||
<title>CTB Recorder</title>
|
<title>CTB Recorder</title>
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
<!-- Bootstrap core CSS -->
|
||||||
<link href="/static/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Custom fonts for this template -->
|
<!-- Custom fonts for this template -->
|
||||||
<link href="/static/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
<link href="vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
|
<link href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
<!-- Plugin CSS -->
|
<!-- Plugin CSS -->
|
||||||
<link href="/static/vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
|
<link href="vendor/magnific-popup/magnific-popup.css" rel="stylesheet" type="text/css">
|
||||||
<link href="/static/vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
|
<link href="vendor/jquery-ui/jquery-ui-1.12.1.css" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
<!-- Custom styles for this template -->
|
<!-- Custom styles for this template -->
|
||||||
<link href="/static/freelancer.css" rel="stylesheet">
|
<link href="freelancer.css" rel="stylesheet">
|
||||||
|
|
||||||
<!-- Flowplayer -->
|
<!-- Flowplayer -->
|
||||||
<link rel="stylesheet" href="/static/vendor/flowplayer/skin/skin.css">
|
<link rel="stylesheet" href="vendor/flowplayer/skin/skin.css">
|
||||||
|
|
||||||
<!-- custom css -->
|
<!-- custom css -->
|
||||||
<link rel="stylesheet" href="/static/custom.css">
|
<link rel="stylesheet" href="custom.css">
|
||||||
|
|
||||||
<link rel="shortcut icon" href="/static/favicon.png" type="image/x-icon" />
|
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.ui-front {
|
.ui-front {
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-pottom: 3rem">
|
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-pottom: 3rem">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand js-scroll-trigger" href="/static/index.html"><img src="/static/icon64.png" alt="Logo" />CTBREC</a>
|
<a class="navbar-brand js-scroll-trigger" href="index.html"><img src="icon64.png" alt="Logo" />CTBREC</a>
|
||||||
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse"
|
<button class="navbar-toggler navbar-toggler-right text-uppercase bg-primary text-white rounded" type="button" data-toggle="collapse"
|
||||||
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
Menu <i class="fa fa-bars"></i>
|
Menu <i class="fa fa-bars"></i>
|
||||||
|
@ -183,22 +183,22 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bootstrap core JavaScript -->
|
<!-- Bootstrap core JavaScript -->
|
||||||
<script src="/static/vendor/jquery/jquery.min.js"></script>
|
<script src="vendor/jquery/jquery.min.js"></script>
|
||||||
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<!-- Plugin JavaScript -->
|
<!-- Plugin JavaScript -->
|
||||||
<script src="/static/vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
<script src="vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
||||||
<script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
|
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||||
<script src="/static/vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
<script src="vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
||||||
<script src="/static/vendor/notify.js/notify.min.js"></script>
|
<script src="vendor/notify.js/notify.min.js"></script>
|
||||||
|
|
||||||
<!-- knockout -->
|
<!-- knockout -->
|
||||||
<script src="/static/vendor/knockout/knockout-3.5.0.js"></script>
|
<script src="vendor/knockout/knockout-3.5.0.js"></script>
|
||||||
<script src="/static/vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
<script src="vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Custom scripts for this template -->
|
<!-- Custom scripts for this template -->
|
||||||
<script src="/static/freelancer.min.js"></script>
|
<script src="freelancer.min.js"></script>
|
||||||
|
|
||||||
<div id="player-window" class="modal">
|
<div id="player-window" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
@ -209,10 +209,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- HLS MediaSource support -->
|
<!-- HLS MediaSource support -->
|
||||||
<script src="/static/vendor/hls.js/hls.js"></script>
|
<script src="vendor/hls.js/hls.js"></script>
|
||||||
|
|
||||||
<!-- CryptoJS for HMAc authentication -->
|
<!-- CryptoJS for HMAc authentication -->
|
||||||
<script src="/static/vendor/CryptoJS/hmac-sha256.js"></script>
|
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let onlineModels = [];
|
let onlineModels = [];
|
||||||
|
@ -248,7 +248,7 @@
|
||||||
let action = '{"action": "startByUrl", "model": ' + JSON.stringify(model) + '}';
|
let action = '{"action": "startByUrl", "model": ' + JSON.stringify(model) + '}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -277,7 +277,7 @@
|
||||||
let action = '{"action": "resume", "model": ' + JSON.stringify(model) + '}';
|
let action = '{"action": "resume", "model": ' + JSON.stringify(model) + '}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -305,7 +305,7 @@
|
||||||
let action = '{"action": "suspend", "model": ' + JSON.stringify(model) + '}';
|
let action = '{"action": "suspend", "model": ' + JSON.stringify(model) + '}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -333,7 +333,7 @@
|
||||||
let action = '{"action": "stop", "model": ' + JSON.stringify(model) + '}';
|
let action = '{"action": "stop", "model": ' + JSON.stringify(model) + '}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -363,7 +363,7 @@
|
||||||
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
|
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -395,9 +395,12 @@
|
||||||
hls.attachMedia(video);
|
hls.attachMedia(video);
|
||||||
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
||||||
let src = recording.playlist;
|
let src = recording.playlist;
|
||||||
|
let hmacOfPath = CryptoJS.HmacSHA256(src, hmac);
|
||||||
|
src = '..' + src;
|
||||||
if (hmac.length > 0) {
|
if (hmac.length > 0) {
|
||||||
src += "?hmac=" + CryptoJS.HmacSHA256(src, hmac);
|
src += "?hmac=" + hmacOfPath;
|
||||||
}
|
}
|
||||||
|
console.log(src);
|
||||||
hls.loadSource(src);
|
hls.loadSource(src);
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
||||||
console.log("manifest loaded, found " + data.levels.length + " quality level");
|
console.log("manifest loaded, found " + data.levels.length + " quality level");
|
||||||
|
@ -432,19 +435,18 @@
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
if (localStorage !== undefined && localStorage.hmac !== undefined) {
|
if (localStorage !== undefined && localStorage.hmac !== undefined) {
|
||||||
console.log('using hmac from cookie');
|
console.log('using hmac from local storage');
|
||||||
hmac = localStorage.hmac;
|
hmac = localStorage.hmac;
|
||||||
} else {
|
} else {
|
||||||
console.log('hmac not found in local storage. requesting hmac from server');
|
console.log('hmac not found in local storage. requesting hmac from server');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'GET',
|
type : 'GET',
|
||||||
url : '/secured/hmac',
|
url : '../secured/hmac',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000
|
timeout : 60000
|
||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
console.log(data);
|
|
||||||
hmac = data.hmac;
|
hmac = data.hmac;
|
||||||
if (localStorage !== undefined) {
|
if (localStorage !== undefined) {
|
||||||
console.log('saving hmac to local storage');
|
console.log('saving hmac to local storage');
|
||||||
|
@ -479,7 +481,7 @@
|
||||||
let action = '{"action": "listOnline"}';
|
let action = '{"action": "listOnline"}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -590,7 +592,7 @@
|
||||||
let action = '{"action": "list"}';
|
let action = '{"action": "list"}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -682,7 +684,7 @@
|
||||||
let action = '{"action": "recordings"}';
|
let action = '{"action": "recordings"}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -710,7 +712,7 @@
|
||||||
let action = '{"action": "space"}';
|
let action = '{"action": "space"}';
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type : 'POST',
|
type : 'POST',
|
||||||
url : '/rec',
|
url : '../rec',
|
||||||
dataType : 'json',
|
dataType : 'json',
|
||||||
async : true,
|
async : true,
|
||||||
timeout : 60000,
|
timeout : 60000,
|
||||||
|
@ -736,7 +738,7 @@
|
||||||
updateRecordings();
|
updateRecordings();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/modal.js"></script>
|
<script src="modal.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue