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() {
|
||||
running = true;
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
Config cfg = Config.getInstance();
|
||||
try {
|
||||
if (Config.getInstance().getSettings().localRecording && rec != null) {
|
||||
File file = new File(Config.getInstance().getSettings().recordingsDir, rec.getPath());
|
||||
if (cfg.getSettings().localRecording && rec != null) {
|
||||
File file = new File(cfg.getSettings().recordingsDir, rec.getPath());
|
||||
String[] args = new String[] {
|
||||
Config.getInstance().getSettings().mediaPlayer,
|
||||
cfg.getSettings().mediaPlayer,
|
||||
file.getName()
|
||||
};
|
||||
playerProcess = rt.exec(args, OS.getEnvironment(), file.getParentFile());
|
||||
} else {
|
||||
if(Config.getInstance().getSettings().requireAuthentication) {
|
||||
if(cfg.getSettings().requireAuthentication) {
|
||||
URL u = new URL(url);
|
||||
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);
|
||||
url = url + "?hmac=" + hmac;
|
||||
}
|
||||
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,
|
||||
|
|
|
@ -472,7 +472,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
File target = chooser.showSaveDialog(null);
|
||||
if(target != null) {
|
||||
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");
|
||||
LOG.info("Downloading {}", recording.getPath());
|
||||
|
||||
|
@ -542,7 +542,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
}.start();
|
||||
} else {
|
||||
String hlsBase = "http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/hls";
|
||||
String hlsBase = Config.getInstance().getServerUrl() + "/hls";
|
||||
url = hlsBase + recording.getPath() + "/playlist.m3u8";
|
||||
new Thread() {
|
||||
@Override
|
||||
|
|
|
@ -61,8 +61,10 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private TextField overviewUpdateIntervalInSecs;
|
||||
private TextField leaveSpaceOnDevice;
|
||||
private TextField minimumLengthInSecs;
|
||||
private TextField servletContext;
|
||||
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 multiplePlayers = new CheckBox();
|
||||
private CheckBox updateThumbnails = new CheckBox();
|
||||
|
@ -152,7 +154,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private Node createRecordLocationPanel() {
|
||||
GridPane layout = createGridLayout();
|
||||
Label l = new Label("Record Location");
|
||||
layout.add(l, 0, 0);
|
||||
int row = 0;
|
||||
layout.add(l, 0, row);
|
||||
recordLocation = new ToggleGroup();
|
||||
recordLocal = new RadioButton("Local");
|
||||
recordRemote = new RadioButton("Remote");
|
||||
|
@ -160,8 +163,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
recordRemote.setToggleGroup(recordLocation);
|
||||
recordLocal.setSelected(Config.getInstance().getSettings().localRecording);
|
||||
recordRemote.setSelected(!recordLocal.isSelected());
|
||||
layout.add(recordLocal, 1, 0);
|
||||
layout.add(recordRemote, 2, 0);
|
||||
layout.add(recordLocal, 1, row);
|
||||
layout.add(recordRemote, 2, row++);
|
||||
recordLocation.selectedToggleProperty().addListener((e) -> {
|
||||
Config.getInstance().getSettings().localRecording = 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(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.textProperty().addListener((ob, o, n) -> {
|
||||
if(!server.getText().isEmpty()) {
|
||||
|
@ -183,9 +186,9 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
GridPane.setFillWidth(server, true);
|
||||
GridPane.setHgrow(server, Priority.ALWAYS);
|
||||
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.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue.matches("\\d*")) {
|
||||
|
@ -199,14 +202,27 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
GridPane.setFillWidth(port, true);
|
||||
GridPane.setHgrow(port, Priority.ALWAYS);
|
||||
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");
|
||||
layout.add(l, 0, 3);
|
||||
secureCommunication.setSelected(Config.getInstance().getSettings().requireAuthentication);
|
||||
secureCommunication.setOnAction((e) -> {
|
||||
Config.getInstance().getSettings().requireAuthentication = secureCommunication.isSelected();
|
||||
if(secureCommunication.isSelected()) {
|
||||
layout.add(l, 0, row);
|
||||
useAuthentication.setSelected(Config.getInstance().getSettings().requireAuthentication);
|
||||
useAuthentication.setOnAction((e) -> {
|
||||
Config.getInstance().getSettings().requireAuthentication = useAuthentication.isSelected();
|
||||
if(useAuthentication.isSelected()) {
|
||||
byte[] key = Config.getInstance().getSettings().key;
|
||||
if(key == null) {
|
||||
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(secureCommunication, new Insets(4, 0, 0, 0));
|
||||
layout.add(secureCommunication, 1, 3);
|
||||
GridPane.setMargin(useAuthentication, new Insets(4, 0, 0, 0));
|
||||
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);
|
||||
recordLocation.setCollapsible(false);
|
||||
|
@ -582,7 +609,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
|||
private void setRecordingMode(boolean local) {
|
||||
server.setDisable(local);
|
||||
port.setDisable(local);
|
||||
secureCommunication.setDisable(local);
|
||||
useAuthentication.setDisable(local);
|
||||
useTLS.setDisable(local);
|
||||
recordingsDirectory.setDisable(!local);
|
||||
splitAfter.setDisable(!local);
|
||||
maxResolution.setDisable(!local);
|
||||
|
|
|
@ -11,6 +11,7 @@ import java.text.SimpleDateFormat;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -163,4 +164,23 @@ public class Config {
|
|||
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 boolean generatePlaylist = true;
|
||||
public int httpPort = 8080;
|
||||
public int httpSecurePort = 8443;
|
||||
public String httpServer = "localhost";
|
||||
public int httpTimeout = 10000;
|
||||
public String httpUserAgent = "Mozilla/5.0 Gecko/20100101 Firefox/62.0";
|
||||
|
@ -100,12 +101,14 @@ public class Settings {
|
|||
public String recordingsSortColumn = "";
|
||||
public String recordingsSortType = "";
|
||||
public boolean requireAuthentication = false;
|
||||
public String servletContext = "";
|
||||
public boolean showPlayerStarting = false;
|
||||
public boolean singlePlayer = true;
|
||||
public int splitRecordings = 0;
|
||||
public String startTab = "Settings";
|
||||
public String streamatePassword = "";
|
||||
public String streamateUsername = "";
|
||||
public boolean transportLayerSecurity = true;
|
||||
public int thumbWidth = 180;
|
||||
public boolean updateThumbnails = true;
|
||||
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.PasswordAuthentication;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
|
@ -13,6 +18,12 @@ import java.util.Map.Entry;
|
|||
import java.util.Set;
|
||||
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.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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
persistCookies();
|
||||
client.connectionPool().evictAll();
|
||||
|
|
|
@ -38,6 +38,7 @@ import okhttp3.Response;
|
|||
public class RemoteRecorder implements Recorder {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(RemoteRecorder.class);
|
||||
|
||||
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||
private Moshi moshi = new Moshi.Builder()
|
||||
.add(Instant.class, new InstantJsonAdapter())
|
||||
|
@ -71,6 +72,10 @@ public class RemoteRecorder implements Recorder {
|
|||
syncThread.start();
|
||||
}
|
||||
|
||||
private String getRecordingEndpoint() {
|
||||
return config.getServerUrl() + "/rec";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
|
||||
sendRequest("start", model);
|
||||
|
@ -86,7 +91,7 @@ public class RemoteRecorder implements Recorder {
|
|||
LOG.debug("Sending request to recording server: {}", payload);
|
||||
RequestBody body = RequestBody.create(JSON, payload);
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||
.url(getRecordingEndpoint())
|
||||
.post(body);
|
||||
addHmacIfNeeded(payload, builder);
|
||||
Request request = builder.build();
|
||||
|
@ -172,7 +177,7 @@ public class RemoteRecorder implements Recorder {
|
|||
String msg = "{\"action\": \"space\"}";
|
||||
RequestBody body = RequestBody.create(JSON, msg);
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||
.url(getRecordingEndpoint())
|
||||
.post(body);
|
||||
addHmacIfNeeded(msg, builder);
|
||||
Request request = builder.build();
|
||||
|
@ -196,7 +201,7 @@ public class RemoteRecorder implements Recorder {
|
|||
String msg = "{\"action\": \"list\"}";
|
||||
RequestBody body = RequestBody.create(JSON, msg);
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||
.url(getRecordingEndpoint())
|
||||
.post(body);
|
||||
addHmacIfNeeded(msg, builder);
|
||||
Request request = builder.build();
|
||||
|
@ -231,7 +236,7 @@ public class RemoteRecorder implements Recorder {
|
|||
String msg = "{\"action\": \"listOnline\"}";
|
||||
RequestBody body = RequestBody.create(JSON, msg);
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||
.url(getRecordingEndpoint())
|
||||
.post(body);
|
||||
addHmacIfNeeded(msg, builder);
|
||||
Request request = builder.build();
|
||||
|
@ -265,7 +270,7 @@ public class RemoteRecorder implements Recorder {
|
|||
String msg = "{\"action\": \"recordings\"}";
|
||||
RequestBody body = RequestBody.create(JSON, msg);
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||
.url(getRecordingEndpoint())
|
||||
.post(body);
|
||||
addHmacIfNeeded(msg, builder);
|
||||
Request request = builder.build();
|
||||
|
@ -344,7 +349,7 @@ public class RemoteRecorder implements Recorder {
|
|||
String msg = recordingRequestAdapter.toJson(recReq);
|
||||
RequestBody body = RequestBody.create(JSON, msg);
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url("http://" + config.getSettings().httpServer + ":" + config.getSettings().httpPort + "/rec")
|
||||
.url(getRecordingEndpoint())
|
||||
.post(body);
|
||||
addHmacIfNeeded(msg, builder);
|
||||
Request request = builder.build();
|
||||
|
@ -472,7 +477,8 @@ public class RemoteRecorder implements Recorder {
|
|||
String msg = recordingRequestAdapter.toJson(recReq);
|
||||
LOG.debug(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);
|
||||
addHmacIfNeeded(msg, builder);
|
||||
Request request = builder.build();
|
||||
|
|
|
@ -89,6 +89,9 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
|||
URL u = new URL(segmentPlaylistUri);
|
||||
String path = u.getPath();
|
||||
byte[] key = Config.getInstance().getSettings().key;
|
||||
if(!Config.getInstance().getContextPath().isEmpty()) {
|
||||
path = path.substring(Config.getInstance().getContextPath().length());
|
||||
}
|
||||
String hmac = Hmac.calculate(path, key);
|
||||
segmentPlaylistUri = segmentPlaylistUri + "?hmac=" + hmac;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
@ -13,14 +15,19 @@ import javax.servlet.http.HttpServletResponse;
|
|||
public class StaticFileServlet extends HttpServlet {
|
||||
|
||||
private String classPathRoot;
|
||||
private Map<String, String> mimetypes = new HashMap<>();
|
||||
|
||||
public StaticFileServlet(String classPathRoot) {
|
||||
this.classPathRoot = classPathRoot;
|
||||
mimetypes.put("css", "text/css");
|
||||
mimetypes.put("js", "application/javascript");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
String request = req.getRequestURI();
|
||||
String contextPath = getServletContext().getContextPath();
|
||||
request = request.substring(contextPath.length());
|
||||
serveFile(request, resp);
|
||||
}
|
||||
|
||||
|
@ -34,7 +41,12 @@ public class StaticFileServlet extends HttpServlet {
|
|||
if (resourceAsStream == null) {
|
||||
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);
|
||||
OutputStream out = resp.getOutputStream();
|
||||
int length = 0;
|
||||
|
@ -43,4 +55,12 @@ public class StaticFileServlet extends HttpServlet {
|
|||
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 hmac = null;
|
||||
String url = req.getRequestURI();
|
||||
url = url.substring(getServletContext().getContextPath().length());
|
||||
|
||||
if(reqParamHmac != null) {
|
||||
hmac = reqParamHmac;
|
||||
|
|
|
@ -32,7 +32,8 @@ public class HlsServlet extends AbstractCtbrecServlet {
|
|||
|
||||
@Override
|
||||
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 requestedFile = new File(recordingsDir, request);
|
||||
|
||||
|
|
|
@ -5,8 +5,10 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.BindException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
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.ServerConnector;
|
||||
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.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.util.security.Credential;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -68,7 +71,7 @@ public class HttpServer {
|
|||
logEnvironment();
|
||||
createSites();
|
||||
System.setProperty("ctbrec.server.mode", "1");
|
||||
if(System.getProperty("ctbrec.config") == null) {
|
||||
if (System.getProperty("ctbrec.config") == null) {
|
||||
System.setProperty("ctbrec.config", "server.json");
|
||||
}
|
||||
try {
|
||||
|
@ -83,12 +86,12 @@ public class HttpServer {
|
|||
registerAlertSystem();
|
||||
|
||||
config = Config.getInstance();
|
||||
if(config.getSettings().key != null) {
|
||||
if (config.getSettings().key != null) {
|
||||
LOG.info("HMAC authentication is enabled");
|
||||
}
|
||||
recorder = new NextGenLocalRecorder(config, sites);
|
||||
for (Site site : sites) {
|
||||
if(site.isEnabled()) {
|
||||
if (site.isEnabled()) {
|
||||
site.init();
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +108,7 @@ public class HttpServer {
|
|||
sites.add(new Fc2Live());
|
||||
sites.add(new Flirt4Free());
|
||||
sites.add(new LiveJasmin());
|
||||
//sites.add(new MyFreeCams());
|
||||
// sites.add(new MyFreeCams());
|
||||
sites.add(new Streamate());
|
||||
}
|
||||
|
||||
|
@ -114,10 +117,10 @@ public class HttpServer {
|
|||
@Override
|
||||
public void run() {
|
||||
LOG.info("Shutting down");
|
||||
if(onlineMonitor != null) {
|
||||
if (onlineMonitor != null) {
|
||||
onlineMonitor.shutdown();
|
||||
}
|
||||
if(recorder != null) {
|
||||
if (recorder != null) {
|
||||
recorder.shutdown();
|
||||
}
|
||||
try {
|
||||
|
@ -140,48 +143,62 @@ public class HttpServer {
|
|||
|
||||
HttpConfiguration config = new HttpConfiguration();
|
||||
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.setIdleTimeout(this.config.getSettings().httpTimeout);
|
||||
server.addConnector(http);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
context.setContextPath("/secured/*");
|
||||
server.setHandler(context);
|
||||
// connector for https (TLS)
|
||||
ServerConnector https = new ServerConnector(server, sslContextFactory, httpConnectionFactory);
|
||||
https.setPort(this.config.getSettings().httpSecurePort);
|
||||
https.setIdleTimeout(this.config.getSettings().httpTimeout);
|
||||
|
||||
ServletHandler handler = new ServletHandler();
|
||||
//server.setHandler(handler);
|
||||
HandlerList handlers = new HandlerList();
|
||||
handlers.setHandlers(new Handler[] { context, handler });
|
||||
server.setHandler(handlers);
|
||||
String contextPath = Config.getInstance().getContextPath();
|
||||
ServletContextHandler basicAuthContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
basicAuthContext.setContextPath(contextPath + "/secured");
|
||||
|
||||
ServletContextHandler defaultContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||
defaultContext.setContextPath(contextPath);
|
||||
|
||||
RecorderServlet recorderServlet = new RecorderServlet(recorder, sites);
|
||||
ServletHolder holder = new ServletHolder(recorderServlet);
|
||||
handler.addServletWithMapping(holder, "/rec");
|
||||
defaultContext.addServlet(holder, "/rec");
|
||||
|
||||
HlsServlet hlsServlet = new HlsServlet(this.config);
|
||||
holder = new ServletHolder(hlsServlet);
|
||||
handler.addServletWithMapping(holder, "/hls/*");
|
||||
|
||||
defaultContext.addServlet(holder, "/hls/*");
|
||||
|
||||
if (this.config.getSettings().webinterface) {
|
||||
LOG.info("Register static file servlet under {}", context.getContextPath());
|
||||
StaticFileServlet staticFileServlet = new StaticFileServlet("/html");
|
||||
holder = new ServletHolder(staticFileServlet);
|
||||
handler.addServletWithMapping(holder, "/static/*");
|
||||
//context.addServlet(holder, "/");
|
||||
String staticFileContext = "/static/*";
|
||||
defaultContext.addServlet(holder, staticFileContext);
|
||||
LOG.info("Register static file servlet under {}", defaultContext.getContextPath()+staticFileContext);
|
||||
|
||||
// servlet to retrieve the HMAC secured by basic auth
|
||||
String username = this.config.getSettings().webinterfaceUsername;
|
||||
String password = this.config.getSettings().webinterfacePassword;
|
||||
context.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
|
||||
context.addServlet(new ServletHolder(new HttpServlet() {
|
||||
basicAuthContext.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
|
||||
basicAuthContext.addServlet(new ServletHolder(new HttpServlet() {
|
||||
@Override
|
||||
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.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();
|
||||
response.put("hmac", new String(hmac, "utf-8"));
|
||||
resp.getOutputStream().println(response.toString());
|
||||
|
@ -190,12 +207,25 @@ public class HttpServer {
|
|||
}), "/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 {
|
||||
server.start();
|
||||
server.join();
|
||||
} catch (BindException e) {
|
||||
LOG.error("Port {} is already in use", http.getPort(), e);
|
||||
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"));
|
||||
try {
|
||||
LOG.debug("ctbrec server {}", getVersion().toString());
|
||||
} catch (IOException e) {}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
private Version getVersion() throws IOException {
|
||||
|
|
|
@ -11,27 +11,27 @@
|
|||
<title>CTB Recorder</title>
|
||||
|
||||
<!-- 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 -->
|
||||
<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=Lato:400,700,400italic,700italic" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Plugin CSS -->
|
||||
<link href="/static/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/magnific-popup/magnific-popup.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 -->
|
||||
<link href="/static/freelancer.css" rel="stylesheet">
|
||||
<link href="freelancer.css" rel="stylesheet">
|
||||
|
||||
<!-- Flowplayer -->
|
||||
<link rel="stylesheet" href="/static/vendor/flowplayer/skin/skin.css">
|
||||
<link rel="stylesheet" href="vendor/flowplayer/skin/skin.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>
|
||||
.ui-front {
|
||||
|
@ -45,7 +45,7 @@
|
|||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg bg-secondary fixed-top text-uppercase" id="mainNav" style="padding-pottom: 3rem">
|
||||
<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"
|
||||
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
|
||||
Menu <i class="fa fa-bars"></i>
|
||||
|
@ -183,22 +183,22 @@
|
|||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript -->
|
||||
<script src="/static/vendor/jquery/jquery.min.js"></script>
|
||||
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="vendor/jquery/jquery.min.js"></script>
|
||||
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Plugin JavaScript -->
|
||||
<script src="/static/vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
||||
<script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
<script src="/static/vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
||||
<script src="/static/vendor/notify.js/notify.min.js"></script>
|
||||
<script src="vendor/jquery-ui/jquery-ui-1.12.1.js"></script>
|
||||
<script src="vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
<script src="vendor/magnific-popup/jquery.magnific-popup.min.js"></script>
|
||||
<script src="vendor/notify.js/notify.min.js"></script>
|
||||
|
||||
<!-- knockout -->
|
||||
<script src="/static/vendor/knockout/knockout-3.5.0.js"></script>
|
||||
<script src="/static/vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
||||
<script src="vendor/knockout/knockout-3.5.0.js"></script>
|
||||
<script src="vendor/knockout-orderable/knockout.bindings.orderable.js"></script>
|
||||
|
||||
|
||||
<!-- 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 class="modal-content">
|
||||
|
@ -209,10 +209,10 @@
|
|||
</div>
|
||||
|
||||
<!-- HLS MediaSource support -->
|
||||
<script src="/static/vendor/hls.js/hls.js"></script>
|
||||
<script src="vendor/hls.js/hls.js"></script>
|
||||
|
||||
<!-- CryptoJS for HMAc authentication -->
|
||||
<script src="/static/vendor/CryptoJS/hmac-sha256.js"></script>
|
||||
<script src="vendor/CryptoJS/hmac-sha256.js"></script>
|
||||
|
||||
<script>
|
||||
let onlineModels = [];
|
||||
|
@ -248,7 +248,7 @@
|
|||
let action = '{"action": "startByUrl", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -277,7 +277,7 @@
|
|||
let action = '{"action": "resume", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -305,7 +305,7 @@
|
|||
let action = '{"action": "suspend", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -333,7 +333,7 @@
|
|||
let action = '{"action": "stop", "model": ' + JSON.stringify(model) + '}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -363,7 +363,7 @@
|
|||
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -395,9 +395,12 @@
|
|||
hls.attachMedia(video);
|
||||
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
||||
let src = recording.playlist;
|
||||
let hmacOfPath = CryptoJS.HmacSHA256(src, hmac);
|
||||
src = '..' + src;
|
||||
if (hmac.length > 0) {
|
||||
src += "?hmac=" + CryptoJS.HmacSHA256(src, hmac);
|
||||
src += "?hmac=" + hmacOfPath;
|
||||
}
|
||||
console.log(src);
|
||||
hls.loadSource(src);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
||||
console.log("manifest loaded, found " + data.levels.length + " quality level");
|
||||
|
@ -432,19 +435,18 @@
|
|||
|
||||
$(document).ready(function() {
|
||||
if (localStorage !== undefined && localStorage.hmac !== undefined) {
|
||||
console.log('using hmac from cookie');
|
||||
console.log('using hmac from local storage');
|
||||
hmac = localStorage.hmac;
|
||||
} else {
|
||||
console.log('hmac not found in local storage. requesting hmac from server');
|
||||
$.ajax({
|
||||
type : 'GET',
|
||||
url : '/secured/hmac',
|
||||
url : '../secured/hmac',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000
|
||||
})
|
||||
.done(function(data) {
|
||||
console.log(data);
|
||||
hmac = data.hmac;
|
||||
if (localStorage !== undefined) {
|
||||
console.log('saving hmac to local storage');
|
||||
|
@ -479,7 +481,7 @@
|
|||
let action = '{"action": "listOnline"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -590,7 +592,7 @@
|
|||
let action = '{"action": "list"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -682,7 +684,7 @@
|
|||
let action = '{"action": "recordings"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -710,7 +712,7 @@
|
|||
let action = '{"action": "space"}';
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/rec',
|
||||
url : '../rec',
|
||||
dataType : 'json',
|
||||
async : true,
|
||||
timeout : 60000,
|
||||
|
@ -736,7 +738,7 @@
|
|||
updateRecordings();
|
||||
});
|
||||
</script>
|
||||
<script src="/static/modal.js"></script>
|
||||
<script src="modal.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue