diff --git a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java index de20a0897b..adf714a074 100644 --- a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java +++ b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java @@ -7,6 +7,7 @@ import android.os.Build; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.content.ContentUris; +import android.content.ContentResolver; import android.os.Environment; import android.webkit.MimeTypeMap; @@ -98,6 +99,7 @@ public class RealPathUtil { cacheDir.mkdirs(); } + String mimeType = getMimeType(uri.getPath()); tmpFile = File.createTempFile("tmp", fileName, cacheDir); ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r"); @@ -182,6 +184,11 @@ public class RealPathUtil { return getMimeType(file); } + public static String getMimeTypeFromUri(final Context context, final Uri uri) { + ContentResolver cR = context.getContentResolver(); + return cR.getType(uri); + } + public static void deleteTempFiles(final File dir) { try { if (dir.isDirectory()) { diff --git a/android/app/src/main/java/com/mattermost/share/ShareModule.java b/android/app/src/main/java/com/mattermost/share/ShareModule.java index de64a6bb1a..6500b660a4 100644 --- a/android/app/src/main/java/com/mattermost/share/ShareModule.java +++ b/android/app/src/main/java/com/mattermost/share/ShareModule.java @@ -126,7 +126,7 @@ public class ShareModule extends ReactContextBaseJavaModule { map = Arguments.createMap(); text = "file://" + filePath; map.putString("value", text); - map.putString("type", RealPathUtil.getMimeType(filePath)); + map.putString("type", RealPathUtil.getMimeTypeFromUri(currentActivity, uri)); items.pushMap(map); } } diff --git a/app/utils/file.js b/app/utils/file.js index 8794d222c8..c68f584ec9 100644 --- a/app/utils/file.js +++ b/app/utils/file.js @@ -3,11 +3,13 @@ import {Platform} from 'react-native'; import RNFetchBlob from 'react-native-fetch-blob'; +import mimeDB from 'mime-db'; import {lookupMimeType} from 'mattermost-redux/utils/file_utils'; import {DeviceTypes} from 'app/constants/'; +const EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/; const {DOCUMENTS_PATH, IMAGES_PATH, VIDEOS_PATH} = DeviceTypes; const DEFAULT_SERVER_MAX_FILE_SIZE = 50 * 1024 * 1024;// 50 Mb @@ -32,6 +34,9 @@ const SUPPORTED_VIDEO_FORMAT = Platform.select({ android: ['video/3gpp', 'video/x-matroska', 'video/mp4', 'video/webm'], }); +const types = {}; +const extensions = {}; + export function generateId() { // Implementation taken from http://stackoverflow.com/a/2117523 let id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; @@ -144,3 +149,69 @@ export const isVideo = (file) => { return SUPPORTED_VIDEO_FORMAT.includes(mime); }; + +/** + * Get the default extension for a MIME type. + * + * @param {string} type + * @return {boolean|string} + */ + +export function getExtensionFromMime(type) { + if (!Object.keys(extensions).length) { + populateMaps(); + } + + if (!type || typeof type !== 'string') { + return false; + } + + // TODO: use media-typer + const match = EXTRACT_TYPE_REGEXP.exec(type); + + // get extensions + const exts = match && extensions[match[1].toLowerCase()]; + + if (!exts || !exts.length) { + return false; + } + + return exts[0]; +} + +/** + * Populate the extensions and types maps. + * @private + */ + +function populateMaps() { + // source preference (least -> most) + const preference = ['nginx', 'apache', undefined, 'iana']; //eslint-disable-line no-undefined + + Object.keys(mimeDB).forEach((type) => { + const mime = mimeDB[type]; + const exts = mime.extensions; + + if (!exts || !exts.length) { + return; + } + + extensions[type] = exts; + + for (let i = 0; i < exts.length; i++) { + const extension = exts[i]; + + if (types[extension]) { + const from = preference.indexOf(mimeDB[types[extension]].source); + const to = preference.indexOf(mime.source); + + if (types[extension] !== 'application/octet-stream' && + (from > to || (from === to && types[extension].substr(0, 12) === 'application/'))) { + continue; + } + } + + types[extension] = type; + } + }); +} diff --git a/share_extension/android/extension_post/extension_post.js b/share_extension/android/extension_post/extension_post.js index a2f92368b2..9a5f1fbb40 100644 --- a/share_extension/android/extension_post/extension_post.js +++ b/share_extension/android/extension_post/extension_post.js @@ -6,6 +6,7 @@ import {NavigationActions} from 'react-navigation'; import TouchableItem from 'react-navigation/src/views/TouchableItem'; import PropTypes from 'prop-types'; import {intlShape} from 'react-intl'; + import { Image, NativeModules, @@ -25,6 +26,7 @@ import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_ import PaperPlane from 'app/components/paper_plane'; import mattermostManaged from 'app/mattermost_managed'; +import {getExtensionFromMime} from 'app/utils/file'; import {emptyFunction} from 'app/utils/general'; import {preventDoubleTap} from 'app/utils/tap'; import {changeOpacity, makeStyleSheetFromTheme} from 'app/utils/theme'; @@ -278,15 +280,20 @@ export default class ExtensionPost extends PureComponent { break; default: { const fullPath = item.value; - const filename = fullPath.replace(/^.*[\\/]/, ''); - const extension = filename.split('.').pop(); const fileSize = await RNFetchBlob.fs.stat(fullPath); + let filename = fullPath.replace(/^.*[\\/]/, ''); + let extension = filename.split('.').pop(); + if (extension === filename) { + extension = getExtensionFromMime(item.type); + filename = `${filename}.${extension}`; + } + totalSize += fileSize.size; files.push({ extension, filename, fullPath, - mimeType: lookupMimeType(filename.toLowerCase()), + mimeType: item.type || lookupMimeType(filename.toLowerCase()), size: getFormattedFileSize(fileSize), type: item.type, });