From 105b21d9f7580402fabbf3c848f8a8c3d942d710 Mon Sep 17 00:00:00 2001 From: mahlon Date: Tue, 20 Jun 2023 11:26:41 +0000 Subject: [PATCH] Got message filtering working. FossilOrigin-Name: 47c9a66b9c547f9af30bebd226b757d475f616b976baf315faae653a01cdf97a --- config.yml | 3 +- src/lib/config.nim | 8 +- src/lib/{maildir.nim => message.nim} | 109 +++++++++++++++++++++------ src/sieb.nim | 22 +++--- 4 files changed, 105 insertions(+), 37 deletions(-) rename src/lib/{maildir.nim => message.nim} (54%) diff --git a/config.yml b/config.yml index 95dd2bf..8a69bcb 100644 --- a/config.yml +++ b/config.yml @@ -7,7 +7,8 @@ ## Filter message before rules pre_filter: - - bogofilter + - 'reformail -A "X-Sieb: Pre-filtered!"' +# - bogofilter ## Filter message after rules #post_filter: diff --git a/src/lib/config.nim b/src/lib/config.nim index 30ad634..905b353 100644 --- a/src/lib/config.nim +++ b/src/lib/config.nim @@ -41,10 +41,10 @@ type # Typed configuration file layout for YAML loading. Config* = object - logfile {.defaultVal: "".}: string - pre_filter {.defaultVal: @[]}: seq[string] - post_filter {.defaultVal: @[]}: seq[string] - rules {.defaultVal: @[]}: seq[rule] + logfile* {.defaultVal: "".}: string + pre_filter* {.defaultVal: @[]}: seq[string] + post_filter* {.defaultVal: @[]}: seq[string] + rules* {.defaultVal: @[]}: seq[rule] ############################################################# diff --git a/src/lib/maildir.nim b/src/lib/message.nim similarity index 54% rename from src/lib/maildir.nim rename to src/lib/message.nim index cd08383..ab90b55 100644 --- a/src/lib/maildir.nim +++ b/src/lib/message.nim @@ -27,6 +27,8 @@ import const OWNERDIRPERMS = { fpUserExec, fpUserWrite, fpUserRead } OWNERFILEPERMS = { fpUserWrite, fpUserRead } + # FILTERPROCOPTS = { poUsePath } + FILTERPROCOPTS = { poUsePath, poEvalCommand } BUFSIZE = 8192 # reading and writing buffer size @@ -52,20 +54,28 @@ type Message* = ref object stream: FileStream +# Count messages generated during a run. +var msgcount = 0 + + ############################################################# # M E T H O D S ############################################################# +#------------------------------------------------------------ +# Maildir +#------------------------------------------------------------ + proc newMaildir*( path: string ): Maildir = ## Create and return a new Maildir object, making it on-disk if necessary. result = new Maildir result.path = path - result.cur = path & "/cur" - result.new = path & "/new" - result.tmp = path & "/tmp" + result.cur = joinPath( path, "cur" ) + result.new = joinPath( path, "new" ) + result.tmp = joinPath( path, "tmp" ) if not dirExists( path ): - debug "Creating new maildir at {path}.".fmt + debug "Creating new maildir at {path}".fmt try: for p in [ result.path, result.cur, result.new, result.tmp ]: p.createDir @@ -80,6 +90,10 @@ proc subDir*( dir: Maildir, path: string ): Maildir = result = newMaildir( dir.path & "/" & path ) +#------------------------------------------------------------ +# Message +#------------------------------------------------------------ + proc newMessage*( dir: Maildir ): Message = ## Create and return a Message - an open FileStream under a specific Maildir ## (in tmp) @@ -89,25 +103,31 @@ proc newMessage*( dir: Maildir ): Message = var hostname = newString(256) discard getHostname( cstring(hostname), cint(256) ) + msgcount = msgcount + 1 result.dir = dir - result.basename = $now.toUnixFloat() & '.' & $getCurrentProcessID() & '.' & $hostname - result.path = result.dir.tmp & "/" & result.basename + result.basename = $now.toUnixFloat & '.' & $getCurrentProcessID() & '.' & $msgcount & '.' & $hostname + result.path = joinPath( result.dir.tmp, result.basename ) result.headers = @[] try: - debug "Opening new message at {result.path}.".fmt + debug "Opening new message at {result.path}".fmt result.stream = openFileStream( result.path, fmWrite ) result.path.setFilePermissions( OWNERFILEPERMS ) except CatchableError as err: - deferral "Unable to write file {result.path}: {err.msg}".fmt + deferral "Unable to write file {result.path} {err.msg}".fmt + + +proc open*( msg: Message ) = + ## Open (or re-open) a Message file stream. + msg.stream = msg.path.openFileStream proc save*( msg: Message, dir=msg.dir ) = ## Move the message from tmp to new. Defaults to its current ## maildir, but can be provided a different one. - msg.stream.close() - let newpath = dir.new & "/" & msg.basename - debug "Moving message to {newpath}.".fmt + msg.stream.close + let newpath = joinPath( dir.new, msg.basename ) + debug "Delivering message to {newpath}".fmt msg.path.moveFile( newpath ) msg.dir = dir msg.path = newpath @@ -115,8 +135,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.stream.close + debug "Removing message at {msg.path}".fmt msg.path.removeFile msg.path = "" @@ -124,7 +144,7 @@ proc delete*( msg: Message ) = proc writeStdin*( msg: Message ) = ## Streams stdin to the message file, returning how ## many bytes were written. - let input = stdin.newFileStream() + let input = stdin.newFileStream var buf = input.readStr( BUFSIZE ) var total = buf.len msg.stream.write( buf ) @@ -133,20 +153,65 @@ proc writeStdin*( msg: Message ) = buf = input.readStr( BUFSIZE ) total = total + buf.len msg.stream.write( buf ) - msg.stream.flush() - msg.stream.close() + msg.stream.flush + msg.stream.close debug "Wrote {total} bytes from stdin".fmt -# FIXME: filter through external program -# - open new message -# - stream current message to new -# - remove current -# - return new -# +proc filter*( orig_msg: Message, cmd: string ): Message = + ## Filter message content through an external program, + ## returning a new Message if successful. + try: + var buf: string + + # let command = cmd.split + # let process = command[0].startProcess( + # args = command[1..(command.len-1)], + # options = FILTERPROCOPTS + # ) + + let process = cmd.startProcess( options = FILTERPROCOPTS ) + + # Read from the original message, write to the filter + # process in chunks. + # + orig_msg.open + buf = orig_msg.stream.readStr( BUFSIZE ) + process.inputStream.write( buf ) + process.inputStream.flush + while buf != "" and buf.len == BUFSIZE: + buf = orig_msg.stream.readStr( BUFSIZE ) + process.inputStream.write( buf ) + process.inputStream.flush + + # Read from the filter process until EOF, send to the + # new message in chunks. + process.inputStream.close + let new_msg = newMessage( orig_msg.dir ) + buf = process.outputStream.readStr( BUFSIZE ) + new_msg.stream.write( buf ) + new_msg.stream.flush + while buf != "" and buf.len == BUFSIZE: + buf = process.outputStream.readStr( BUFSIZE ) + new_msg.stream.write( buf ) + new_msg.stream.flush + + let exitcode = process.waitForExit + debug "Filter exited: {exitcode}".fmt + process.close + orig_msg.delete + result = new_msg + + except OSError as err: + debug "Unable to filter message: {err.msg}".fmt + result = orig_msg + + + # FIXME: header parsing to tuples # - open file # - skip lines that don't match headers # - unwrap multiline headers # - store header, add value to seq of strings + diff --git a/src/sieb.nim b/src/sieb.nim index 4fbe979..9b69cad 100644 --- a/src/sieb.nim +++ b/src/sieb.nim @@ -1,14 +1,16 @@ # vim: set et nosta sw=4 ts=4 : import - std/os + std/os, + std/strformat import lib/config, - lib/maildir, + lib/message, lib/util +# Without this, we got nuthin'! if not existsEnv( "HOME" ): deferral "Unable to determine HOME from environment." @@ -16,13 +18,13 @@ let home = getHomeDir() opts = parse_cmdline() conf = get_config( opts.config ) - default = newMaildir( home & "Maildir" ) - -let dest = default.subDir( ".wooo" ) - -let msg = default.newMessage -msg.writeStdin() -# msg.filter() -msg.save( dest ) + default = newMaildir( joinPath( home, "Maildir" ) ) +# let dest = default.subDir( "woo" ) +var msg = default.newMessage +msg.writeStdin +for filter in conf.pre_filter: + debug "Running pre-filter: {filter}".fmt + msg = msg.filter( filter ) +msg.save()