mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Fixed handling of max item size for encrypted items
This commit is contained in:
parent
d129744138
commit
112157e33f
@ -12,6 +12,12 @@ function hexPad(s: string, length: number) {
|
||||
return padLeft(s, length, '0');
|
||||
}
|
||||
|
||||
export function isValidHeaderIdentifier(id: string, ignoreTooLongLength = false) {
|
||||
if (!id) return false;
|
||||
if (!ignoreTooLongLength && id.length !== 5) return false;
|
||||
return /JED\d\d/.test(id);
|
||||
}
|
||||
|
||||
export default class EncryptionService {
|
||||
|
||||
public static instance_: EncryptionService = null;
|
||||
@ -657,7 +663,7 @@ export default class EncryptionService {
|
||||
|
||||
async decodeHeaderSource_(source: any) {
|
||||
const identifier = await source.read(5);
|
||||
if (!this.isValidHeaderIdentifier(identifier)) throw new JoplinError(`Invalid encryption identifier. Data is not actually encrypted? ID was: ${identifier}`, 'invalidIdentifier');
|
||||
if (!isValidHeaderIdentifier(identifier)) throw new JoplinError(`Invalid encryption identifier. Data is not actually encrypted? ID was: ${identifier}`, 'invalidIdentifier');
|
||||
const mdSizeHex = await source.read(6);
|
||||
const mdSize = parseInt(mdSizeHex, 16);
|
||||
if (isNaN(mdSize) || !mdSize) throw new Error(`Invalid header metadata size: ${mdSizeHex}`);
|
||||
@ -697,12 +703,6 @@ export default class EncryptionService {
|
||||
return output;
|
||||
}
|
||||
|
||||
isValidHeaderIdentifier(id: string, ignoreTooLongLength = false) {
|
||||
if (!id) return false;
|
||||
if (!ignoreTooLongLength && id.length !== 5) return false;
|
||||
return /JED\d\d/.test(id);
|
||||
}
|
||||
|
||||
isValidEncryptionMethod(method: number) {
|
||||
return [EncryptionService.METHOD_SJCL, EncryptionService.METHOD_SJCL_1A, EncryptionService.METHOD_SJCL_2, EncryptionService.METHOD_SJCL_3, EncryptionService.METHOD_SJCL_4].indexOf(method) >= 0;
|
||||
}
|
||||
@ -711,13 +711,13 @@ export default class EncryptionService {
|
||||
if (!item) throw new Error('No item');
|
||||
const ItemClass = BaseItem.itemClass(item);
|
||||
if (!ItemClass.encryptionSupported()) return false;
|
||||
return item.encryption_applied && this.isValidHeaderIdentifier(item.encryption_cipher_text, true);
|
||||
return item.encryption_applied && isValidHeaderIdentifier(item.encryption_cipher_text, true);
|
||||
}
|
||||
|
||||
async fileIsEncrypted(path: string) {
|
||||
const handle = await this.fsDriver().open(path, 'r');
|
||||
const headerIdentifier = await this.fsDriver().readFileChunk(handle, 5, 'ascii');
|
||||
await this.fsDriver().close(handle);
|
||||
return this.isValidHeaderIdentifier(headerIdentifier);
|
||||
return isValidHeaderIdentifier(headerIdentifier);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { ErrorUnprocessableEntity, ErrorForbidden, ErrorPayloadTooLarge, ErrorNo
|
||||
import { ModelType } from '@joplin/lib/BaseModel';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { formatBytes, MB } from '../utils/bytes';
|
||||
import { itemIsEncrypted } from '../utils/joplinUtils';
|
||||
|
||||
export enum AccountType {
|
||||
Default = 0,
|
||||
@ -147,7 +148,8 @@ export default class UserModel extends BaseModel<User> {
|
||||
// If the item is encrypted, we apply a multipler because encrypted
|
||||
// items can be much larger (seems to be up to twice the size but for
|
||||
// safety let's go with 2.2).
|
||||
const maxSize = user.max_item_size * (item.jop_encryption_applied ? 2.2 : 1);
|
||||
|
||||
const maxSize = user.max_item_size * (itemIsEncrypted(item) ? 2.2 : 1);
|
||||
if (maxSize && buffer.byteLength > maxSize) {
|
||||
const itemTitle = joplinItem ? joplinItem.title || '' : '';
|
||||
const isNote = joplinItem && joplinItem.type_ === ModelType.Note;
|
||||
|
24
packages/server/src/utils/joplinUtils.test.ts
Normal file
24
packages/server/src/utils/joplinUtils.test.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Item } from '../db';
|
||||
import { itemIsEncrypted } from './joplinUtils';
|
||||
import { expectThrow } from './testing/testUtils';
|
||||
|
||||
describe('joplinUtils', function() {
|
||||
|
||||
it('should check if an item is encrypted', async function() {
|
||||
type TestCase = [boolean, Item];
|
||||
|
||||
const testCases: TestCase[] = [
|
||||
[true, { jop_encryption_applied: 1 }],
|
||||
[false, { jop_encryption_applied: 0 }],
|
||||
[true, { content: Buffer.from('JED01blablablabla', 'utf8') }],
|
||||
[false, { content: Buffer.from('plain text', 'utf8') }],
|
||||
];
|
||||
|
||||
for (const [expected, input] of testCases) {
|
||||
expect(itemIsEncrypted(input)).toBe(expected);
|
||||
}
|
||||
|
||||
await expectThrow(async () => itemIsEncrypted({ name: 'missing props' }));
|
||||
});
|
||||
|
||||
});
|
@ -18,6 +18,7 @@ import { formatDateTime } from './time';
|
||||
import { ErrorNotFound } from './errors';
|
||||
import { MarkupToHtml } from '@joplin/renderer';
|
||||
import { OptionsResourceModel } from '@joplin/renderer/MarkupToHtml';
|
||||
import { isValidHeaderIdentifier } from '@joplin/lib/services/EncryptionService';
|
||||
const { DatabaseDriverNode } = require('@joplin/lib/database-driver-node.js');
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
@ -221,6 +222,13 @@ async function renderNote(share: Share, note: NoteEntity, resourceInfos: Resourc
|
||||
};
|
||||
}
|
||||
|
||||
export function itemIsEncrypted(item: Item): boolean {
|
||||
if ('jop_encryption_applied' in item) return !!item.jop_encryption_applied;
|
||||
if (!('content' in item)) throw new Error('Cannot check encryption - item is missing both "content" and "jop_encryption_applied" property');
|
||||
const header = item.content.toString('utf8', 0, 5);
|
||||
return isValidHeaderIdentifier(header);
|
||||
}
|
||||
|
||||
export async function renderItem(userId: Uuid, item: Item, share: Share, query: Record<string, any>): Promise<FileViewerResponse> {
|
||||
const rootNote: NoteEntity = models_.item().itemToJoplinItem(item); // await this.unserializeItem(content);
|
||||
const linkedItemInfos: LinkedItemInfos = await noteLinkedItemInfos(userId, models_.item(), rootNote);
|
||||
|
Loading…
Reference in New Issue
Block a user