From ad211b4b4ec257f1e6376faa2d04209b5129e301 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 12 Jun 2019 09:45:31 +0100 Subject: [PATCH] Clipper: Fixes #1600: Handle SVG images and fix issue with invalid file extensions --- ReactNativeClient/lib/net-utils.js | 7 +++++++ ReactNativeClient/lib/path-utils.js | 5 +++-- ReactNativeClient/lib/services/rest/Api.js | 20 +++++++++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/ReactNativeClient/lib/net-utils.js b/ReactNativeClient/lib/net-utils.js index e1f62d52b..ca14e95ca 100644 --- a/ReactNativeClient/lib/net-utils.js +++ b/ReactNativeClient/lib/net-utils.js @@ -30,4 +30,11 @@ netUtils.findAvailablePort = async (possiblePorts, extraRandomPortsToTry = 20) = return port; } +netUtils.mimeTypeFromHeaders = headers => { + if (!headers || !headers['content-type']) return null; + + const splitted = headers['content-type'].split(';'); + return splitted[0].trim().toLowerCase(); +} + module.exports = { netUtils }; \ No newline at end of file diff --git a/ReactNativeClient/lib/path-utils.js b/ReactNativeClient/lib/path-utils.js index df2917d23..4d4a1d8f1 100644 --- a/ReactNativeClient/lib/path-utils.js +++ b/ReactNativeClient/lib/path-utils.js @@ -37,9 +37,10 @@ function isHidden(path) { return b[0] === '.'; } -function safeFileExtension(e) { +function safeFileExtension(e, maxLength = null) { + if (maxLength === null) maxLength = 8; if (!e || !e.replace) return ''; - return e.replace(/[^a-zA-Z0-9]/g, '') + return e.replace(/[^a-zA-Z0-9]/g, '').substr(0, maxLength); } function safeFilename(e, maxLength = null, allowSpaces = false) { diff --git a/ReactNativeClient/lib/services/rest/Api.js b/ReactNativeClient/lib/services/rest/Api.js index bd971d19c..4c0ebd6a3 100644 --- a/ReactNativeClient/lib/services/rest/Api.js +++ b/ReactNativeClient/lib/services/rest/Api.js @@ -12,6 +12,7 @@ const md5 = require('md5'); const { shim } = require('lib/shim'); const HtmlToMd = require('lib/HtmlToMd'); const urlUtils = require('lib/urlUtils.js'); +const { netUtils } = require('lib/net-utils'); const { fileExtension, safeFileExtension, safeFilename, filename } = require('lib/path-utils'); const ApiResponse = require('lib/services/rest/ApiResponse'); const SearchEngineUtils = require('lib/services/SearchEngineUtils'); @@ -459,6 +460,18 @@ class Api { return await shim.attachFileToNote(note, tempFilePath); } + async tryToGuessImageExtFromMimeType_(response, imagePath) { + const mimeType = netUtils.mimeTypeFromHeaders(response.headers); + if (!mimeType) return imagePath; + + const newExt = mimeUtils.toFileExtension(mimeType); + if (!newExt) return imagePath; + + const newImagePath = imagePath + '.' + newExt; + await shim.fsDriver().move(imagePath, newImagePath); + return newImagePath; + } + async downloadImage_(url, allowFileProtocolImages) { const tempDir = Setting.value('tempDir'); @@ -466,6 +479,7 @@ class Api { const name = isDataUrl ? md5(Math.random() + '_' + Date.now()) : filename(url); let fileExt = isDataUrl ? mimeUtils.toFileExtension(mimeUtils.fromDataUrl(url)) : safeFileExtension(fileExtension(url).toLowerCase()); + if (!mimeUtils.fromFileExtension(fileExt)) fileExt = ''; // If the file extension is unknown - clear it. if (fileExt) fileExt = '.' + fileExt; let imagePath = tempDir + '/' + safeFilename(name) + fileExt; if (await shim.fsDriver().exists(imagePath)) imagePath = tempDir + '/' + safeFilename(name) + '_' + md5(Math.random() + '_' + Date.now()).substr(0,10) + fileExt; @@ -479,7 +493,11 @@ class Api { const localPath = uri2path(url); await shim.fsDriver().copy(localPath, imagePath); } else { - await shim.fetchBlob(url, { path: imagePath }); + const response = await shim.fetchBlob(url, { path: imagePath, maxRetry: 1 }); + + // If we could not find the file extension from the URL, try to get it + // now based on the Content-Type header. + if (!fileExt) imagePath = this.tryToGuessImageExtFromMimeType_(response, imagePath); } return imagePath; } catch (error) {