Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #15990 from GuiLeme/fix-check-mime-insert-document
  • Loading branch information
gustavotrott committed Nov 25, 2022
2 parents bdf8dad + 04d397a commit 520b31a
Show file tree
Hide file tree
Showing 25 changed files with 374 additions and 38 deletions.
@@ -0,0 +1,38 @@
package org.bigbluebutton.core.apps.presentationpod

import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting

trait PresentationHasInvalidMimeTypeErrorPubMsgHdlr {
this: PresentationPodHdlrs =>

def handle(
msg: PresentationHasInvalidMimeTypeErrorSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
): MeetingState2x = {

def broadcastEvent(msg: PresentationHasInvalidMimeTypeErrorSysPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val envelope = BbbCoreEnvelope(PresentationHasInvalidMimeTypeErrorEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(
PresentationHasInvalidMimeTypeErrorEvtMsg.NAME,
liveMeeting.props.meetingProp.intId, msg.header.userId
)

val body = PresentationHasInvalidMimeTypeErrorEvtMsgBody(msg.body.podId, msg.body.meetingId,
msg.body.presentationName, msg.body.temporaryPresentationId,
msg.body.presentationId, msg.body.messageKey, msg.body.fileMime, msg.body.fileExtension)
val event = PresentationHasInvalidMimeTypeErrorEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}

broadcastEvent(msg)
state
}
}
Expand Up @@ -26,7 +26,8 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
with PresentationPageConvertedSysMsgHdlr
with PresentationPageConversionStartedSysMsgHdlr
with PresentationConversionEndedSysMsgHdlr
with PresentationUploadedFileTimeoutErrorPubMsgHdlr {
with PresentationUploadedFileTimeoutErrorPubMsgHdlr
with PresentationHasInvalidMimeTypeErrorPubMsgHdlr {

val log = Logging(context.system, getClass)
}
Expand Up @@ -288,6 +288,8 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[PreuploadedPresentationsSysPubMsg](envelope, jsonNode)
case PresentationUploadedFileTooLargeErrorSysPubMsg.NAME =>
routeGenericMsg[PresentationUploadedFileTooLargeErrorSysPubMsg](envelope, jsonNode)
case PresentationHasInvalidMimeTypeErrorSysPubMsg.NAME =>
routeGenericMsg[PresentationHasInvalidMimeTypeErrorSysPubMsg](envelope, jsonNode)
case PresentationUploadedFileTimeoutErrorSysPubMsg.NAME =>
routeGenericMsg[PresentationUploadedFileTimeoutErrorSysPubMsg](envelope, jsonNode)
case PresentationConversionUpdateSysPubMsg.NAME =>
Expand Down
Expand Up @@ -523,6 +523,7 @@ class MeetingActor(
case m: SetPresentationDownloadablePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionUpdateSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadedFileTooLargeErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationHasInvalidMimeTypeErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadedFileTimeoutErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageGeneratedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageCountErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
Expand Down
Expand Up @@ -166,6 +166,23 @@ case class PresentationUploadedFileTooLargeErrorSysPubMsgBody(
maxFileSize: Int
)

object PresentationHasInvalidMimeTypeErrorSysPubMsg { val NAME = "PresentationHasInvalidMimeTypeErrorSysPubMsg" }
case class PresentationHasInvalidMimeTypeErrorSysPubMsg(
header: BbbClientMsgHeader,
body: PresentationHasInvalidMimeTypeErrorSysPubMsgBody
) extends StandardMsg
case class PresentationHasInvalidMimeTypeErrorSysPubMsgBody(
podId: String,
meetingId: String,
presentationName: String,
temporaryPresentationId: String,
presentationId: String,
messageKey: String,
fileMime: String,
fileExtension: String,
)


object PresentationUploadedFileTimeoutErrorSysPubMsg { val NAME = "PresentationUploadedFileTimeoutErrorSysPubMsg" }
case class PresentationUploadedFileTimeoutErrorSysPubMsg(
header: BbbClientMsgHeader,
Expand Down Expand Up @@ -237,6 +254,13 @@ object PresentationUploadedFileTooLargeErrorEvtMsg { val NAME = "PresentationUpl
case class PresentationUploadedFileTooLargeErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationUploadedFileTooLargeErrorEvtMsgBody) extends BbbCoreMsg
case class PresentationUploadedFileTooLargeErrorEvtMsgBody(podId: String, messageKey: String, code: String, presentationName: String, presentationToken: String, fileSize: Int, maxFileSize: Int)

object PresentationHasInvalidMimeTypeErrorEvtMsg { val NAME = "PresentationHasInvalidMimeTypeErrorEvtMsg" }
case class PresentationHasInvalidMimeTypeErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationHasInvalidMimeTypeErrorEvtMsgBody) extends BbbCoreMsg
case class PresentationHasInvalidMimeTypeErrorEvtMsgBody(podId: String, meetingId: String, presentationName: String,
temporaryPresentationId: String, presentationId: String,
messageKey: String, fileMime: String, fileExtension: String,
)

object PresentationUploadedFileTimeoutErrorEvtMsg { val NAME = "PresentationUploadedFileTimeoutErrorEvtMsg" }
case class PresentationUploadedFileTimeoutErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationUploadedFileTimeoutErrorEvtMsgBody) extends BbbCoreMsg
case class PresentationUploadedFileTimeoutErrorEvtMsgBody(podId: String, meetingId: String, presentationName: String,
Expand Down
Expand Up @@ -21,4 +21,5 @@

public interface DocumentConversionService {
void processDocument(UploadedPresentation pres);
void sendDocConversionFailedOnMimeType(UploadedPresentation pres, String fileMime, String fileExtension);
}
Expand Up @@ -19,15 +19,19 @@

package org.bigbluebutton.presentation;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.presentation.imp.*;
import org.bigbluebutton.presentation.messages.DocConversionRequestReceived;
import org.bigbluebutton.presentation.messages.DocInvalidMimeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;

import static org.bigbluebutton.presentation.Util.deleteDirectoryFromFileHandlingErrors;

public class DocumentConversionServiceImp implements DocumentConversionService {
private static Logger log = LoggerFactory.getLogger(DocumentConversionServiceImp.class);

Expand Down Expand Up @@ -93,6 +97,9 @@ public void processDocumentStart(UploadedPresentation pres) {
}

} else {
File presentationFile = pres.getUploadedFile();
deleteDirectoryFromFileHandlingErrors(presentationFile);

Map<String, Object> logData = new HashMap<String, Object>();
logData = new HashMap<String, Object>();
logData.put("podId", pres.getPodId());
Expand Down Expand Up @@ -124,6 +131,11 @@ public void processDocumentStart(UploadedPresentation pres) {
}
}

public void sendDocConversionFailedOnMimeType(UploadedPresentation pres, String fileMime,
String fileExtension) {
notifier.sendInvalidMimeTypeMessage(pres, fileMime, fileExtension);
}

private void sendDocConversionRequestReceived(UploadedPresentation pres) {
if (! pres.isConversionStarted()) {
Map<String, Object> logData = new HashMap<String, Object>();
Expand Down
Expand Up @@ -39,5 +39,6 @@ public final class FileTypeConstants {
public static final String JPG = "jpg";
public static final String JPEG = "jpeg";
public static final String PNG = "png";
public static final String SVG = "svg";
private FileTypeConstants() {} // Prevent instantiation
}
@@ -0,0 +1,67 @@
package org.bigbluebutton.presentation;

import java.util.*;

import static org.bigbluebutton.presentation.FileTypeConstants.*;

public class MimeTypeUtils {
private static final String XLS = "application/vnd.ms-excel";
private static final String XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
private static final String DOC = "application/msword";
private static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
private static final String PPT = "application/vnd.ms-powerpoint";
private static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
private static final String ODT = "application/vnd.oasis.opendocument.text";
private static final String RTF = "application/rtf";
private static final String TXT = "text/plain";
private static final String ODS = "application/vnd.oasis.opendocument.spreadsheet";
private static final String ODP = "application/vnd.oasis.opendocument.presentation";
private static final String PDF = "application/pdf";
private static final String JPEG = "image/jpeg";
private static final String PNG = "image/png";
private static final String SVG = "image/svg+xml";

private static final HashMap<String,String> EXTENSIONS_MIME = new HashMap<String,String>(16) {
{
// Add all the supported files
put(FileTypeConstants.XLS, XLS);
put(FileTypeConstants.XLSX, XLSX);
put(FileTypeConstants.DOC, DOC);
put(FileTypeConstants.DOCX, DOCX);
put(FileTypeConstants.PPT, PPT);
put(FileTypeConstants.PPTX, PPTX);
put(FileTypeConstants.ODT, ODT);
put(FileTypeConstants.RTF, RTF);
put(FileTypeConstants.TXT, TXT);
put(FileTypeConstants.ODS, ODS);
put(FileTypeConstants.ODP, ODP);
put(FileTypeConstants.PDF, PDF);
put(FileTypeConstants.JPG, JPEG);
put(FileTypeConstants.JPEG, JPEG);
put(FileTypeConstants.PNG, PNG);
put(FileTypeConstants.SVG, SVG);
}
};

public Boolean extensionMatchMimeType(String mimeType, String finalExtension) {
if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase()) &&
EXTENSIONS_MIME.get(finalExtension.toLowerCase()).equalsIgnoreCase(mimeType)) {
return true;
} else if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase() + 'x') &&
EXTENSIONS_MIME.get(finalExtension.toLowerCase() + 'x').equalsIgnoreCase(mimeType)) {
//Exception for MS Office files named with old extension but using internally the new mime type
//e.g. a file named with extension `ppt` but has the content of a `pptx`
return true;
}

return false;
}

public List<String> getValidMimeTypes() {
List<String> validMimeTypes = Arrays.asList(XLS, XLSX,
DOC, DOCX, PPT, PPTX, ODT, RTF, TXT, ODS, ODP,
PDF, JPEG, PNG, SVG
);
return validMimeTypes;
}
}
Expand Up @@ -19,15 +19,25 @@

package org.bigbluebutton.presentation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.bigbluebutton.presentation.FileTypeConstants.*;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;

@SuppressWarnings("serial")
public final class SupportedFileTypes {


private static Logger log = LoggerFactory.getLogger(SupportedFileTypes.class);
private static MimeTypeUtils mimeTypeUtils = new MimeTypeUtils();

private static final List<String> SUPPORTED_FILE_LIST = Collections.unmodifiableList(new ArrayList<String>(15) {
{
// Add all the supported files
Expand Down Expand Up @@ -76,4 +86,56 @@ public static boolean isPdfFile(String fileExtension) {
public static boolean isImageFile(String fileExtension) {
return IMAGE_FILE_LIST.contains(fileExtension.toLowerCase());
}

/*
* It was tested native java methods to detect mimetypes, such as:
* - URLConnection.guessContentTypeFromStream(InputStream is);
* - Files.probeContentType(Path path);
* - FileNameMap fileNameMap.getContentTypeFor(String file.getName());
* - MimetypesFileTypeMap fileTypeMap.getContentType(File file);
* But none of them was as successful as the linux based command
*/
public static String detectMimeType(File pres) {
String mimeType = "";
if (pres != null && pres.isFile()){
try {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("bash", "-c", "file -b --mime-type " + pres.getAbsolutePath());
Process process = processBuilder.start();
StringBuilder output = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
int exitVal = process.waitFor();
if (exitVal == 0) {
mimeType = output.toString().trim();
} else {
log.error("Error while executing command {} for file {}, error: {}",
process.toString(), pres.getAbsolutePath(), process.getErrorStream());
}
} catch (IOException e) {
log.error("Could not read file [{}]", pres.getAbsolutePath(), e.getMessage());
} catch (InterruptedException e) {
log.error("Flow interrupted for file [{}]", pres.getAbsolutePath(), e.getMessage());
}
}
return mimeType;
}

public static Boolean isPresentationMimeTypeValid(File pres, String fileExtension) {
String mimeType = detectMimeType(pres);

if(mimeType == null || mimeType == "") return false;

if(!mimeTypeUtils.getValidMimeTypes().contains(mimeType)) return false;

if(!mimeTypeUtils.extensionMatchMimeType(mimeType, fileExtension)) {
log.error("File with extension [{}] doesn't match with mimeType [{}].", fileExtension, mimeType);
return false;
}

return true;
}
}
Expand Up @@ -19,10 +19,15 @@

package org.bigbluebutton.presentation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.nio.file.Path;

public final class Util {

private static Logger log = LoggerFactory.getLogger(Util.class);

public static void deleteDirectory(File directory) {
/**
* Go through each directory and check if it's not empty.
Expand All @@ -40,4 +45,20 @@ public static void deleteDirectory(File directory) {
// Now that the directory is empty. Delete it.
directory.delete();
}


public static void deleteDirectoryFromFileHandlingErrors(File presentationFile) {
if ( presentationFile != null ){
Path presDir = presentationFile.toPath().getParent();
try {
File presFileDir = new File(presDir.toString());
if (presFileDir.exists()) {
deleteDirectory(presFileDir);
}
} catch (Exception ex) {
log.error("Error while trying to delete directory {}", presDir.toString(), ex);
}
}
}

}
Expand Up @@ -35,6 +35,8 @@

import com.google.gson.Gson;

import static org.bigbluebutton.presentation.Util.deleteDirectoryFromFileHandlingErrors;

public abstract class Office2PdfPageConverter {
private static Logger log = LoggerFactory.getLogger(Office2PdfPageConverter.class);

Expand Down Expand Up @@ -95,6 +97,7 @@ public static boolean convert(File presentationFile, File output, int page, Uplo
return false;
}
} catch (Exception e) {
deleteDirectoryFromFileHandlingErrors(presentationFile);
Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
Expand Down
Expand Up @@ -50,6 +50,20 @@ public void sendUploadFileTooLargeMessage(PresentationUploadToken pres, int uplo
maxUploadFileSize);
messagingService.sendDocConversionMsg(progress);
}
public void sendInvalidMimeTypeMessage(UploadedPresentation pres, String fileMime, String fileExtension) {
DocInvalidMimeType invalidMimeType = new DocInvalidMimeType(
pres.getPodId(),
pres.getMeetingId(),
pres.getId(),
pres.getTemporaryPresentationId(),
pres.getName(),
pres.getAuthzToken(),
"IVALID_MIME_TYPE",
fileMime,
fileExtension
);
messagingService.sendDocConversionMsg(invalidMimeType);
}
public void sendUploadFileTimedout(UploadedPresentation pres, int page) {
UploadFileTimedoutMessage errorMessage = new UploadFileTimedoutMessage(
pres.getPodId(),
Expand Down

0 comments on commit 520b31a

Please sign in to comment.