From f0221aac0af6800aaee15acd6125588f607cb199 Mon Sep 17 00:00:00 2001 From: mahlon Date: Wed, 21 Jun 2023 04:14:54 +0000 Subject: [PATCH] Add the magic 'TO' header. Checks both To: and Cc: as an OR, without needing to specify both in separate rules. FossilOrigin-Name: e8cde0ba2d9b19089d80aeea455d6d6024cda477e206f567a379fd1b1d596815 --- config.yml | 17 ++++++------ src/lib/config.nim | 3 +++ src/lib/message.nim | 66 +++++++++++++++++++++++++++------------------ src/sieb.nim | 8 ++++-- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/config.yml b/config.yml index d02f020..8b84b38 100644 --- a/config.yml +++ b/config.yml @@ -3,13 +3,13 @@ # # Default, no logging. Relative to homedir. -#logfile: sieb.log +logfile: sieb.log ## Rules tried before global filtering early_rules: - headers: - to: mahlon@(laika|ravn) + TO: mahlon@(laika|ravn) received: .*sendgrid.* filter: - [ reformail, -A, "X-Sieb: This matched." ] @@ -17,7 +17,7 @@ early_rules: ## Filter message before additional rules. filter: - - [ reformail, -A, "X-Sieb: That shit totally matched" ] + - [ reformail, -A, "X-Sieb: Processed!" ] ## Ordered, top down, first match wins. ## Headers are lowercased. Multiple matches are AND'ed. @@ -25,12 +25,11 @@ filter: ## Delivery default is ~/Maildir, any set value is an auto-created maildir under ## that path. ## -# rules: -# - -# headers: -# x-what: pcre-matcher -# poonie: pcre-matcher -# deliver: .whatever +rules: + - + headers: + x-sieb: global + deliver: .whoas # # Magic "TO" which means To: OR Cc: # - diff --git a/src/lib/config.nim b/src/lib/config.nim index db8ad0d..40e7e0a 100644 --- a/src/lib/config.nim +++ b/src/lib/config.nim @@ -29,6 +29,9 @@ const CONFFILES = @[ ] +### FIXME: generate default config? + + ############################################################# # T Y P E S ############################################################# diff --git a/src/lib/message.nim b/src/lib/message.nim index 63322f7..4670e31 100644 --- a/src/lib/message.nim +++ b/src/lib/message.nim @@ -113,7 +113,7 @@ proc newMessage*( dir: Maildir ): Message = result.path = joinPath( result.dir.tmp, result.basename ) try: - debug "Opening new message at {result.path}".fmt + debug "Opening new message at:\n {result.path}".fmt result.stream = openFileStream( result.path, fmWrite ) result.path.setFilePermissions( OWNERFILEPERMS ) except CatchableError as err: @@ -130,8 +130,8 @@ proc save*( msg: Message, dir=msg.dir ) = ## maildir, but can be provided a different one. msg.stream.close let newpath = joinPath( dir.new, msg.basename ) - debug "Delivering message to {newpath}".fmt msg.path.moveFile( newpath ) + debug "Delivered message to:\n {newpath}".fmt msg.dir = dir msg.path = newpath @@ -139,8 +139,8 @@ proc save*( msg: Message, dir=msg.dir ) = proc delete*( msg: Message ) = ## Remove a message from disk. msg.stream.close - debug "Removing message at {msg.path}".fmt msg.path.removeFile + debug "Removed message at:\n {msg.path}".fmt msg.path = "" @@ -157,7 +157,7 @@ proc writeStdin*( msg: Message ): Message = msg.stream.write( buf ) msg.stream.flush msg.stream.close - debug "Wrote {total} bytes from stdin".fmt + debug "Wrote {total} bytes".fmt proc filter*( orig_msg: Message, cmd: seq[string] ): Message = @@ -249,44 +249,58 @@ proc parseHeaders*( msg: Message ) = ( header, value ) = ( matches[0].toLower, matches[1] ) -# FIXME: magic TO -proc walkRules*( msg: var Message, rules: seq[Rule], default: Maildir ): bool = +proc evalRules*( msg: var Message, rules: seq[Rule], default: Maildir ): bool = ## Evaluate each rule against the Message, returning true ## if there was a valid match found. - msg.parseHeaders result = false + msg.parseHeaders for rule in rules: var match = false + if rule.headers.len > 0: debug "Evaluating rule..." block thisRule: for header, regexp in rule.headers: let header_chk = header.toLower - var hmatch = false + var + hmatch = false + headerlbl = header_chk + recipients: seq[ string ] + values: seq[ string ] - debug " checking header \"{header}\"".fmt - if msg.headers.hasKey( header_chk ): - for val in msg.headers[ header_chk ]: - try: - hmatch = val.match( regexp.re ) - if hmatch: - debug " match on \"{regexp}\"".fmt - break # a single multi-header is sufficient - except RegexError as err: - debug " invalid regexp \"{regexp}\" ({err.msg}), skipping".fmt.replace( "\n", " " ) - break thisRule - - # Did any of the (possibly) multi-header values match? - if hmatch: - match = true - else: - debug " no match, skipping others" - break thisRule + # TO checks both To: and Cc: simultaneously. + # + if header == "TO": + headerlbl = "to|cc" + recipients = msg.headers.getOrDefault( "to", @[] ) & + msg.headers.getOrDefault( "cc", @[] ) + debug " checking header \"{headerlbl}\"".fmt + if recipients.len > 0: + values = recipients + elif msg.headers.hasKey( header_chk ): + values = msg.headers[ header_chk ] else: debug " nonexistent header, skipping others" break thisRule + for val in values: + try: + hmatch = val.match( regexp.re({reStudy,reIgnoreCase}) ) + if hmatch: + debug " match on \"{regexp}\"".fmt + break # a single multi-header is sufficient + except RegexError as err: + debug " invalid regexp \"{regexp}\" ({err.msg}), skipping".fmt.replace( "\n", " " ) + break thisRule + + # Did any of the (possibly) multi-header values match? + if hmatch: + match = true + else: + debug " no match for \"{regexp}\", skipping others".fmt + break thisRule + result = match diff --git a/src/sieb.nim b/src/sieb.nim index d487237..a81084d 100644 --- a/src/sieb.nim +++ b/src/sieb.nim @@ -9,6 +9,10 @@ import lib/util +# TODO: logfile +# TODO: timer/performance +# TODO: more performant debug + # Without this, we got nuthin'! if not existsEnv( "HOME" ): deferral "Unable to determine HOME from environment." @@ -25,14 +29,14 @@ var msg = default.newMessage.writeStdin # If there are "early rules", parse the message now and walk those. if conf.early_rules.len > 0: - if msg.walkRules( conf.early_rules, default ): quit( 0 ) + if msg.evalRules( conf.early_rules, default ): quit( 0 ) # Apply any configured global filtering. for filter in conf.filter: msg = msg.filter( filter ) # Walk the rules, and if nothing hits, deliver to fallthrough. if conf.rules.len > 0: - if not msg.walkRules( conf.rules, default ): msg.save + if not msg.evalRules( conf.rules, default ): msg.save else: msg.save