From 7d2843e018c49a79a91e0a2dac35dbbaaf6f26a1 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 12 Apr 2017 14:09:11 +0200
Subject: [PATCH] OO-2693: add stricter validation for the upload in QTI 2.1
 editor

---
 .../LinkFileCombiCalloutController.java       |  2 +-
 .../FileLinkChooserController.java            |  8 +-
 .../linkchooser/LinkChooserController.java    |  5 +-
 .../linkchooser/MediaChooserController.java   |  2 +-
 .../modules/bc/FileCopyController.java        |  2 +-
 .../modules/bc/FileUploadController.java      | 85 ++++++++++++++-----
 .../bc/_i18n/LocalStrings_de.properties       |  1 +
 .../bc/_i18n/LocalStrings_en.properties       |  1 +
 .../modules/bc/commands/CmdUpload.java        |  2 +-
 .../form/flexible/FormUIFactory.java          |  4 +-
 .../richText/RichTextConfiguration.java       | 15 +++-
 .../richText/RichTextElementComponent.java    |  5 +-
 .../ims/qti21/pool/QTI12To21Converter.java    | 11 ++-
 .../ims/qti21/pool/QTI12To21HtmlHandler.java  | 14 +++
 .../dialog/DialogElementsController.java      |  4 +-
 .../wiki/WikiFileUploadController.java        |  2 +-
 16 files changed, 125 insertions(+), 38 deletions(-)

diff --git a/src/main/java/org/olat/core/commons/controllers/filechooser/LinkFileCombiCalloutController.java b/src/main/java/org/olat/core/commons/controllers/filechooser/LinkFileCombiCalloutController.java
index 0256f6f0210..eb3515865c5 100644
--- a/src/main/java/org/olat/core/commons/controllers/filechooser/LinkFileCombiCalloutController.java
+++ b/src/main/java/org/olat/core/commons/controllers/filechooser/LinkFileCombiCalloutController.java
@@ -300,7 +300,7 @@ public class LinkFileCombiCalloutController extends BasicController {
 				// remove file name from relFilePath to represent directory path
 				folderPath = relFilePath.substring(0, relFilePath.lastIndexOf("/"));
 			}
-			toolCtr = new FileUploadController(getWindowControl(), baseContainer, ureq, quotaLeftKB, quotaLeftKB, null, true, false, false, true, true, folderPath);
+			toolCtr = new FileUploadController(getWindowControl(), baseContainer, ureq, quotaLeftKB, quotaLeftKB, null, false, true, false, false, true, true, folderPath);
 		}
 		displayModal(toolCtr);
 	}
diff --git a/src/main/java/org/olat/core/commons/controllers/linkchooser/FileLinkChooserController.java b/src/main/java/org/olat/core/commons/controllers/linkchooser/FileLinkChooserController.java
index 64cf8755bf5..4a2980e052f 100644
--- a/src/main/java/org/olat/core/commons/controllers/linkchooser/FileLinkChooserController.java
+++ b/src/main/java/org/olat/core/commons/controllers/linkchooser/FileLinkChooserController.java
@@ -90,13 +90,15 @@ public class FileLinkChooserController extends BasicController {
 	 * @param rootDir The VFS root directory from which the linkable files should be read
 	 * @param uploadRelPath The relative path within the rootDir where uploaded
 	 *          files should be put into. If NULL, the root Dir is used
+	 * @param absolutePath
 	 * @param suffixes Array of allowed file types
+	 * @param uriValidation Set to true if the filename need to be a valid URI
 	 * @param fileName the path of the file currently edited (in order to compute
 	 *          the correct relative paths for links), e.g. bla/blu.html or
 	 *          index.html
 	 */
 	public FileLinkChooserController(UserRequest ureq, WindowControl wControl,
-			VFSContainer rootDir, String uploadRelPath, String absolutePath, String[] suffixes, String fileName) {
+			VFSContainer rootDir, String uploadRelPath, String absolutePath, String[] suffixes, boolean uriValidation, String fileName) {
 		super(ureq, wControl);
 		this.fileName = fileName;
 		this.suffixes = suffixes;
@@ -167,8 +169,8 @@ public class FileLinkChooserController extends BasicController {
 				}
 			}
 			
-			uploadCtr = new FileUploadController(wControl, fileUploadBase, ureq, uploadLimit, remainingSpace, mimeTypes,
-					true, false, true, true, false);
+			uploadCtr = new FileUploadController(wControl, fileUploadBase, ureq, uploadLimit, remainingSpace,
+					mimeTypes, uriValidation, true, false, true, true, false);
 			listenTo(uploadCtr);
 			// set specific upload path
 			uploadCtr.setUploadRelPath(uploadRelPath);
diff --git a/src/main/java/org/olat/core/commons/controllers/linkchooser/LinkChooserController.java b/src/main/java/org/olat/core/commons/controllers/linkchooser/LinkChooserController.java
index fc074917857..88d2b5cf4ec 100644
--- a/src/main/java/org/olat/core/commons/controllers/linkchooser/LinkChooserController.java
+++ b/src/main/java/org/olat/core/commons/controllers/linkchooser/LinkChooserController.java
@@ -63,6 +63,7 @@ public class LinkChooserController extends BasicController {
 	 * @param uploadRelPath The relative path within the rootDir where uploaded
 	 *          files should be put into. If NULL, the root Dir is used
 	 * @param suffixes Supported file suffixes for file-chooser.
+	 * @param uriValidation Set to true if the filename need to be a valid URI
 	 * @param fileName Base file-path for file-chooser.
 	 * @param userActivityLogger
 	 * @param internalLinkTreeModel Model with internal links e.g. course-node
@@ -70,7 +71,7 @@ public class LinkChooserController extends BasicController {
 	 *          internalLinkTreeModel is null.
 	 */
 	public LinkChooserController(UserRequest ureq, WindowControl wControl, VFSContainer rootDir,
-			String uploadRelPath, String absolutPath, String[] suffixes, String fileName,
+			String uploadRelPath, String absolutPath, String[] suffixes, boolean uriValidation, String fileName,
 			CustomLinkTreeModel customLinkTreeModel, boolean allowCustomMediaChooserFactory) {
 		super(ureq, wControl);
 		
@@ -79,7 +80,7 @@ public class LinkChooserController extends BasicController {
 		linkChooserTabbedPane = new TabbedPane("linkChooserTabbedPane", ureq.getLocale());
 		tabbedPaneViewVC.put("linkChooserTabbedPane", linkChooserTabbedPane);
 
-		fileLinkChooserController = new FileLinkChooserController(ureq, wControl, rootDir, uploadRelPath, absolutPath, suffixes, fileName);		
+		fileLinkChooserController = new FileLinkChooserController(ureq, wControl, rootDir, uploadRelPath, absolutPath, suffixes, uriValidation, fileName);		
 		listenTo(fileLinkChooserController);
 		linkChooserTabbedPane.addTab(translate("linkchooser.tabbedpane.label.filechooser"), fileLinkChooserController.getInitialComponent());
 		
diff --git a/src/main/java/org/olat/core/commons/controllers/linkchooser/MediaChooserController.java b/src/main/java/org/olat/core/commons/controllers/linkchooser/MediaChooserController.java
index 3a75162fc81..120db398b52 100644
--- a/src/main/java/org/olat/core/commons/controllers/linkchooser/MediaChooserController.java
+++ b/src/main/java/org/olat/core/commons/controllers/linkchooser/MediaChooserController.java
@@ -64,7 +64,7 @@ public class MediaChooserController extends LinkChooserController {
 	 */
 	public MediaChooserController(UserRequest ureq, WindowControl wControl, VFSContainer rootDir, String uploadRelPath, String[] suffixes, String fileName,
 			CustomLinkTreeModel customLinkTreeModel, boolean allowCustomMediaFactory) {
-		super(ureq, wControl, rootDir, uploadRelPath, null, suffixes, fileName, customLinkTreeModel, allowCustomMediaFactory);
+		super(ureq, wControl, rootDir, uploadRelPath, null, suffixes, false, fileName, customLinkTreeModel, allowCustomMediaFactory);
 	}
 
 	/**
diff --git a/src/main/java/org/olat/core/commons/modules/bc/FileCopyController.java b/src/main/java/org/olat/core/commons/modules/bc/FileCopyController.java
index e44be4aef6e..8a33a38a43d 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/FileCopyController.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/FileCopyController.java
@@ -87,7 +87,7 @@ public class FileCopyController extends LinkChooserController {
 	
 	public FileCopyController(UserRequest ureq, WindowControl wControl, VFSContainer rootDir,
 			FolderComponent folderComponent) {
-		super(ureq, wControl, rootDir, null, null, null, "", null, true);
+		super(ureq, wControl, rootDir, null, null, null, false, "", null, true);
 		this.folderComponent = folderComponent;
 		vfsLockManager = CoreSpringFactory.getImpl(VFSLockManager.class);
 	}
diff --git a/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java b/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java
index 99c36669dbb..06ca204f17f 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java
@@ -33,6 +33,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -123,6 +124,7 @@ public class FileUploadController extends FormBasicController {
 	private long uploadLimitKB;
 	private long remainingQuotKB;
 	private Set<String> mimeTypes;
+	private boolean uriValidation;
 	//
 	// Form elements
 	private FileElement fileEl;
@@ -174,23 +176,23 @@ public class FileUploadController extends FormBasicController {
 	 */
 	public FileUploadController(WindowControl wControl, VFSContainer curContainer, UserRequest ureq, long upLimitKB, long remainingQuotKB,
 			Set<String> mimeTypesRestriction, boolean showTargetPath) {
-		this(wControl, curContainer, ureq, upLimitKB, remainingQuotKB, mimeTypesRestriction, showTargetPath, false, true, true, true);
+		this(wControl, curContainer, ureq, upLimitKB, remainingQuotKB, mimeTypesRestriction, false, showTargetPath, false, true, true, true);
 	}
 	
 	public FileUploadController(WindowControl wControl, VFSContainer curContainer, UserRequest ureq, long upLimitKB, long remainingQuotKB,
-			Set<String> mimeTypesRestriction, boolean showTargetPath, boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle) {
+			Set<String> mimeTypesRestriction, boolean uriValidation, boolean showTargetPath, boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle) {
 		this(wControl,curContainer,  ureq,  upLimitKB,  remainingQuotKB,
-				mimeTypesRestriction,  showTargetPath,  showMetadata,  resizeImg,  showCancel,  showTitle,null);
+				mimeTypesRestriction, uriValidation, showTargetPath,  showMetadata,  resizeImg,  showCancel,  showTitle,null);
 	}
 	
 	public FileUploadController(WindowControl wControl, VFSContainer curContainer, UserRequest ureq, long upLimitKB, long remainingQuotKB,
-			Set<String> mimeTypesRestriction, boolean showTargetPath, boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle, String subfolderPath) {
+			Set<String> mimeTypesRestriction, boolean uriValidation, boolean showTargetPath, boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle, String subfolderPath) {
 		super(ureq, wControl, "file_upload");
-		setVariables(curContainer, upLimitKB, remainingQuotKB, mimeTypesRestriction, showTargetPath, showMetadata, resizeImg, showCancel, showTitle, subfolderPath);
+		setVariables(curContainer, upLimitKB, remainingQuotKB, mimeTypesRestriction, uriValidation, showTargetPath, showMetadata, resizeImg, showCancel, showTitle, subfolderPath);
 		initForm(ureq);
 	}
 	
-	private void setVariables(VFSContainer curContainer, long upLimitKB, long remainingQuotKB, Set<String> mimeTypesRestriction, boolean showTargetPath,
+	private void setVariables(VFSContainer curContainer, long upLimitKB, long remainingQuotKB, Set<String> mimeTypesRestriction, boolean uriValidation, boolean showTargetPath,
 			boolean showMetadata, boolean resizeImg, boolean showCancel, boolean showTitle, String subfolderPath) {
 		this.currentContainer = curContainer;
 		this.mimeTypes = mimeTypesRestriction;
@@ -198,6 +200,7 @@ public class FileUploadController extends FormBasicController {
 		this.showTargetPath = showTargetPath;
 		// set remaining quota and max upload size
 		this.uploadLimitKB = upLimitKB;
+		this.uriValidation = uriValidation;
 		this.remainingQuotKB = remainingQuotKB;
 		// use base container as upload dir
 		this.uploadRelPath = null;
@@ -883,13 +886,42 @@ public class FileUploadController extends FormBasicController {
 		if(metaDataCtr != null && StringHelper.containsNonWhitespace(metaDataCtr.getFilename())) {
 			return validateFilename(metaDataCtr.getFilename(), metaDataCtr.getFilenameEl());
 		}
-		String filename = fileEl.getUploadFileName();
-		
-		boolean allOk = validateFilename(filename, fileEl);
+
+		boolean allOk = validateFilename(fileEl);
+		return allOk;
+	}
+	
+	private boolean validateFilename(FileElement itemEl) {
+		boolean allOk = true;
+		// validate clean the errors
 		List<ValidationStatus> fileStatus = new ArrayList<>();
-		fileEl.validate(fileStatus);//revalidate because we clear the error
-		fileEl.setDeleteEnabled(fileStatus.size() > 0);
-		return allOk && fileStatus.isEmpty();
+		// revalidate
+		itemEl.validate(fileStatus);
+
+		if(fileStatus.isEmpty()) {
+			String filename = itemEl.getUploadFileName();
+			if (!StringHelper.containsNonWhitespace(filename)) {
+				itemEl.setErrorKey("NoFileChosen", null);
+				allOk &= false;
+			}
+			
+			if(uriValidation) {
+				try {
+					new URI(filename);
+				} catch(Exception e) {
+					itemEl.setErrorKey("cfile.name.notvalid.uri", null);
+					allOk &= false;
+				}
+			}	
+			if(!FileUtils.validateFilename(filename)) {
+				itemEl.setErrorKey("cfile.name.notvalid", null);
+				allOk &= false;
+			}
+			allOk &= validateQuota(itemEl);
+		}
+		
+		itemEl.setDeleteEnabled(!allOk);
+		return allOk;
 	}
 	
 	private boolean validateFilename(String filename, FormItem itemEl) {
@@ -900,20 +932,31 @@ public class FileUploadController extends FormBasicController {
 			itemEl.setErrorKey("NoFileChosen", null);
 			allOk &= false;
 		}
-
-		boolean isFilenameValid = FileUtils.validateFilename(filename);		
-		if(!isFilenameValid) {
+		
+		if(uriValidation) {
+			try {
+				new URI(filename);
+			} catch(Exception e) {
+				itemEl.setErrorKey("cfile.name.notvalid.uri", null);
+				allOk &= false;
+			}
+		}	
+		if(!FileUtils.validateFilename(filename)) {
 			itemEl.setErrorKey("cfile.name.notvalid", null);
 			allOk &= false;
 		}
-		if (remainingQuotKB != -1  && fileEl.getUploadFile() != null
-				&& fileEl.getUploadFile().length() / 1024 > remainingQuotKB) {
-			fileEl.clearError();
+		
+		allOk &= validateQuota(fileEl);
+		return allOk;
+	}
+	
+	private boolean validateQuota(FileElement itemEl) {
+		if (remainingQuotKB != -1  && itemEl.getUploadFile() != null
+				&& itemEl.getUploadFile().length() / 1024 > remainingQuotKB) {
 			String supportAddr = WebappHelper.getMailConfig("mailQuota");
 			getWindowControl().setError(translate("ULLimitExceeded", new String[] { Formatter.roundToString((uploadLimitKB+0f) / 1000, 1), supportAddr }));
-			allOk &= false;
+			return false;
 		}
-		
-		return allOk;
+		return true;
 	}
 }
diff --git a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties
index ff4b9658c21..af0b600ef24 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_de.properties
@@ -45,6 +45,7 @@ cfile.name.nodir=Ein Verzeichnis kann nicht als Dateiname verwendet werden.
 cfile.name.noextension=Diese Datei  enth\u00E4lt keine Erweiterung. Bitte f\u00FCgen Sie eine Erweiterung an bevor Sie die Datei erneut hochladen.
 cfile.name.noname=Kein Dateiname angegeben.
 cfile.name.notvalid=Dieser Dateiname enth\u00E4lt unzul\u00E4ssige Zeichen, bitte entfernen Sie alle Sonderzeichen wie /,\:, etc.
+cfile.name.notvalid.uri=Dieser Dateiname enth\u00E4lt unzul\u00E4ssige Zeichen, bitte entfernen Sie alle Sonderzeichen wie /,\:, Leerzeichen etc.
 cfile.name.notvalid.cannot.edit.metadata=Dieser Dateiname enth\u00E4lt unzul\u00E4ssige Zeichen, Sie k\u00F6nnen die Metadaten daher nicht editieren.
 cfile.name.notvalidchars=Dieser Dateiname enth\u00E4lt die unzul\u00E4ssigen Zeichen "{0}". Bitte entfernen Sie diese bevor Sie die Datei erneut hochladen.
 checkall=Alle ausw\u00E4hlen
diff --git a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties
index cdbea214a9d..73911c3423c 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/core/commons/modules/bc/_i18n/LocalStrings_en.properties
@@ -45,6 +45,7 @@ cfile.name.nodir=Can't use a directory as a file name.
 cfile.name.noextension=This file name contains no extension. Please add a file extension before uploading the file.
 cfile.name.noname=No file name given.
 cfile.name.notvalid=This file name contains invalid characters. Please remove these special characters before uploading the file again.
+cfile.name.notvalid.uri=This file name contains invalid characters. Please remove these special characters like /,\:, blank... before uploading the file again.
 cfile.name.notvalid.cannot.edit.metadata=This file name contains invalid characters. Metadata cannot be edited.
 cfile.name.notvalidchars=This file name contains the invalid characters "{0}". Please remove them before uploading the file again.
 checkall=Select all
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java
index f8d16350649..eb630687a0e 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUpload.java
@@ -148,7 +148,7 @@ public class CmdUpload extends BasicController implements FolderCommand {
 		else remainingQuotaKB = quotaKB - actualUsage;
 		
 		removeAsListenerAndDispose(fileUploadCtr);
-		fileUploadCtr = new FileUploadController(getWindowControl(), currentContainer, ureq, uploadLimitKB, remainingQuotaKB, null,
+		fileUploadCtr = new FileUploadController(getWindowControl(), currentContainer, ureq, uploadLimitKB, remainingQuotaKB, null, false,
 				true, showMetadata, true, showCancel, false);
 		listenTo(fileUploadCtr);
 		mainVC.put("fileUploadCtr", fileUploadCtr.getInitialComponent());
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java b/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java
index ba5ea347a8b..857012b711e 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/FormUIFactory.java
@@ -767,7 +767,7 @@ public class FormUIFactory {
 	/**
 	 * 
 	 * This is a version with olat media only. The tiny media is disabled because we need to catch the object
-	 * tag use by QTI and interpret it as a olat video.
+	 * tag use by QTI and interpret it as a olat video. It enable the strict uri validation for file names.
 	 * 
 	 * @param name
 	 * @param i18nLabel
@@ -791,6 +791,7 @@ public class FormUIFactory {
 		rte.getEditorConfiguration().setInvalidElements(RichTextConfiguration.INVALID_ELEMENTS_FORM_FULL_VALUE_UNSAVE_WITH_SCRIPT);
 		rte.getEditorConfiguration().setExtendedValidElements("script[src|type|defer]");
 		rte.getEditorConfiguration().disableTinyMedia();
+		rte.getEditorConfiguration().setFilenameUriValidation(true);
 		// Add to form and finish
 		formLayout.add(rte);
 		return rte;
@@ -807,6 +808,7 @@ public class FormUIFactory {
 		rte.getEditorConfiguration().setInvalidElements(RichTextConfiguration.INVALID_ELEMENTS_FORM_FULL_VALUE_UNSAVE_WITH_SCRIPT);
 		rte.getEditorConfiguration().setExtendedValidElements("script[src|type|defer]");
 		rte.getEditorConfiguration().disableTinyMedia();
+		rte.getEditorConfiguration().setFilenameUriValidation(true);
 		// Add to form and finish
 		formLayout.add(rte);
 		return rte;
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java
index 5c434a6300b..4e0541b0871 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextConfiguration.java
@@ -156,6 +156,7 @@ public class RichTextConfiguration implements Disposable {
 	private boolean inline = false;
 	private boolean sendOnBlur;
 	private boolean readOnly;
+	private boolean filenameUriValidation = false;
 	private CustomLinkTreeModel linkBrowserCustomTreeModel;	
 	// DOM ID of the flexi form element
 	private String domID;
@@ -882,8 +883,20 @@ public class RichTextConfiguration implements Disposable {
 	public void enableEditorHeight() {
 		setNonQuotedConfigValue(RichTextConfiguration.HEIGHT, "b_initialEditorHeight()");
 	}
-
 	
+	public boolean isFilenameUriValidation() {
+		return filenameUriValidation;
+	}
+
+	/**
+	 * Enable the validation of the URI for filename base on java.net.URI
+	 * 
+	 * @param filenameUriValidation
+	 */
+	public void setFilenameUriValidation(boolean filenameUriValidation) {
+		this.filenameUriValidation = filenameUriValidation;
+	}
+
 	/**
 	 * Get the image suffixes that are supported
 	 * 
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java
index 58ad6a686e4..4836447aac7 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/richText/RichTextElementComponent.java
@@ -136,6 +136,7 @@ class RichTextElementComponent extends FormBaseComponentImpl {
 		// Get allowed suffixes from configuration and requested media browser type from event
 		final RichTextConfiguration config = element.getEditorConfiguration();
 		final boolean allowCustomMediaFactory = config.isAllowCustomMediaFactory();
+		final boolean uriValidation = config.isFilenameUriValidation();
 		final String[] suffixes;
 		if(type.equals(CMD_FILEBROWSER)) {
 			suffixes = null;
@@ -162,10 +163,10 @@ class RichTextElementComponent extends FormBaseComponentImpl {
 				CustomLinkTreeModel linkBrowserCustomTreeModel = config.getLinkBrowserCustomLinkTreeModel();
 				if (type.equals(CMD_FILEBROWSER)) {
 					// when in file mode we include the internal links to the selection
-					myLinkChooserController = new LinkChooserController(lureq, lwControl, baseContainer, uploadRelPath, absolutePath, suffixes, fileName, linkBrowserCustomTreeModel, allowCustomMediaFactory);			
+					myLinkChooserController = new LinkChooserController(lureq, lwControl, baseContainer, uploadRelPath, absolutePath, suffixes, uriValidation, fileName, linkBrowserCustomTreeModel, allowCustomMediaFactory);			
 				} else {
 					// in media or image mode, internal links make no sense here
-					myLinkChooserController = new LinkChooserController(lureq, lwControl, baseContainer, uploadRelPath, absolutePath, suffixes, fileName, null, allowCustomMediaFactory);						
+					myLinkChooserController = new LinkChooserController(lureq, lwControl, baseContainer, uploadRelPath, absolutePath, suffixes, uriValidation, fileName, null, allowCustomMediaFactory);						
 				}
 				return new LayoutMain3ColsController(lureq, lwControl, myLinkChooserController);
 			}
diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java
index 72fdff1e92a..85452ea9f3d 100644
--- a/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java
+++ b/src/main/java/org/olat/ims/qti21/pool/QTI12To21Converter.java
@@ -29,9 +29,11 @@ import java.io.Writer;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.DoubleAdder;
 
@@ -137,6 +139,7 @@ public class QTI12To21Converter {
 	
 	private final ManifestBuilder manifest;
 	private List<String> materialPath = new ArrayList<>();
+	private Map<String,String> materialMappings = new HashMap<>();
 	private List<String> errors = new ArrayList<>();
 	private final DoubleAdder atomicMaxScore = new DoubleAdder();
 	
@@ -298,6 +301,9 @@ public class QTI12To21Converter {
 				VFSItem materialItem = originalContainer.resolve(material);
 				if(materialItem instanceof VFSLeaf) {
 					try(InputStream in = ((VFSLeaf) materialItem).getInputStream()) {
+						if(materialMappings.containsKey(material)) {
+							material = materialMappings.get(material);
+						}
 						File dest = new File(unzippedDirRoot, material);
 						FileUtils.copyToFile(in, dest, "");
 					} catch(Exception e) {
@@ -721,10 +727,12 @@ public class QTI12To21Converter {
 					XMLStreamWriter xtw = xof.createXMLStreamWriter(out);
 						
 					SAXParser parser = new SAXParser();
-					parser.setContentHandler(new QTI12To21HtmlHandler(xtw));
+					QTI12To21HtmlHandler handler = new QTI12To21HtmlHandler(xtw);
+					parser.setContentHandler(handler);
 					parser.parse(new InputSource(new StringReader(trimmedText)));
 					String blockedHtml = out.toString();
 					text = blockedHtml.replace("<start>", "").replace("</start>", "");
+					materialMappings.putAll(handler.getMaterialsMapping());
 				} catch (FactoryConfigurationError | XMLStreamException | SAXException | IOException e) {
 					log.error("", e);
 				}
@@ -733,6 +741,7 @@ public class QTI12To21Converter {
 				text = StringEscapeUtils.unescapeHtml(text);
 			}
 		}
+		System.out.println(text);
 		return text;
 	}
 	
diff --git a/src/main/java/org/olat/ims/qti21/pool/QTI12To21HtmlHandler.java b/src/main/java/org/olat/ims/qti21/pool/QTI12To21HtmlHandler.java
index 76c03abd74f..f1b086190d8 100644
--- a/src/main/java/org/olat/ims/qti21/pool/QTI12To21HtmlHandler.java
+++ b/src/main/java/org/olat/ims/qti21/pool/QTI12To21HtmlHandler.java
@@ -21,6 +21,8 @@ package org.olat.ims.qti21.pool;
 
 import java.util.ArrayDeque;
 import java.util.Deque;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
@@ -47,6 +49,7 @@ class QTI12To21HtmlHandler extends DefaultHandler {
 
 	private int subLevel = 0;
 	private Deque<String> skipTags = new ArrayDeque<String>();
+	private Map<String,String> materialsMapping = new HashMap<>();
 
 	private boolean envelopP = false;
 	private boolean started = false;
@@ -54,6 +57,10 @@ class QTI12To21HtmlHandler extends DefaultHandler {
 	public QTI12To21HtmlHandler(XMLStreamWriter xtw) {
 		this.xtw = xtw;
 	}
+	
+	public Map<String,String> getMaterialsMapping() {
+		return materialsMapping;
+	}
 
 	@Override
 	public void startDocument() throws SAXException {
@@ -120,6 +127,13 @@ class QTI12To21HtmlHandler extends DefaultHandler {
 				//ignore align
 			} else if("xmlns".equals(attrQName) && !StringHelper.containsNonWhitespace(attrValue)) {
 				//ignore empty schema
+			} else if("src".equals(attrQName)) {
+				if(attrValue.contains(" ")) {
+					String newValue = attrValue.replace(' ', '_');
+					materialsMapping.put(attrValue, newValue);
+					attrValue = newValue;
+				}
+				xtw.writeAttribute(attrQName, attrValue);
 			} else {
 				xtw.writeAttribute(attrQName, attrValue);
 			}
diff --git a/src/main/java/org/olat/modules/dialog/DialogElementsController.java b/src/main/java/org/olat/modules/dialog/DialogElementsController.java
index 9b6972d24bf..e3ec5ea6527 100644
--- a/src/main/java/org/olat/modules/dialog/DialogElementsController.java
+++ b/src/main/java/org/olat/modules/dialog/DialogElementsController.java
@@ -415,7 +415,7 @@ public class DialogElementsController extends BasicController {
 			OlatRootFolderImpl forumContainer = getForumContainer(forum.getKey());
 			
 			removeAsListenerAndDispose(fileUplCtr);
-			fileUplCtr = new FileUploadController(getWindowControl(),forumContainer, ureq, (int)FolderConfig.getLimitULKB(), Quota.UNLIMITED, null, false, false, false, true, false);
+			fileUplCtr = new FileUploadController(getWindowControl(),forumContainer, ureq, (int)FolderConfig.getLimitULKB(), Quota.UNLIMITED, null, false, false, false, false, true, false);
 			listenTo(fileUplCtr);
 			
 			recentDialogElement = new DialogElement();
@@ -472,7 +472,7 @@ public class DialogElementsController extends BasicController {
 	
 	private class MyLinkChooserController extends LinkChooserController {
 		public MyLinkChooserController(UserRequest ureq, WindowControl wControl, VFSContainer rootDir, String uploadRelPath) {
-			super(ureq, wControl, rootDir, uploadRelPath, null, null, "", null, true);
+			super(ureq, wControl, rootDir, uploadRelPath, null, null, false, "", null, true);
 		}
 		
 		@Override
diff --git a/src/main/java/org/olat/modules/wiki/WikiFileUploadController.java b/src/main/java/org/olat/modules/wiki/WikiFileUploadController.java
index ee510144b7b..166dfc02e6b 100644
--- a/src/main/java/org/olat/modules/wiki/WikiFileUploadController.java
+++ b/src/main/java/org/olat/modules/wiki/WikiFileUploadController.java
@@ -49,7 +49,7 @@ public class WikiFileUploadController extends BasicController {
 		VelocityContainer mainVC = this.createVelocityContainer("upload_file");
 		
 		fileUplCtr = new FileUploadController(getWindowControl(), mediaFolder, ureq,
-				(int)FolderConfig.getLimitULKB(), Quota.UNLIMITED, null, false, false, true, true, false);
+				(int)FolderConfig.getLimitULKB(), Quota.UNLIMITED, null, false, false, false, true, true, false);
 		listenTo(fileUplCtr);
 		mainVC.put("fileUpload", fileUplCtr.getInitialComponent());
 		putInitialPanel(mainVC);
-- 
GitLab