diff --git a/libs/common/src/vault/search/ast.ts b/libs/common/src/vault/search/ast.ts index da35c94456e..62454d5b92d 100644 --- a/libs/common/src/vault/search/ast.ts +++ b/libs/common/src/vault/search/ast.ts @@ -6,6 +6,7 @@ export const AstNodeTypeNames = [ "or", "term", "fieldTerm", + "hasField", "hasAttachment", "hasUri", "hasFolder", @@ -30,6 +31,7 @@ export type AstNode = | Or | Term | FieldTerm + | HasField | HasAttachment | HasUri | HasFolder @@ -118,6 +120,14 @@ export function isFieldTerm(x: AstNode): x is FieldTerm { return x.type === "fieldTerm"; } +export type HasField = AstNodeBase & { + type: "hasField"; + field: string; +}; +export function isHasField(x: AstNode): x is HasField { + return x.type === "hasField"; +} + export type HasAttachment = AstNodeBase & { type: "hasAttachment"; }; diff --git a/libs/common/src/vault/search/bitwarden-query-grammar.ne b/libs/common/src/vault/search/bitwarden-query-grammar.ne index 395b3b601b8..173206fe503 100644 --- a/libs/common/src/vault/search/bitwarden-query-grammar.ne +++ b/libs/common/src/vault/search/bitwarden-query-grammar.ne @@ -47,6 +47,8 @@ TERM -> %string {% function(d) { const start = d[0].offset; const end = d[0].offset + d[0].value.length; return { type: 'term', value: d[0].value, start, end, length: d[0].value.length } } %} # specified field search term | %func_field %string %access %string {% function(d) { const start = d[0].offset; const end = d[3].offset + d[3].value.length; return { type: 'fieldTerm', field: d[1].value, term: d[3].value, start, end, length: end - start + 1 } } %} + # Has specified field as non-null + | %func_has %func_field %string {% function(d) { const start = d[0].offset; const end = d[2].offset + d[2].value.length; return { type: 'hasField', field: d[2].value, start, end, length: end - start + 1 } } %} # only items with attachments | %func_has "attachment" {% function(d) { const start = d[0].offset; const length = 14; return { type: 'hasAttachment', start, end: d[0].offset + length, length } } %} # only items with URIs diff --git a/libs/common/src/vault/search/bitwarden-query-grammar.ts b/libs/common/src/vault/search/bitwarden-query-grammar.ts index 04892fbd1d3..7a89c32c267 100644 --- a/libs/common/src/vault/search/bitwarden-query-grammar.ts +++ b/libs/common/src/vault/search/bitwarden-query-grammar.ts @@ -185,6 +185,19 @@ const grammar: Grammar = { }; }, }, + { + name: "TERM", + symbols: [ + lexer.has("func_has") ? { type: "func_has" } : func_has, + lexer.has("func_field") ? { type: "func_field" } : func_field, + lexer.has("string") ? { type: "string" } : string, + ], + postprocess: function (d) { + const start = d[0].offset; + const end = d[2].offset + d[2].value.length; + return { type: "hasField", field: d[2].value, start, end, length: end - start + 1 }; + }, + }, { name: "TERM", symbols: [lexer.has("func_has") ? { type: "func_has" } : func_has, { literal: "attachment" }], diff --git a/libs/common/src/vault/search/parse.ts b/libs/common/src/vault/search/parse.ts index 4353361f3e1..ca510ca9c45 100644 --- a/libs/common/src/vault/search/parse.ts +++ b/libs/common/src/vault/search/parse.ts @@ -14,6 +14,7 @@ import { isAnd, isFieldTerm, isHasAttachment, + isHasField, isHasFolder, isHasUri, isInCollection, @@ -118,6 +119,17 @@ function handleNode(node: AstNode): { filter: (context: SearchContext) => Search }; }, }; + } else if (isHasField(node)) { + const fieldTest = fieldNameToRegexTest(node.field); + return { + filter: (context) => ({ + ...context, + ciphers: context.ciphers.filter((cipher) => { + const foundValues = fieldValues(cipher, fieldTest); + return foundValues.fields.some((foundValue) => !!foundValue.value); + }), + }), + }; } else if (isHasAttachment(node)) { return { filter: (context) => ({