1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

Server: Fixed crash when rendering note with links to non-existing resources or notes

This commit is contained in:
Laurent Cozic 2021-01-31 17:00:24 +00:00
parent 61399cec62
commit 07484de91e
2 changed files with 76 additions and 96 deletions

View File

@ -79,8 +79,14 @@ export default class Application extends BaseApplication {
return `${itemId}.md`;
}
private async itemMetadataFile(parentId: Uuid, itemId: string): Promise<File> {
private async itemMetadataFile(parentId: Uuid, itemId: string): Promise<File | null> {
const file = await this.models.file().fileByName(parentId, this.itemIdFilename(itemId), { skipPermissionCheck: true });
if (!file) {
// We don't throw an error because it can happen if the note
// contains an invalid link to a resource or note.
logger.error(`Could not find item with ID "${itemId}" on parent "${parentId}"`);
return null;
}
return this.models.file().loadWithContent(file.id, { skipPermissionCheck: true });
}
@ -114,6 +120,8 @@ export default class Application extends BaseApplication {
for (const itemId of itemIds) {
const itemFile = await this.itemMetadataFile(noteFileParentId, itemId);
if (!itemFile) continue;
output[itemId] = {
item: await this.unserializeItem(itemFile),
file: itemFile,
@ -162,6 +170,8 @@ export default class Application extends BaseApplication {
resources: resourceInfos,
itemIdToUrl: (itemId: Uuid) => {
if (!linkedItemInfos[itemId]) return '#';
const item = linkedItemInfos[itemId].item;
if (!item) throw new Error(`No such item in this note: ${itemId}`);

View File

@ -1,7 +1,39 @@
import { NoteEntity } from '@joplin/lib/services/database/types';
import routeHandler from '../../middleware/routeHandler';
import { putFileContent, testFilePath, postDirectory } from '../../utils/testing/fileApiUtils';
import { postShare } from '../../utils/testing/shareApiUtils';
import { beforeAllDb, afterAllTests, parseHtml, beforeEachDb, createUserAndSession, createFile, koaAppContext, checkContextError } from '../../utils/testing/testUtils';
import { beforeAllDb, afterAllTests, parseHtml, beforeEachDb, createUserAndSession, createFile, koaAppContext, checkContextError, expectNotThrow } from '../../utils/testing/testUtils';
function makeNoteSerializedBody(note: NoteEntity): string {
return `${'title' in note ? note.title : 'Title'}
${'body' in note ? note.body : 'Body'}
id: ${'id' in note ? note.id : 'b39dadd7a63742bebf3125fd2a9286d4'}
parent_id: e98f305dde8b47b793f031cf883324ff
created_time: 2020-10-15T10:34:16.044Z
updated_time: 2021-01-28T23:10:30.054Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 1
todo_due: 1602760405000
todo_completed: 0
source: joplindev-desktop
source_application: net.cozic.joplindev-desktop
application_data:
order: 0
user_created_time: 2020-10-15T10:34:16.044Z
user_updated_time: 2020-10-19T17:21:03.394Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 1
type_: 1`;
}
const resourceSize = 2720;
@ -25,97 +57,6 @@ type_: 4`,
};
const noteContents: Record<string, string> = {
simple: `Testing title
Testing body
id: b39dadd7a63742bebf3125fd2a9286d4
parent_id: e98f305dde8b47b793f031cf883324ff
created_time: 2020-10-15T10:34:16.044Z
updated_time: 2021-01-28T23:10:30.054Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 1
todo_due: 1602760405000
todo_completed: 0
source: joplindev-desktop
source_application: net.cozic.joplindev-desktop
application_data:
order: 0
user_created_time: 2020-10-15T10:34:16.044Z
user_updated_time: 2020-10-19T17:21:03.394Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 1
type_: 1`,
katex: `Katex Test
$\\sqrt{3x-1}+(1+x)^2$
id: b39dadd7a63742bebf3125fd2a9286d4
parent_id: e98f305dde8b47b793f031cf883324ff
created_time: 2020-10-15T10:34:16.044Z
updated_time: 2021-01-28T23:10:30.054Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 1
todo_due: 1602760405000
todo_completed: 0
source: joplindev-desktop
source_application: net.cozic.joplindev-desktop
application_data:
order: 0
user_created_time: 2020-10-15T10:34:16.044Z
user_updated_time: 2020-10-19T17:21:03.394Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 1
type_: 1`,
image: `Image Test
![my image](:/96765a68655f4446b3dbad7d41b6566e)
id: b39dadd7a63742bebf3125fd2a9286d4
parent_id: e98f305dde8b47b793f031cf883324ff
created_time: 2020-10-15T10:34:16.044Z
updated_time: 2021-01-28T23:10:30.054Z
is_conflict: 0
latitude: 0.00000000
longitude: 0.00000000
altitude: 0.0000
author:
source_url:
is_todo: 1
todo_due: 1602760405000
todo_completed: 0
source: joplindev-desktop
source_application: net.cozic.joplindev-desktop
application_data:
order: 0
user_created_time: 2020-10-15T10:34:16.044Z
user_updated_time: 2020-10-19T17:21:03.394Z
encryption_cipher_text:
encryption_applied: 0
markup_language: 1
is_shared: 1
type_: 1`,
};
async function getShareContent(shareId: string, query: any = {}): Promise<string | Buffer> {
const context = await koaAppContext({
request: {
@ -146,7 +87,10 @@ describe('shares.joplin', function() {
test('should display a simple note', async function() {
const { user, session } = await createUserAndSession();
await createFile(user.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:', noteContents.simple);
await createFile(user.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:', makeNoteSerializedBody({
title: 'Testing title',
body: 'Testing body',
}));
const share = await postShare(session.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:');
@ -161,7 +105,9 @@ describe('shares.joplin', function() {
test('should load plugins', async function() {
const { user, session } = await createUserAndSession();
await createFile(user.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:', noteContents.katex);
await createFile(user.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:', makeNoteSerializedBody({
body: '$\\sqrt{3x-1}+(1+x)^2$',
}));
const share = await postShare(session.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:');
@ -173,7 +119,9 @@ describe('shares.joplin', function() {
test('should render attached images', async function() {
const { user, session } = await createUserAndSession();
await createFile(user.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:', noteContents.image);
await createFile(user.id, 'root:/b39dadd7a63742bebf3125fd2a9286d4.md:', makeNoteSerializedBody({
body: '![my image](:/96765a68655f4446b3dbad7d41b6566e)',
}));
await postDirectory(session.id, 'root', '.resource');
await putFileContent(session.id, 'root:/.resource/96765a68655f4446b3dbad7d41b6566e:', testFilePath());
await createFile(user.id, 'root:/96765a68655f4446b3dbad7d41b6566e.md:', resourceContents.image);
@ -200,4 +148,26 @@ describe('shares.joplin', function() {
expect(resourceContent.byteLength).toBe(resourceSize);
});
test('should not throw an error if the note contains links to non-existing items', async function() {
const { user, session } = await createUserAndSession();
{
const noteId = 'b39dadd7a63742bebf3125fd2a9286d4';
await createFile(user.id, `root:/${noteId}.md:`, makeNoteSerializedBody({
body: '![missing](:/531a2a839a2c493a88c45e39c6cb9ed4)',
}));
const share = await postShare(session.id, `root:/${noteId}.md:`);
await expectNotThrow(async () => getShareContent(share.id));
}
{
const noteId = 'b39dadd7a63742bebf3125fd2a9286d5';
await createFile(user.id, `root:/${noteId}.md:`, makeNoteSerializedBody({
body: '[missing too](:/531a2a839a2c493a88c45e39c6cb9ed4)',
}));
const share = await postShare(session.id, `root:/${noteId}.md:`);
await expectNotThrow(async () => getShareContent(share.id));
}
});
});