diff --git a/src/services/search/expressions/note_content_protected_fulltext.js b/src/services/search/expressions/note_content_protected_fulltext.js new file mode 100644 index 000000000..74633d5b0 --- /dev/null +++ b/src/services/search/expressions/note_content_protected_fulltext.js @@ -0,0 +1,54 @@ +"use strict"; + +const Expression = require('./expression'); +const NoteSet = require('../note_set'); +const log = require('../../log'); +const noteCache = require('../../note_cache/note_cache'); +const protectedSessionService = require('../../protected_session'); + +class NoteContentProtectedFulltextExp extends Expression { + constructor(operator, tokens) { + super(); + + if (operator !== '*=*') { + throw new Error(`Note content can be searched only with *=* operator`); + } + + this.tokens = tokens; + } + + execute(inputNoteSet) { + const resultNoteSet = new NoteSet(); + + if (!protectedSessionService.isProtectedSessionAvailable()) { + return resultNoteSet; + } + + const sql = require('../../sql'); + + for (let {noteId, content} of sql.iterateRows(`SELECT noteId, content FROM notes JOIN note_contents USING (noteId) WHERE isDeleted = 0 AND isProtected = 1`)) { + + try { + content = protectedSessionService.decryptString(content); + } + catch (e) { + log.info('Cannot decrypt content of note', noteId); + continue; + } + + content = content.toLowerCase(); + + if (this.tokens.find(token => !content.includes(token))) { + continue; + } + + if (inputNoteSet.hasNoteId(noteId) && noteId in noteCache.notes) { + resultNoteSet.add(noteCache.notes[noteId]); + } + } + + return resultNoteSet; + } +} + +module.exports = NoteContentProtectedFulltextExp; diff --git a/src/services/search/expressions/note_content_fulltext.js b/src/services/search/expressions/note_content_unprotected_fulltext.js similarity index 64% rename from src/services/search/expressions/note_content_fulltext.js rename to src/services/search/expressions/note_content_unprotected_fulltext.js index 71894ee9b..9fa45cd7f 100644 --- a/src/services/search/expressions/note_content_fulltext.js +++ b/src/services/search/expressions/note_content_unprotected_fulltext.js @@ -5,22 +5,27 @@ const NoteSet = require('../note_set'); const noteCache = require('../../note_cache/note_cache'); const utils = require('../../utils'); -class NoteContentFulltextExp extends Expression { +class NoteContentUnprotectedFulltextExp extends Expression { constructor(operator, tokens) { super(); - this.likePrefix = ["*=*", "*="].includes(operator) ? "%" : ""; - this.likeSuffix = ["*=*", "=*"].includes(operator) ? "%" : ""; + if (operator !== '*=*') { + throw new Error(`Note content can be searched only with *=* operator`); + } this.tokens = tokens; } execute(inputNoteSet) { const resultNoteSet = new NoteSet(); - const wheres = this.tokens.map(token => "note_contents.content LIKE " + utils.prepareSqlForLike(this.likePrefix, token, this.likeSuffix)); + const wheres = this.tokens.map(token => "note_contents.content LIKE " + utils.prepareSqlForLike('%', token, '%')); const sql = require('../../sql'); - +console.log(` + SELECT notes.noteId + FROM notes + JOIN note_contents ON notes.noteId = note_contents.noteId + WHERE isDeleted = 0 AND isProtected = 0 AND ${wheres.join(' AND ')}`); const noteIds = sql.getColumn(` SELECT notes.noteId FROM notes @@ -37,4 +42,4 @@ class NoteContentFulltextExp extends Expression { } } -module.exports = NoteContentFulltextExp; +module.exports = NoteContentUnprotectedFulltextExp; diff --git a/src/services/search/parse_filters.js b/src/services/search/parse_filters.js index 472a938d0..e21b67c09 100644 --- a/src/services/search/parse_filters.js +++ b/src/services/search/parse_filters.js @@ -1,9 +1,4 @@ const dayjs = require("dayjs"); -const AndExp = require('./expressions/and'); -const OrExp = require('./expressions/or'); -const NotExp = require('./expressions/not'); -const NoteCacheFulltextExp = require('./expressions/note_cache_fulltext'); -const NoteContentFulltextExp = require('./expressions/note_content_fulltext'); const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}\p{Number}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([^\s=*"]+|"[^"]+"))?/igu; const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i; diff --git a/src/services/search/parser.js b/src/services/search/parser.js index 64102b6b5..c45636594 100644 --- a/src/services/search/parser.js +++ b/src/services/search/parser.js @@ -11,7 +11,8 @@ const PropertyComparisonExp = require('./expressions/property_comparison'); const AttributeExistsExp = require('./expressions/attribute_exists'); const LabelComparisonExp = require('./expressions/label_comparison'); const NoteCacheFulltextExp = require('./expressions/note_cache_fulltext'); -const NoteContentFulltextExp = require('./expressions/note_content_fulltext'); +const NoteContentProtectedFulltextExp = require('./expressions/note_content_protected_fulltext'); +const NoteContentUnprotectedFulltextExp = require('./expressions/note_content_unprotected_fulltext'); const OrderByAndLimitExp = require('./expressions/order_by_and_limit'); const comparatorBuilder = require('./comparator_builder'); const ValueExtractor = require('./value_extractor'); @@ -25,7 +26,8 @@ function getFulltext(tokens, parsingContext) { else if (parsingContext.includeNoteContent) { return new OrExp([ new NoteCacheFulltextExp(tokens), - new NoteContentFulltextExp('*=*', tokens) + new NoteContentProtectedFulltextExp('*=*', tokens), + new NoteContentUnprotectedFulltextExp('*=*', tokens) ]); } else { @@ -67,7 +69,10 @@ function getExpression(tokens, parsingContext, level = 0) { i++; - return new NoteContentFulltextExp(operator, [tokens[i]]); + return new OrExp([ + NoteContentUnprotectedFulltextExp(operator, [tokens[i]]), + NoteContentProtectedFulltextExp(operator, [tokens[i]]) + ]); } if (tokens[i] === 'parents') {