diff --git a/packages/lib/models/BaseItem.test.ts b/packages/lib/models/BaseItem.test.ts index f6c1ba29a6..542c2502dd 100644 --- a/packages/lib/models/BaseItem.test.ts +++ b/packages/lib/models/BaseItem.test.ts @@ -1,3 +1,4 @@ +import { Second } from '@joplin/utils/time'; import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, syncTargetId, synchronizerStart, msleep } from '../testing/test-utils'; import BaseItem from './BaseItem'; import Folder from './Folder'; @@ -42,6 +43,19 @@ describe('BaseItem', () => { expect(unserialized2.title).toBe(folder2.title); }); + it.each([ + '', + '\n\na\nb\nc\nç\nTest!\n Testing. \n', + 'Test! ☺', + 'Test! ☺\n\n\n', + ])('should not modify body when unserializing (body: %j)', async (body) => { + const note = await Note.save({ title: 'note1', body }); + + expect(await Note.unserialize(await Note.serialize(note))).toMatchObject({ + body, + }); + }); + it('should correctly unserialize note timestamps', async () => { const folder = await Folder.save({ title: 'folder' }); const note = await Note.save({ title: 'note', parent_id: folder.id }); @@ -55,6 +69,22 @@ describe('BaseItem', () => { expect(unserialized.user_updated_time).toEqual(note.user_updated_time); }); + it('should unserialize a very large note quickly', async () => { + const folder = await Folder.save({ title: 'folder' }); + const note = await Note.save({ title: 'note', parent_id: folder.id }); + + const serialized = await Note.serialize({ + ...note, + // 2 MiB + body: '\n.'.repeat(1 * 1024 * 1024), + }); + + const start = performance.now(); + await Note.unserialize(serialized); + // Locally, this passes in in < 2s, so 30s should be a safe upper bound. + expect(performance.now() - start).toBeLessThan(30 * Second); + }); + it('should serialize geolocation fields', async () => { const folder = await Folder.save({ title: 'folder' }); let note = await Note.save({ title: 'note', parent_id: folder.id }); diff --git a/packages/lib/models/BaseItem.ts b/packages/lib/models/BaseItem.ts index 02120eae73..27915129e0 100644 --- a/packages/lib/models/BaseItem.ts +++ b/packages/lib/models/BaseItem.ts @@ -578,28 +578,24 @@ export default class BaseItem extends BaseModel { const lines = content.split('\n'); // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied let output: any = {}; - let state = 'readingProps'; - const body: string[] = []; + let body: string[] = []; for (let i = lines.length - 1; i >= 0; i--) { let line = lines[i]; - if (state === 'readingProps') { - line = line.trim(); + line = line.trim(); - if (line === '') { - state = 'readingBody'; - continue; - } - - const p = line.indexOf(':'); - if (p < 0) throw new Error(`Invalid property format: ${line}: ${content}`); - const key = line.substr(0, p).trim(); - const value = line.substr(p + 1).trim(); - output[key] = value; - } else if (state === 'readingBody') { - body.splice(0, 0, line); + // Props are separated from the body by a single blank line + if (line === '') { + body = lines.slice(0, i); + break; } + + const p = line.indexOf(':'); + if (p < 0) throw new Error(`Invalid property format: ${line}: ${content}`); + const key = line.substr(0, p).trim(); + const value = line.substr(p + 1).trim(); + output[key] = value; } if (!output.type_) throw new Error(`Missing required property: type_: ${content}`);