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