From 90192d9b8f06c44255cdf7651f10e0044cd36aac Mon Sep 17 00:00:00 2001 From: 0xb00bface <0xboobface@gmail.com> Date: Sun, 13 Sep 2020 19:54:43 +0200 Subject: [PATCH] Fix server stuff for new recording path handling --- .../main/java/ctbrec/ui/JavaFxRecording.java | 10 +++ common/src/main/java/ctbrec/Recording.java | 4 - .../ctbrec/recorder/NextGenLocalRecorder.java | 1 + .../ctbrec/recorder/RecordingManager.java | 5 ++ .../ctbrec/recorder/server/HlsServlet.java | 75 +++++++++++++------ .../ctbrec/recorder/server/HttpServer.java | 2 +- .../main/resources/html/static/recordings.js | 14 ++-- 7 files changed, 75 insertions(+), 36 deletions(-) diff --git a/client/src/main/java/ctbrec/ui/JavaFxRecording.java b/client/src/main/java/ctbrec/ui/JavaFxRecording.java index 5ba78e6b..e5ecf139 100644 --- a/client/src/main/java/ctbrec/ui/JavaFxRecording.java +++ b/client/src/main/java/ctbrec/ui/JavaFxRecording.java @@ -230,5 +230,15 @@ public class JavaFxRecording extends Recording { delegate.setAbsoluteFile(absoluteFile); } + @Override + public String getId() { + return delegate.getId(); + } + + @Override + public void setId(String id) { + delegate.setId(id); + } + } diff --git a/common/src/main/java/ctbrec/Recording.java b/common/src/main/java/ctbrec/Recording.java index 5ab665cd..12559935 100644 --- a/common/src/main/java/ctbrec/Recording.java +++ b/common/src/main/java/ctbrec/Recording.java @@ -19,7 +19,6 @@ import java.time.format.DateTimeFormatter; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; -import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,9 +71,6 @@ public class Recording implements Serializable { } public String getId() { - if (id == null) { - id = UUID.randomUUID().toString(); - } return id; } diff --git a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java index 7825781d..d410edee 100644 --- a/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java +++ b/common/src/main/java/ctbrec/recorder/NextGenLocalRecorder.java @@ -281,6 +281,7 @@ public class NextGenLocalRecorder implements Recorder { private Recording createRecording(Download download) throws IOException { Model model = download.getModel(); Recording rec = new Recording(); + rec.setId(UUID.randomUUID().toString()); rec.setDownload(download); String recordingFile = download.getPath(model).replaceAll("\\\\", "/"); File absoluteFile = new File(config.getSettings().recordingsDir, recordingFile); diff --git a/common/src/main/java/ctbrec/recorder/RecordingManager.java b/common/src/main/java/ctbrec/recorder/RecordingManager.java index 4678bb2d..90072924 100644 --- a/common/src/main/java/ctbrec/recorder/RecordingManager.java +++ b/common/src/main/java/ctbrec/recorder/RecordingManager.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.UUID; import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; @@ -82,6 +83,10 @@ public class RecordingManager { if (recording.getStatus() == RECORDING || recording.getStatus() == GENERATING_PLAYLIST || recording.getStatus() == POST_PROCESSING) { recording.setStatus(WAITING); } + if (recording.getId() == null) { + recording.setId(UUID.randomUUID().toString()); + saveRecording(recording); + } if (recordingExists(recording)) { recordings.add(recording); } else { diff --git a/server/src/main/java/ctbrec/recorder/server/HlsServlet.java b/server/src/main/java/ctbrec/recorder/server/HlsServlet.java index 7919cea6..9498aede 100644 --- a/server/src/main/java/ctbrec/recorder/server/HlsServlet.java +++ b/server/src/main/java/ctbrec/recorder/server/HlsServlet.java @@ -9,6 +9,8 @@ import java.nio.file.Paths; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Enumeration; +import java.util.Objects; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -20,6 +22,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ctbrec.Config; +import ctbrec.Recording; +import ctbrec.recorder.Recorder; public class HlsServlet extends AbstractCtbrecServlet { @@ -27,50 +31,69 @@ public class HlsServlet extends AbstractCtbrecServlet { private final Config config; - public HlsServlet(Config config) { + private Recorder recorder; + + public HlsServlet(Config config, Recorder recorder) { this.config = config; + this.recorder = recorder; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + + String contextPath = getServletContext().getContextPath(); String request = req.getRequestURI().substring(contextPath.length() + 5); Path recordingsDirPath = Paths.get(config.getSettings().recordingsDir).toAbsolutePath().normalize(); Path requestedFilePath = recordingsDirPath.resolve(request).toAbsolutePath().normalize(); - boolean isValidRequestedPath = requestedFilePath.startsWith(recordingsDirPath); - if (isValidRequestedPath) { - File requestedFile = requestedFilePath.toFile(); + File requestedFile = requestedFilePath.toFile(); + try { if (requestedFile.getName().equals("playlist.m3u8")) { - try { - boolean isRequestAuthenticated = checkAuthentication(req, req.getRequestURI()); - if (!isRequestAuthenticated) { - writeResponse(resp, SC_UNAUTHORIZED, "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}"); - return; - } - } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { - writeResponse(resp, SC_UNAUTHORIZED, "{\"status\": \"error\", \"msg\": \"Authentication failed\"}"); + boolean isRequestAuthenticated = checkAuthentication(req, req.getRequestURI()); + if (!isRequestAuthenticated) { + writeResponse(resp, SC_UNAUTHORIZED, "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}"); return; } - servePlaylist(req, resp, requestedFile); - } else { - if (requestedFile.exists()) { - Enumeration headerNames = req.getHeaderNames(); - while(headerNames.hasMoreElements()) { - String header = headerNames.nextElement(); - LOG.trace("{}: {}", header, req.getHeader(header)); - } - serveSegment(req, resp, requestedFile); + String id = request.substring(0, request.indexOf('/')); + Optional rec = getRecordingById(id); + if (rec.isPresent()) { + servePlaylist(req, resp, rec.get().getAbsoluteFile()); } else { error404(req, resp); + return; + } + } else { + String id = request.split("/")[0]; + Optional rec = getRecordingById(id); + if (rec.isPresent()) { + File file = rec.get().getAbsoluteFile(); + if (LOG.isTraceEnabled()) { + Enumeration headerNames = req.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + LOG.trace("{}: {}", header, req.getHeader(header)); + } + } + serveSegment(req, resp, file); + } else { + error404(req, resp); + return; } } - } else { - writeResponse(resp, SC_FORBIDDEN, "Stop it!"); + } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { + writeResponse(resp, SC_UNAUTHORIZED, "{\"status\": \"error\", \"msg\": \"Authentication failed\"}"); + return; } } + private Optional getRecordingById(String id) throws InvalidKeyException, NoSuchAlgorithmException, IOException { + return recorder.getRecordings().stream() + .filter(r -> Objects.equals(id, r.getId())) + .findFirst(); + } + private void writeResponse(HttpServletResponse resp, int code, String body) { try { resp.setStatus(code); @@ -81,6 +104,7 @@ public class HlsServlet extends AbstractCtbrecServlet { } private void error404(HttpServletRequest req, HttpServletResponse resp) { + writeResponse(resp, SC_NOT_FOUND, "{\"status\": \"error\", \"msg\": \"Recording not found\"}"); resp.setStatus(HttpServletResponse.SC_NOT_FOUND); } @@ -90,7 +114,9 @@ public class HlsServlet extends AbstractCtbrecServlet { } private void servePlaylist(HttpServletRequest req, HttpServletResponse resp, File requestedFile) throws IOException { - serveFile(req, resp, requestedFile, "application/x-mpegURL"); + LOG.debug("Serving playlist {}", requestedFile); + File playlist = new File(requestedFile, "playlist.m3u8"); + serveFile(req, resp, playlist, "application/x-mpegURL"); } private void serveFile(HttpServletRequest req, HttpServletResponse resp, File file, String contentType) throws IOException { @@ -103,6 +129,7 @@ public class HlsServlet extends AbstractCtbrecServlet { byte[] buffer = new byte[1024 * 100]; long bytesLeft = range.to - range.from; resp.setContentLengthLong(bytesLeft); + resp.addHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\""); if (range.set) { resp.setHeader("Content-Range", "bytes " + range.from + '-' + range.to + '/' + file.length()); } diff --git a/server/src/main/java/ctbrec/recorder/server/HttpServer.java b/server/src/main/java/ctbrec/recorder/server/HttpServer.java index 56126c9b..fc354de5 100644 --- a/server/src/main/java/ctbrec/recorder/server/HttpServer.java +++ b/server/src/main/java/ctbrec/recorder/server/HttpServer.java @@ -213,7 +213,7 @@ public class HttpServer { holder = new ServletHolder(configServlet); defaultContext.addServlet(holder, "/config"); - HlsServlet hlsServlet = new HlsServlet(this.config); + HlsServlet hlsServlet = new HlsServlet(this.config, recorder); holder = new ServletHolder(hlsServlet); defaultContext.addServlet(holder, "/hls/*"); diff --git a/server/src/main/resources/html/static/recordings.js b/server/src/main/resources/html/static/recordings.js index 6c446355..f41a28e7 100644 --- a/server/src/main/resources/html/static/recordings.js +++ b/server/src/main/resources/html/static/recordings.js @@ -1,5 +1,5 @@ function play(recording) { - let src = recording.singleFile ? '/hls' + recording.path : recording.playlist; + let src = recording.singleFile ? '/hls/' + recording.id : recording.playlist; let hmacOfPath = CryptoJS.HmacSHA256(src, hmac); src = '..' + src; if(console) console.log("Path", src, "HMAC", hmacOfPath); @@ -48,7 +48,7 @@ function play(recording) { } function download(recording) { - let src = recording.singleFile ? '/hls' + recording.path : recording.playlist; + let src = recording.singleFile ? '/hls/' + recording.id : recording.playlist; let hmacOfPath = CryptoJS.HmacSHA256(src, hmac); src = '..' + src; if(console) console.log("Path", src, "HMAC", hmacOfPath); @@ -77,7 +77,7 @@ function calculateSize(sizeInByte) { function isRecordingInArray(array, recording) { for ( let idx in array) { let r = array[idx]; - if (r.path === recording.path) { + if (r.id === recording.id) { return true; } } @@ -115,10 +115,10 @@ function syncRecordings(recordings) { recording.ko_progressString = ko.observable(recording.progress === -1 ? '' : recording.progress); recording.ko_size = ko.observable(calculateSize(recording.sizeInByte)); recording.ko_status = ko.observable(recording.status); - if (recording.path.endsWith('.mp4')) { - recording.playlist = '/hls' + recording.path; + if (recording.singleFile) { + recording.playlist = '/hls/' + recording.id; } else { - recording.playlist = '/hls' + recording.path + '/playlist.m3u8'; + recording.playlist = '/hls/' + recording.id + '/playlist.m3u8'; } observableRecordingsArray.push(recording); } @@ -129,7 +129,7 @@ function syncRecordings(recordings) { let recording = recordings[i]; for ( let j in observableRecordingsArray()) { let r = observableRecordingsArray()[j]; - if (recording.path === r.path) { + if (recording.id === r.id) { r.progress = recording.progress; r.sizeInByte = recording.sizeInByte; r.status = recording.status;