-
-
Notifications
You must be signed in to change notification settings - Fork 5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Desktop: Fixes #10946: Stop crashing HTML/MD importer when content has link with very long name #10947
Desktop: Fixes #10946: Stop crashing HTML/MD importer when content has link with very long name #10947
Changes from all commits
b1db308
deceb77
7d25c00
f82c6d3
f7cf69f
0223773
07e7ff7
fbc7aea
3a8f417
706bdc0
bf7e03b
f49b092
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -195,4 +195,27 @@ describe('InteropService_Importer_Md', () => { | |
// The invalid image is imported as-is | ||
expect(resource.title).toBe('invalid-image.jpg'); | ||
}); | ||
|
||
it.each([ | ||
'https://example.com', | ||
'http://example.com', | ||
'https://example.com/image.png', | ||
'mailto:[email protected]?subject=test', | ||
'onenote:Title of the note', | ||
'tel:554799992292910', | ||
])('should filter paths to external files', async (link: string) => { | ||
const importer = new InteropService_Importer_Md(); | ||
expect(importer.isLinkToLocalFile(link)).toBe(false); | ||
}); | ||
|
||
it.each([ | ||
'asdfasf', | ||
'asdfasf.png', | ||
'base/path/asdfasf.png', | ||
'./base/path/asdfasf.png', | ||
'/base/path/asdfasf.pdf', | ||
])('should consider local file', async (link: string) => { | ||
const importer = new InteropService_Importer_Md(); | ||
expect(importer.isLinkToLocalFile(link)).toBe(true); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,6 @@ import htmlUtils from '../../htmlUtils'; | |
import { unique } from '../../ArrayUtils'; | ||
const { pregQuote } = require('../../string-utils-common'); | ||
import { MarkupToHtml } from '@joplin/renderer'; | ||
import { isDataUrl } from '@joplin/utils/url'; | ||
import { stripBom } from '../../string-utils'; | ||
|
||
export default class InteropService_Importer_Md extends InteropService_Importer_Base { | ||
|
@@ -112,48 +111,47 @@ export default class InteropService_Importer_Md extends InteropService_Importer_ | |
for (const encodedLink of fileLinks) { | ||
const link = decodeURI(encodedLink); | ||
|
||
if (isDataUrl(link)) { | ||
// Just leave it as it is. We could potentially import | ||
// it as a resource but for now that's good enough. | ||
} else { | ||
// Handle anchor links appropriately | ||
const trimmedLink = this.trimAnchorLink(link); | ||
const attachmentPath = filename(`${dirname(filePath)}/${trimmedLink}`, true); | ||
const pathWithExtension = `${attachmentPath}.${fileExtension(trimmedLink)}`; | ||
const stat = await shim.fsDriver().stat(pathWithExtension); | ||
const isDir = stat ? stat.isDirectory() : false; | ||
if (stat && !isDir) { | ||
const supportedFileExtension = this.metadata().fileExtensions; | ||
const resolvedPath = shim.fsDriver().resolve(pathWithExtension); | ||
let id = ''; | ||
// If the link looks like a note, then import it | ||
if (supportedFileExtension.indexOf(fileExtension(trimmedLink).toLowerCase()) >= 0) { | ||
// If the note hasn't been imported yet, do so now | ||
if (!this.importedNotes[resolvedPath]) { | ||
await this.importFile(resolvedPath, parentFolderId); | ||
} | ||
|
||
id = this.importedNotes[resolvedPath].id; | ||
} else { | ||
const resource = await shim.createResourceFromPath(pathWithExtension, null, { resizeLargeImages: 'never' }); | ||
id = resource.id; | ||
// Just leave it as it is. We could potentially import 'data:' links | ||
// as a resource but for now that's good enough. | ||
if (!this.isLinkToLocalFile(link)) continue; | ||
|
||
// Handle anchor links appropriately | ||
const trimmedLink = this.trimAnchorLink(link); | ||
const attachmentPath = filename(`${dirname(filePath)}/${trimmedLink}`, true); | ||
const pathWithExtension = `${attachmentPath}.${fileExtension(trimmedLink)}`; | ||
const stat = await shim.fsDriver().stat(pathWithExtension); | ||
const isDir = stat ? stat.isDirectory() : false; | ||
if (stat && !isDir) { | ||
const supportedFileExtension = this.metadata().fileExtensions; | ||
const resolvedPath = shim.fsDriver().resolve(pathWithExtension); | ||
let id = ''; | ||
// If the link looks like a note, then import it | ||
if (supportedFileExtension.indexOf(fileExtension(trimmedLink).toLowerCase()) >= 0) { | ||
// If the note hasn't been imported yet, do so now | ||
if (!this.importedNotes[resolvedPath]) { | ||
await this.importFile(resolvedPath, parentFolderId); | ||
} | ||
|
||
// The first is a normal link, the second is supports the <link> and [](<link with spaces>) syntax | ||
// Only opening patterns are consider in order to cover all occurrences | ||
// We need to use the encoded link as well because some links (link's with spaces) | ||
// will appear encoded in the source. Other links (unicode chars) will not | ||
const linksToReplace = [this.trimAnchorLink(link), this.trimAnchorLink(encodedLink)]; | ||
id = this.importedNotes[resolvedPath].id; | ||
} else { | ||
const resource = await shim.createResourceFromPath(pathWithExtension, null, { resizeLargeImages: 'never' }); | ||
id = resource.id; | ||
} | ||
|
||
// The first is a normal link, the second is supports the <link> and [](<link with spaces>) syntax | ||
// Only opening patterns are consider in order to cover all occurrences | ||
// We need to use the encoded link as well because some links (link's with spaces) | ||
// will appear encoded in the source. Other links (unicode chars) will not | ||
const linksToReplace = [this.trimAnchorLink(link), this.trimAnchorLink(encodedLink)]; | ||
|
||
for (let j = 0; j < linksToReplace.length; j++) { | ||
const linkToReplace = pregQuote(linksToReplace[j]); | ||
for (let j = 0; j < linksToReplace.length; j++) { | ||
const linkToReplace = pregQuote(linksToReplace[j]); | ||
|
||
// Markdown links | ||
updated = markdownUtils.replaceResourceUrl(updated, linkToReplace, id); | ||
// Markdown links | ||
updated = markdownUtils.replaceResourceUrl(updated, linkToReplace, id); | ||
|
||
// HTML links | ||
updated = htmlUtils.replaceResourceUrl(updated, linkToReplace, id); | ||
} | ||
// HTML links | ||
updated = htmlUtils.replaceResourceUrl(updated, linkToReplace, id); | ||
} | ||
} | ||
} | ||
|
@@ -184,4 +182,15 @@ export default class InteropService_Importer_Md extends InteropService_Importer_ | |
|
||
return this.importedNotes[resolvedPath]; | ||
} | ||
|
||
public isLinkToLocalFile(path: string) { | ||
try { | ||
new URL(path); | ||
} catch (error) { | ||
// if it fails it probably is a relative path (local file) | ||
if (error && error.code === 'ERR_INVALID_URL') return true; | ||
throw error; | ||
} | ||
return false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think your logic now is that if the protocol is defined you return false, and otherwise you return false. Basically I don't think your check on the protocol is doing anything There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By the way is the URL object defined on mobile? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea was that if the link has a protocol it isn't a local file ( I don't think it is an issue if the URL exists on mobile or not because this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't know, would you mind checking? Do we use
That's not what your code does though - it instantiate URL and either returns true if it fails, or false otherwise. Protocol has no relevance to your code. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -325,6 +325,7 @@ ondoctype | |
onedrive | ||
onelink | ||
onend | ||
onenote | ||
onfoo | ||
onformat | ||
onmatch | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way I've just checked
c:\\test.txt
orc:/test.txt
andnew URL
doesn't throw an error which means they won't be considered link to files. Not to mention thatfile:///path/to/file.txt
is both a valid URL and valid link to a local file. Actually I'm wondering what we're doing here testing for URLs when we are not looking for URLs?Are there no other reasonable ways to know if something look like a valid local path? I feel like this is a solved problem and there must be plenty of libraries or code snippets out there that can do this. Maybe even in Joplin source code we already have this.