From f876ce01c2d238e8bcb128d24d5dbb479b8a3169 Mon Sep 17 00:00:00 2001 From: Richard Alam <ritzalam@gmail.com> Date: Fri, 1 May 2020 14:16:42 -0700 Subject: [PATCH] Rework presentation download and upload - verify presentation and meeting id formats - construct presentation file path making sure that they are valid - add "downloadable" flag to check if presentation can be downloaded or not - collect presentation upload errors so we can send to the client in the future --- .../bigbluebutton/api/RecordingService.java | 69 ++++----- .../main/java/org/bigbluebutton/api/Util.java | 135 +++++++++++++---- .../DocumentConversionServiceImp.java | 8 + .../PresentationUrlDownloadService.java | 58 ++++---- .../presentation/UploadedPresentation.java | 18 ++- .../imp/PresentationFileProcessor.java | 15 ++ .../web/controllers/ApiController.groovy | 138 ++++++++++++------ .../controllers/PresentationController.groovy | 120 ++++++++------- 8 files changed, 369 insertions(+), 192 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java index 450938ce4a..d1f2eb9d56 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/RecordingService.java @@ -32,7 +32,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; @@ -70,54 +69,42 @@ public class RecordingService { } public void processMakePresentationDownloadableMsg(MakePresentationDownloadableMsg msg) { - File presDir = Util.getPresentationDir(presentationBaseDir, msg.meetingId, msg.presId); - File downloadableFile = new File(presDir.getAbsolutePath() + File.separatorChar + msg.presFilename); - - if (presDir != null) { - if (msg.downloadable) { - String fileExt = FilenameUtils.getExtension(msg.presFilename); - File presFile = new File(presDir.getAbsolutePath() + File.separatorChar + msg.presId + "." + fileExt); - log.info("Make file downloadable. {}", downloadableFile.getAbsolutePath()); - copyPresentationFile(presFile, downloadableFile); - } else { - if (downloadableFile.exists()) { - if(downloadableFile.delete()) { - log.info("File deleted. {}", downloadableFile.getAbsolutePath()); - } else { - log.warn("Failed to delete. {}", downloadableFile.getAbsolutePath()); - } - } - } + try { + Util.makePresentationDownloadable(presentationBaseDir, msg.meetingId, msg.presId, msg.downloadable); + } catch (IOException e) { + log.error("Failed to make presentation downloadable: {}", e); } + } public File getDownloadablePresentationFile(String meetingId, String presId, String presFilename) { - log.info("Find downloadable presentation for meetingId={} presId={} filename={}", meetingId, presId, - presFilename); - Matcher metaMatcher = PRESENTATION_ID_PATTERN.matcher(presFilename); - if (metaMatcher.matches()) { - File presDir = Util.getPresentationDir(presentationBaseDir, meetingId, presId); - // Build file to presFilename - // Get canonicalPath and make sure it starts with - // /var/bigbluebutton/<meetingid-pattern> - // If so return file, if not return null - try { - File presFile = new File(presDir.getAbsolutePath() + File.separatorChar + presFilename); - String presFileCanonical = presFile.getCanonicalPath(); - log.debug("Requested presentation name file full path {}",presFileCanonical); - if (presFileCanonical.startsWith(presentationBaseDir)) { - return presFile; - } - } catch (IOException e) { - log.error("Exception getting canonical path for {}.\n{}", presFilename, e); - return null; + log.info("Find downloadable presentation for meetingId={} presId={} filename={}", meetingId, presId, + presFilename); + + if (! Util.isPresFileIdValidFormat(presFilename)) { + log.error("Invalid presentation filename for meetingId={} presId={} filename={}", meetingId, presId, + presFilename); + return null; + } + + String presFilenameExt = FilenameUtils.getExtension(presFilename); + File presDir = Util.getPresentationDir(presentationBaseDir, meetingId, presId); + File downloadMarker = Util.getPresFileDownloadMarker(presentationBaseDir, meetingId, presId); + if (presDir != null && downloadMarker != null && downloadMarker.exists()) { + String safePresFilename = presId.concat(".").concat(presFilenameExt); + File presFile = new File(presDir.getAbsolutePath() + File.separatorChar + safePresFilename); + if (presFile.exists()) { + return presFile; } + + log.error("Presentation file missing for meetingId={} presId={} filename={}", meetingId, presId, + presFilename); + return null; } - - log.error("Cannot find file for {}.", presFilename); + log.error("Invalid presentation directory for meetingId={} presId={} filename={}", meetingId, presId, + presFilename); return null; - } public void kickOffRecordingChapterBreak(String meetingId, Long timestamp) { diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/Util.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/Util.java index ac186d4a03..9b8d3e5c70 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/Util.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/Util.java @@ -1,49 +1,132 @@ package org.bigbluebutton.api; import java.io.File; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; public final class Util { - - private Util() { - throw new IllegalStateException("Utility class"); - } - - public static String generatePresentationId(String name) { - long timestamp = System.currentTimeMillis(); - return DigestUtils.sha1Hex(name) + "-" + timestamp; + + private static final Pattern MEETING_ID_PATTERN = Pattern.compile("^[a-z0-9-]+$"); + private static final Pattern PRES_ID_PATTERN = Pattern.compile("^[a-z0-9-]+$"); + private static final Pattern PRES_FILE_ID_PATTERN = Pattern.compile("^[a-z0-9-]+.[a-zA-Z]{3,4}$"); + + private Util() { + throw new IllegalStateException("Utility class"); + } + + public static boolean isMeetingIdValidFormat(String id) { + Matcher matcher = MEETING_ID_PATTERN.matcher(id); + if (matcher.matches()) { + return true; + } + return false; + } + + public static boolean isPresIdValidFormat(String id) { + Matcher matcher = PRES_ID_PATTERN.matcher(id); + if (matcher.matches()) { + return true; + } + return false; + } + + public static boolean isPresFileIdValidFormat(String id) { + Matcher matcher = PRES_FILE_ID_PATTERN.matcher(id); + if (matcher.matches()) { + return true; + } + return false; + } + + public static String generatePresentationId(String presFilename) { + long timestamp = System.currentTimeMillis(); + return DigestUtils.sha1Hex(presFilename) + "-" + timestamp; + } + + public static String createNewFilename(String presId, String fileExt) { + return presId + "." + fileExt; } - - public static String createNewFilename(String presId, String fileExt) { - return presId + "." + fileExt; - } public static File createPresentationDir(String meetingId, String presentationDir, String presentationId) { - String meetingPath = presentationDir + File.separatorChar + meetingId + File.separatorChar + meetingId; - String presPath = meetingPath + File.separatorChar + presentationId; - File dir = new File(presPath); - if (dir.mkdirs()) { - return dir; + if (Util.isMeetingIdValidFormat(meetingId) && Util.isPresIdValidFormat(presentationId)) { + String meetingPath = presentationDir + File.separatorChar + meetingId + File.separatorChar + meetingId; + String presPath = meetingPath + File.separatorChar + presentationId; + File dir = new File(presPath); + if (dir.mkdirs()) { + return dir; + } + } + + return null; + } + + public static File getMeetingDirPath(String presentationBaseDir, String meetingId) { + if (Util.isMeetingIdValidFormat(meetingId)) { + String meetingPath = presentationBaseDir + File.separatorChar + meetingId + File.separatorChar + meetingId; + File dir = new File(meetingPath); + if (dir.isDirectory() && dir.exists()) { + return dir; + } } + return null; } public static File getPresentationDir(String presentationBaseDir, String meetingId, String presentationId) { - String meetingPath = presentationBaseDir + File.separatorChar + meetingId + File.separatorChar + meetingId; - String presPath = meetingPath + File.separatorChar + presentationId; - File dir = new File(presPath); - if (dir.isDirectory() && dir.exists()) { - return dir; + if (Util.isMeetingIdValidFormat(meetingId) && Util.isPresIdValidFormat(presentationId)) { + String meetingPath = presentationBaseDir + File.separatorChar + meetingId + File.separatorChar + meetingId; + String presPath = meetingPath + File.separatorChar + presentationId; + File dir = new File(presPath); + if (dir.isDirectory() && dir.exists()) { + return dir; + } } + return null; } - public static File downloadPresentationDirectory(String uploadDirectory) { - File dir = new File(uploadDirectory + File.separatorChar + "download"); - if (dir.mkdirs()) { - return dir; + public String stripPresBaseDirFromPath(String presentationBaseDir, String path) { + if (path.startsWith(presentationBaseDir)) { + String presBaseDir = presentationBaseDir; + if (! presBaseDir.endsWith("/")) { + presBaseDir = presBaseDir.concat("/"); + return StringUtils.removeStart(path, presBaseDir); + } else { + return StringUtils.removeStart(path, presBaseDir); + } + } + return path; + } + + public static File getPresFileDownloadMarker(String presBaseDir, String meetingId, String presId) { + File presDir = Util.getPresentationDir(presBaseDir, meetingId, presId); + + if (presDir != null) { + String downloadMarker = presId.concat(".downloadable"); + return new File(presDir.getAbsolutePath() + File.separatorChar + downloadMarker); } return null; } + + public static void makePresentationDownloadable( + String presBaseDir, + String meetingId, + String presId, + boolean downloadable + ) throws IOException { + File downloadMarker = Util.getPresFileDownloadMarker(presBaseDir, meetingId, presId); + if (downloadable) { + if (downloadMarker != null && ! downloadMarker.exists()) { + downloadMarker.createNewFile(); + } + } else { + if (downloadMarker != null && downloadMarker.exists()) { + downloadMarker.delete(); + } + } + } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java index 3a1c4f70fb..24e0c5f6c7 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/DocumentConversionServiceImp.java @@ -44,6 +44,14 @@ public class DocumentConversionServiceImp implements DocumentConversionService { public void processDocument(UploadedPresentation pres) { + if (pres.isUploadFailed()) { + // We should send a message to the client in the future. + // ralam may 1, 2020 + log.error("Presentation upload failed for meetingId={} presId={}", pres.getMeetingId(), pres.getId()); + log.error("Presentation upload fail reasons {}", pres.getUploadFailReason()); + return; + } + SupportedDocumentFilter sdf = new SupportedDocumentFilter(gw); sendDocConversionRequestReceived(pres); diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java index a4b217756f..f6b5acf581 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/PresentationUrlDownloadService.java @@ -6,12 +6,14 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import com.sun.org.apache.xpath.internal.operations.Bool; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; @@ -59,10 +61,19 @@ public class PresentationUrlDownloadService { } public void processUploadedFile(String podId, String meetingId, String presId, - String filename, File presFile, Boolean current, String authzToken) { + String filename, File presFile, Boolean current, String authzToken, + Boolean uploadFailed, ArrayList<String> uploadFailReasons) { // TODO add podId - UploadedPresentation uploadedPres = new UploadedPresentation(podId, meetingId, - presId, filename, presentationBaseURL, current, authzToken); + UploadedPresentation uploadedPres = new UploadedPresentation( + podId, + meetingId, + presId, + filename, + presentationBaseURL, + current, + authzToken, + uploadFailed, + uploadFailReasons); uploadedPres.setUploadedFile(presFile); processUploadedPresentation(uploadedPres); } @@ -84,6 +95,9 @@ public class PresentationUrlDownloadService { private void extractPage(final String sourceMeetingId, final String presentationId, final Integer presentationSlide, final String destinationMeetingId) { + Boolean uploadFailed = false; + ArrayList<String> uploadFailedReasons = new ArrayList<String>(); + // Build the source meeting path File sourceMeetingPath = new File(presentationDir + File.separatorChar + sourceMeetingId + File.separatorChar + sourceMeetingId @@ -118,13 +132,13 @@ public class PresentationUrlDownloadService { } else { sourcePresentationFile = matches[0]; } + // Build the target meeting path - String filenameExt = FilenameUtils.getExtension(sourcePresentationFile - .getName()); - String presId = generatePresentationId(presentationId); + String filenameExt = FilenameUtils.getExtension(sourcePresentationFile.getName()); + String presId = Util.generatePresentationId(presentationId); String newFilename = Util.createNewFilename(presId, filenameExt); - File uploadDir = createPresentationDirectory(destinationMeetingId, + File uploadDir = Util.createPresentationDir(destinationMeetingId, presentationDir, presId); String newFilePath = uploadDir.getAbsolutePath() + File.separatorChar + newFilename; @@ -143,27 +157,15 @@ public class PresentationUrlDownloadService { } // Hardcode pre-uploaded presentation for breakout room to the default presentation window - processUploadedFile("DEFAULT_PRESENTATION_POD", destinationMeetingId, presId, "default-" - + presentationSlide.toString() + "." + filenameExt, - newPresentation, true, "breakout-authz-token"); - } - - public String generatePresentationId(String name) { - long timestamp = System.currentTimeMillis(); - return DigestUtils.sha1Hex(name) + "-" + timestamp; - } - - public File createPresentationDirectory(String meetingId, - String presentationDir, String presentationId) { - String meetingPath = presentationDir + File.separatorChar + meetingId - + File.separatorChar + meetingId; - String presPath = meetingPath + File.separatorChar + presentationId; - File dir = new File(presPath); - log.debug("Creating dir [{}]", presPath); - if (dir.mkdirs()) { - return dir; - } - return null; + processUploadedFile("DEFAULT_PRESENTATION_POD", + destinationMeetingId, + presId, + "default-" + presentationSlide.toString() + "." + filenameExt, + newPresentation, + true, + "breakout-authz-token", + uploadFailed, + uploadFailedReasons); } private String followRedirect(String meetingId, String redirectUrl, diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java index 0bbb49eec2..d017dc3f62 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/UploadedPresentation.java @@ -20,12 +20,15 @@ package org.bigbluebutton.presentation; import java.io.File; +import java.util.ArrayList; public final class UploadedPresentation { private final String podId; private final String meetingId; private final String id; private final String name; + private final boolean uploadFailed; + private final ArrayList<String> uploadFailReason; private File uploadedFile; private String fileType = "unknown"; private int numberOfPages = 0; @@ -36,13 +39,16 @@ public final class UploadedPresentation { private String authzToken; private boolean conversionStarted = false; + public UploadedPresentation(String podId, String meetingId, String id, String name, String baseUrl, Boolean current, - String authzToken) { + String authzToken, + Boolean uploadFailed, + ArrayList<String> uploadFailReason) { this.podId = podId; this.meetingId = meetingId; this.id = id; @@ -51,6 +57,8 @@ public final class UploadedPresentation { this.isDownloadable = false; this.current = current; this.authzToken = authzToken; + this.uploadFailed = uploadFailed; + this.uploadFailReason = uploadFailReason; } public File getUploadedFile() { @@ -132,4 +140,12 @@ public final class UploadedPresentation { public boolean isConversionStarted() { return conversionStarted; } + + public boolean isUploadFailed() { + return uploadFailed; + } + + public ArrayList<String> getUploadFailReason() { + return uploadFailReason; + } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationFileProcessor.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationFileProcessor.java index 6a509c2bb5..b36288b551 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationFileProcessor.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/PresentationFileProcessor.java @@ -1,6 +1,7 @@ package org.bigbluebutton.presentation.imp; import com.google.gson.Gson; +import org.bigbluebutton.api.Util; import org.bigbluebutton.presentation.*; import org.bigbluebutton.presentation.messages.DocPageConversionStarted; import org.bigbluebutton.presentation.messages.DocPageCountExceeded; @@ -10,6 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; @@ -53,6 +55,19 @@ public class PresentationFileProcessor { } public synchronized void process(UploadedPresentation pres) { + if (pres.isDownloadable()) { + try { + Util.makePresentationDownloadable( + pres.getUploadedFile().getParent(), + pres.getMeetingId(), + pres.getId(), + pres.isDownloadable() + ); + } catch (IOException e) { + log.error("Failed to make presentation downloadable: {}", e); + } + } + Runnable messageProcessor = new Runnable() { public void run() { processUploadedPresentation(pres); diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index 2566ab051b..f2eef90364 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -2017,68 +2017,110 @@ class ApiController { } } - def processDocumentFromRawBytes(bytes, presFilename, meetingId, current) { - def filenameExt = FilenameUtils.getExtension(presFilename); - String presentationDir = presentationService.getPresentationDir() - def presId = Util.generatePresentationId(presFilename) - File uploadDir = presDownloadService.createPresentationDirectory(meetingId, presentationDir, presId) - if (uploadDir != null) { - def newFilename = Util.createNewFilename(presId, filenameExt) - def pres = new File(uploadDir.absolutePath + File.separatorChar + newFilename); - - FileOutputStream fos = new java.io.FileOutputStream(pres) - fos.write(bytes) - fos.flush() - fos.close() - - // Hardcode pre-uploaded presentation to the default presentation window - processUploadedFile("DEFAULT_PRESENTATION_POD", + def processDocumentFromRawBytes(bytes, presOrigFilename, meetingId, current) { + def uploadFailed = false + def uploadFailReasons = new ArrayList<String>() + + // Gets the name minus the path from a full fileName. + // a/b/c.txt --> c.txt + def presFilename = FilenameUtils.getName(presOrigFilename) + def filenameExt = FilenameUtils.getExtension(presOrigFilename) + def pres = null + def presId = null + + if (presFilename == "" || filenameExt == "") { + log.debug("Upload failed. Invalid filename " + presOrigFilename) + uploadFailReasons.add("invalid_filename") + uploadFailed = true + } else { + String presentationDir = presentationService.getPresentationDir() + presId = Util.generatePresentationId(presFilename) + + File uploadDir = Util.createPresentationDir(meetingId, presentationDir, presId) + if (uploadDir != null) { + def newFilename = Util.createNewFilename(presId, filenameExt) + pres = new File(uploadDir.absolutePath + File.separatorChar + newFilename); + + FileOutputStream fos = new java.io.FileOutputStream(pres) + fos.write(bytes) + fos.flush() + fos.close() + } else { + log.warn "Upload failed. File Empty." + uploadFailReasons.add("failed_to_download_file") + uploadFailed = true + } + } + + // Hardcode pre-uploaded presentation to the default presentation window + processUploadedFile("DEFAULT_PRESENTATION_POD", meetingId, presId, presFilename, pres, current, - "preupload-raw-authz-token"); - } - + "preupload-raw-authz-token", + uploadFailed, + uploadFailReasons) } def downloadAndProcessDocument(address, meetingId, current, fileName) { log.debug("ApiController#downloadAndProcessDocument(${address}, ${meetingId}, ${fileName})"); - String presFilename; + String presOrigFilename; if (StringUtils.isEmpty(fileName)) { - presFilename = address.tokenize("/")[-1]; + presOrigFilename = address.tokenize("/")[-1]; } else { - presFilename = fileName; - } - - def filenameExt = FilenameUtils.getExtension(presFilename); - String presentationDir = presentationService.getPresentationDir() - - def presId = presDownloadService.generatePresentationId(presFilename) - File uploadDir = presDownloadService.createPresentationDirectory(meetingId, presentationDir, presId) - if (uploadDir != null) { - def newFilename = Util.createNewFilename(presId, filenameExt) - def newFilePath = uploadDir.absolutePath + File.separatorChar + newFilename - - if (presDownloadService.savePresentation(meetingId, newFilePath, address)) { - def pres = new File(newFilePath) - // Hardcode pre-uploaded presentation to the default presentation window - processUploadedFile("DEFAULT_PRESENTATION_POD", - meetingId, - presId, - presFilename, - pres, - current, - "preupload-download-authz-token"); - } else { - log.error("Failed to download presentation=[${address}], meeting=[${meetingId}], fileName=[${fileName}]") + presOrigFilename = fileName; + } + + def uploadFailed = false + def uploadFailReasons = new ArrayList<String>() + + // Gets the name minus the path from a full fileName. + // a/b/c.txt --> c.txt + def presFilename = FilenameUtils.getName(presOrigFilename) + def filenameExt = FilenameUtils.getExtension(presOrigFilename) + def pres = null + def presId + + if (presFilename == "" || filenameExt == "") { + log.debug("Upload failed. Invalid filename " + presOrigFilename) + uploadFailReasons.add("invalid_filename") + uploadFailed = true + } else { + String presentationDir = presentationService.getPresentationDir() + presId = Util.generatePresentationId(presFilename) + File uploadDir = Util.createPresentationDir(meetingId, presentationDir, presId) + if (uploadDir != null) { + def newFilename = Util.createNewFilename(presId, filenameExt) + def newFilePath = uploadDir.absolutePath + File.separatorChar + newFilename + + if (presDownloadService.savePresentation(meetingId, newFilePath, address)) { + pres = new File(newFilePath) + } else { + log.error("Failed to download presentation=[${address}], meeting=[${meetingId}], fileName=[${fileName}]") + uploadFailReasons.add("failed_to_download_file") + uploadFailed = true + } } } + + // Hardcode pre-uploaded presentation to the default presentation window + processUploadedFile( + "DEFAULT_PRESENTATION_POD", + meetingId, + presId, + presFilename, + pres, + current, + "preupload-download-authz-token", + uploadFailed, + uploadFailReasons + ) } - def processUploadedFile(podId, meetingId, presId, filename, presFile, current, authzToken) { + def processUploadedFile(podId, meetingId, presId, filename, presFile, current, authzToken, uploadFailed, uploadFailReasons ) { def presentationBaseUrl = presentationService.presentationBaseUrl // TODO add podId UploadedPresentation uploadedPres = new UploadedPresentation(podId, @@ -2087,7 +2129,9 @@ class ApiController { filename, presentationBaseUrl, current, - authzToken); + authzToken, + uploadFailed, + uploadFailReasons) uploadedPres.setUploadedFile(presFile); presentationService.processUploadedPresentation(uploadedPres); } diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy index 31e7e800eb..a65a6b3ce2 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PresentationController.groovy @@ -76,73 +76,95 @@ class PresentationController { // check if the authorization token provided is valid if (null == params.authzToken || !meetingService.authzTokenIsValidAndExpired(params.authzToken)) { log.debug "WARNING! AuthzToken=" + params.authzToken + " was not valid in meetingId=" + params.conference + response.addHeader("Cache-Control", "no-cache") + response.contentType = 'plain/text' + response.outputStream << 'invalid auth token' return } def meetingId = params.conference - def meeting = meetingService.getNotEndedMeetingWithId(meetingId); - if (meeting == null) { - flash.message = 'meeting is not running' - log.debug("Upload failed. No meeting running " + meetingId) + if (Util.isMeetingIdValidFormat(meetingId)) { + def meeting = meetingService.getNotEndedMeetingWithId(meetingId) + if (meeting == null) { + log.debug("Upload failed. No meeting running " + meetingId) + response.addHeader("Cache-Control", "no-cache") + response.contentType = 'plain/text' + response.outputStream << 'no-meeting' + return + } + } else { + log.debug("Upload failed. Invalid meeting id format " + meetingId) response.addHeader("Cache-Control", "no-cache") response.contentType = 'plain/text' response.outputStream << 'no-meeting'; + return } + def isDownloadable = params.boolean('is_downloadable') //instead of params.is_downloadable + def podId = params.pod_id + log.debug "@Default presentation pod" + podId + + def uploadFailed = false + def uploadFailReasons = new ArrayList<String>() + def presOrigFilename = "" + def presFilename = "" + def filenameExt = "" + def presId = "" + def pres = null + def file = request.getFile('fileUpload') if (file && !file.empty) { - flash.message = 'Your file has been uploaded' - def presFilename = file.getOriginalFilename() - def filenameExt = FilenameUtils.getExtension(presFilename); + presOrigFilename = file.getOriginalFilename() + // Gets the name minus the path from a full fileName. + // a/b/c.txt --> c.txt + presFilename = FilenameUtils.getName(presOrigFilename) + filenameExt = FilenameUtils.getExtension(presFilename) + } else { + log.warn "Upload failed. File Empty." + uploadFailReasons.add("uploaded_file_empty") + uploadFailed = true + } + + if (presFilename == "" || filenameExt == "") { + log.debug("Upload failed. Invalid filename " + presOrigFilename) + uploadFailReasons.add("invalid_filename") + uploadFailed = true + } else { String presentationDir = presentationService.getPresentationDir() - def presId = Util.generatePresentationId(presFilename) + presId = Util.generatePresentationId(presFilename) File uploadDir = Util.createPresentationDir(meetingId, presentationDir, presId) - if (uploadDir != null) { def newFilename = Util.createNewFilename(presId, filenameExt) - def pres = new File(uploadDir.absolutePath + File.separatorChar + newFilename) + pres = new File(uploadDir.absolutePath + File.separatorChar + newFilename) file.transferTo(pres) - - def isDownloadable = params.boolean('is_downloadable') //instead of params.is_downloadable - def podId = params.pod_id - log.debug "@Default presentation pod" + podId - - if (isDownloadable) { - log.debug "@Creating download directory..." - File downloadDir = Util.downloadPresentationDirectory(uploadDir.absolutePath) - if (downloadDir != null) { - def notValidCharsRegExp = /[^0-9a-zA-Z_\.]/ - def downloadableFileName = presFilename.replaceAll(notValidCharsRegExp, '-') - def downloadableFile = new File(downloadDir.absolutePath + File.separatorChar + downloadableFileName) - downloadableFile << pres.newInputStream() - } - } - - log.debug("processing file upload " + presFilename) - def presentationBaseUrl = presentationService.presentationBaseUrl - UploadedPresentation uploadedPres = new UploadedPresentation(podId, meetingId, presId, - presFilename, presentationBaseUrl, false /* default presentation */, - params.authzToken); - - if (isDownloadable) { - log.debug "@Setting file to be downloadable..." - uploadedPres.setDownloadable(); - } - - uploadedPres.setUploadedFile(pres); - presentationService.processUploadedPresentation(uploadedPres) - log.debug("file upload success " + presFilename) - response.addHeader("Cache-Control", "no-cache") - response.contentType = 'plain/text' - response.outputStream << 'upload-success' } - } else { - log.warn "Upload failed. File Empty." - flash.message = 'file cannot be empty' - response.addHeader("Cache-Control", "no-cache") - response.contentType = 'plain/text' - response.outputStream << 'file-empty' } + + log.debug("processing file upload " + presFilename) + def presentationBaseUrl = presentationService.presentationBaseUrl + UploadedPresentation uploadedPres = new UploadedPresentation( + podId, + meetingId, + presId, + presFilename, + presentationBaseUrl, + false /* default presentation */, + params.authzToken, + uploadFailed, + uploadFailReasons + ) + + if (isDownloadable) { + log.debug "@Setting file to be downloadable..." + uploadedPres.setDownloadable(); + } + + uploadedPres.setUploadedFile(pres); + presentationService.processUploadedPresentation(uploadedPres) + log.debug("file upload success " + presFilename) + response.addHeader("Cache-Control", "no-cache") + response.contentType = 'plain/text' + response.outputStream << 'upload-success' } def testConversion = { -- GitLab