From 68b963638aa201a7bc10da1c8060b2900424af81 Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Mon, 22 Jun 2020 09:04:33 +0200
Subject: [PATCH 1/2] MOBILE-3401 iframe: Don't call cookies plugin if no
 domain

---
 src/components/iframe/iframe.ts | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/components/iframe/iframe.ts b/src/components/iframe/iframe.ts
index 5806412bb..14dc98c54 100644
--- a/src/components/iframe/iframe.ts
+++ b/src/components/iframe/iframe.ts
@@ -106,17 +106,19 @@ export class CoreIframeComponent implements OnChanges {
         if (changes.src) {
             const url = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue) || changes.src.currentValue;
 
-            if (this.platform.is('ios') && !this.urlUtils.isLocalFileUrl(url)) {
+            if (this.platform.is('ios') && url && !this.urlUtils.isLocalFileUrl(url)) {
                 // Save a "fake" cookie for the iframe's domain to fix a bug in WKWebView.
                 try {
                     const win = <WKWebViewCookiesWindow> window;
                     const urlParts = CoreUrl.parse(url);
 
-                    await win.WKWebViewCookies.setCookie({
-                        name: 'MoodleAppCookieForWKWebView',
-                        value: '1',
-                        domain: urlParts.domain,
-                    });
+                    if (urlParts.domain) {
+                        await win.WKWebViewCookies.setCookie({
+                            name: 'MoodleAppCookieForWKWebView',
+                            value: '1',
+                            domain: urlParts.domain,
+                        });
+                    }
                 } catch (err) {
                     // Ignore errors.
                     this.logger.error('Error setting cookie', err);

From 4da6a311687e24c81ea9bcdee2cdf3819f7a1e3f Mon Sep 17 00:00:00 2001
From: Dani Palou <dani@moodle.com>
Date: Mon, 22 Jun 2020 14:24:21 +0200
Subject: [PATCH 2/2] MOBILE-3401 file: Fix selecting files for offline with
 chooser

---
 .../fileuploader/providers/file-handler.ts    |   2 +-
 src/core/fileuploader/providers/helper.ts     |  38 +++--
 src/providers/file.ts                         | 133 +++++++++---------
 3 files changed, 96 insertions(+), 77 deletions(-)

diff --git a/src/core/fileuploader/providers/file-handler.ts b/src/core/fileuploader/providers/file-handler.ts
index d6fabc9fb..257110258 100644
--- a/src/core/fileuploader/providers/file-handler.ts
+++ b/src/core/fileuploader/providers/file-handler.ts
@@ -74,7 +74,7 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler {
 
         if (this.appProvider.isMobile()) {
             handler.action = (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]): Promise<any> => {
-                return this.uploaderHelper.chooseAndUploadFile(maxSize, upload, mimetypes).then((result) => {
+                return this.uploaderHelper.chooseAndUploadFile(maxSize, upload, allowOffline, mimetypes).then((result) => {
                     return {
                         treated: true,
                         result: result
diff --git a/src/core/fileuploader/providers/helper.ts b/src/core/fileuploader/providers/helper.ts
index 15b237a92..d2b4d07c5 100644
--- a/src/core/fileuploader/providers/helper.ts
+++ b/src/core/fileuploader/providers/helper.ts
@@ -60,9 +60,10 @@ export class CoreFileUploaderHelperProvider {
      * @param maxSize Max size of the upload. -1 for no max size.
      * @param upload True if the file should be uploaded, false to return the picked file.
      * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported.
+     * @param allowOffline True to allow uploading in offline.
      * @return Promise resolved when done.
      */
-    async chooseAndUploadFile(maxSize: number, upload?: boolean, mimetypes?: string[]): Promise<any> {
+    async chooseAndUploadFile(maxSize: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]): Promise<any> {
 
         const result = await this.fileChooser.getFile(mimetypes ? mimetypes.join(',') : undefined);
 
@@ -76,9 +77,28 @@ export class CoreFileUploaderHelperProvider {
             result.name = this.getChosenFileNameFromPath(result) || result.name;
         }
 
-        const options = this.fileUploaderProvider.getFileUploadOptions(result.uri, result.name, result.mediaType, true);
+        // Verify that the mimetype is supported.
+        const error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, result.name, result.mediaType);
 
-        return this.uploadFile(result.uri, maxSize, true, options);
+        if (error) {
+            return Promise.reject(error);
+        }
+
+        if (upload) {
+            const size = await this.fileProvider.getExternalFileSize(result.uri);
+
+            await this.confirmUploadFile(size, false, allowOffline);
+
+            const options = this.fileUploaderProvider.getFileUploadOptions(result.uri, result.name, result.mediaType, true);
+
+            return this.uploadFile(result.uri, maxSize, true, options);
+        } else {
+            const entry = await this.fileProvider.getExternalFile(result.uri);
+
+            entry.name = result.name; // In Android sometimes the file is exported with a different name, use the original one.
+
+            return entry;
+        }
     }
 
     /**
@@ -664,15 +684,17 @@ export class CoreFileUploaderHelperProvider {
      * @param name Name to use when uploading the file. If not defined, use the file's name.
      * @return Promise resolved when done.
      */
-    uploadFileObject(file: any, maxSize?: number, upload?: boolean, allowOffline?: boolean, name?: string): Promise<any> {
+    async uploadFileObject(file: any, maxSize?: number, upload?: boolean, allowOffline?: boolean, name?: string): Promise<any> {
         if (maxSize != -1 && file.size > maxSize) {
             return this.errorMaxBytes(maxSize, file.name);
         }
 
-        return this.confirmUploadFile(file.size, false, allowOffline).then(() => {
-            // We have the data of the file to be uploaded, but not its URL (needed). Create a copy of the file to upload it.
-            return this.copyAndUploadFile(file, upload, name);
-        });
+        if (upload) {
+            await this.confirmUploadFile(file.size, false, allowOffline);
+        }
+
+        // We have the data of the file to be uploaded, but not its URL (needed). Create a copy of the file to upload it.
+        return this.copyAndUploadFile(file, upload, name);
     }
 
     /**
diff --git a/src/providers/file.ts b/src/providers/file.ts
index 173ff4311..dad6b1c8f 100644
--- a/src/providers/file.ts
+++ b/src/providers/file.ts
@@ -696,6 +696,18 @@ export class CoreFileProvider {
         });
     }
 
+    /**
+     * Calculate the size of a file.
+     *
+     * @param path Absolute path to the file.
+     * @return Promise to be resolved when the size is calculated.
+     */
+    async getExternalFileSize(path: string): Promise<number> {
+        const fileEntry = await this.getExternalFile(path);
+
+        return this.getSize(fileEntry);
+    }
+
     /**
      * Removes a file that might be outside the app's folder.
      *
@@ -770,7 +782,7 @@ export class CoreFileProvider {
      * @return Promise resolved when the entry is moved.
      */
     moveDir(originalPath: string, newPath: string, destDirExists?: boolean): Promise<any> {
-        return this.moveFileOrDir(originalPath, newPath, true, destDirExists);
+        return this.copyOrMoveFileOrDir(originalPath, newPath, true, false, destDirExists);
     }
 
     /**
@@ -783,47 +795,7 @@ export class CoreFileProvider {
      * @return Promise resolved when the entry is moved.
      */
     moveFile(originalPath: string, newPath: string, destDirExists?: boolean): Promise<any> {
-        return this.moveFileOrDir(originalPath, newPath, false, destDirExists);
-    }
-
-    /**
-     * Move a file/dir.
-     *
-     * @param originalPath Path to the file/dir to move.
-     * @param newPath New path of the file/dir.
-     * @param isDir Whether it's a dir or a file.
-     * @param destDirExists Set it to true if you know the directory where to put the file/dir exists. If false, the function will
-     *                      try to create it (slower).
-     * @return Promise resolved when the entry is moved.
-     */
-    protected moveFileOrDir(originalPath: string, newPath: string, isDir?: boolean, destDirExists?: boolean): Promise<any> {
-        const moveFn = isDir ? this.file.moveDir.bind(this.file) : this.file.moveFile.bind(this.file);
-
-        return this.init().then(() => {
-            // Remove basePath if it's in the paths.
-            originalPath = this.removeStartingSlash(originalPath.replace(this.basePath, ''));
-            newPath = this.removeStartingSlash(newPath.replace(this.basePath, ''));
-
-            const newPathFileAndDir = this.getFileAndDirectoryFromPath(newPath);
-
-            if (newPathFileAndDir.directory && !destDirExists) {
-                // Create the target directory if it doesn't exist.
-                return this.createDir(newPathFileAndDir.directory);
-            }
-        }).then(() => {
-
-            return moveFn(this.basePath, originalPath, this.basePath, newPath).catch((error) => {
-                // The move can fail if the path has encoded characters. Try again if that's the case.
-                const decodedOriginal = decodeURI(originalPath),
-                    decodedNew = decodeURI(newPath);
-
-                if (decodedOriginal != originalPath || decodedNew != newPath) {
-                    return moveFn(this.basePath, decodedOriginal, this.basePath, decodedNew);
-                } else {
-                    return Promise.reject(error);
-                }
-            });
-        });
+        return this.copyOrMoveFileOrDir(originalPath, newPath, false, false, destDirExists);
     }
 
     /**
@@ -836,7 +808,7 @@ export class CoreFileProvider {
      * @return Promise resolved when the entry is copied.
      */
     copyDir(from: string, to: string, destDirExists?: boolean): Promise<any> {
-        return this.copyFileOrDir(from, to, true, destDirExists);
+        return this.copyOrMoveFileOrDir(from, to, true, true, destDirExists);
     }
 
     /**
@@ -849,46 +821,61 @@ export class CoreFileProvider {
      * @return Promise resolved when the entry is copied.
      */
     copyFile(from: string, to: string, destDirExists?: boolean): Promise<any> {
-        return this.copyFileOrDir(from, to, false, destDirExists);
+        return this.copyOrMoveFileOrDir(from, to, false, true, destDirExists);
     }
 
     /**
-     * Copy a file or a directory.
+     * Copy or move a file or a directory.
      *
      * @param from Path to the file/dir to move.
      * @param to New path of the file/dir.
      * @param isDir Whether it's a dir or a file.
+     * @param copy Whether to copy. If false, it will move the file.
      * @param destDirExists Set it to true if you know the directory where to put the file/dir exists. If false, the function will
      *                      try to create it (slower).
      * @return Promise resolved when the entry is copied.
      */
-    protected copyFileOrDir(from: string, to: string, isDir?: boolean, destDirExists?: boolean): Promise<any> {
-        const copyFn = isDir ? this.file.copyDir.bind(this.file) : this.file.copyFile.bind(this.file);
+    protected async copyOrMoveFileOrDir(from: string, to: string, isDir?: boolean, copy?: boolean, destDirExists?: boolean)
+            : Promise<Entry> {
 
-        return this.init().then(() => {
-            // Paths cannot start with "/". Remove basePath if present.
-            from = this.removeStartingSlash(from.replace(this.basePath, ''));
-            to = this.removeStartingSlash(to.replace(this.basePath, ''));
+        const fileIsInAppFolder = this.isPathInAppFolder(from);
 
-            const toFileAndDir = this.getFileAndDirectoryFromPath(to);
+        if (!fileIsInAppFolder) {
+            return this.copyOrMoveExternalFile(from, to, copy);
+        }
 
-            if (toFileAndDir.directory && !destDirExists) {
-                // Create the target directory if it doesn't exist.
-                return this.createDir(toFileAndDir.directory);
+        const moveCopyFn = copy ?
+                (isDir ? this.file.copyDir.bind(this.file) : this.file.copyFile.bind(this.file)) :
+                (isDir ? this.file.moveDir.bind(this.file) : this.file.moveFile.bind(this.file));
+
+        await this.init();
+
+        // Paths cannot start with "/". Remove basePath if present.
+        from = this.removeStartingSlash(from.replace(this.basePath, ''));
+        to = this.removeStartingSlash(to.replace(this.basePath, ''));
+
+        const toFileAndDir = this.getFileAndDirectoryFromPath(to);
+
+        if (toFileAndDir.directory && !destDirExists) {
+            // Create the target directory if it doesn't exist.
+            await this.createDir(toFileAndDir.directory);
+        }
+
+        try {
+            const entry = await moveCopyFn(this.basePath, from, this.basePath, to);
+
+            return entry;
+        } catch (error) {
+            // The copy can fail if the path has encoded characters. Try again if that's the case.
+            const decodedFrom = decodeURI(from);
+            const decodedTo = decodeURI(to);
+
+            if (from != decodedFrom || to != decodedTo) {
+                return moveCopyFn(this.basePath, decodedFrom, this.basePath, decodedTo);
+            } else {
+                return Promise.reject(error);
             }
-        }).then(() => {
-            return copyFn(this.basePath, from, this.basePath, to).catch((error) => {
-                // The copy can fail if the path has encoded characters. Try again if that's the case.
-                const decodedFrom = decodeURI(from),
-                    decodedTo = decodeURI(to);
-
-                if (from != decodedFrom || to != decodedTo) {
-                    return copyFn(this.basePath, decodedFrom, this.basePath, decodedTo);
-                } else {
-                    return Promise.reject(error);
-                }
-            });
-        });
+        }
     }
 
     /**
@@ -1281,6 +1268,16 @@ export class CoreFileProvider {
 
         return src.replace(CoreConfigConstants.ioswebviewscheme + '://localhost/_app_file_', 'file://');
     }
+
+    /**
+     * Check if a certain path is in the app's folder (basePath).
+     *
+     * @param path Path to check.
+     * @return Whether it's in the app folder.
+     */
+    protected isPathInAppFolder(path: string): boolean {
+        return !path || !path.match(/^[a-z0-9]+:\/\//i) || path.indexOf(this.basePath) != -1;
+    }
 }
 
 export class CoreFile extends makeSingleton(CoreFileProvider) {}