# pm-jamime-save.rc -- save message's MIME attachement (one file) to a file # $Id: pm-jamime-save.rc,v 2.9 2004/09/21 14:50:57 jaalto Exp $ # # File id # # .Copyright (C) 1998-2004 Jari Aalto # .$Keywords: procmail, subroutine, mime $ # # This code is free software in terms of GNU Gen. pub. Lic. v2 or later # Refer to http://www.gnu.org/copyleft/gpl.html # # Documentation # # This module saves _one_ simple file attachment (MIME) from he # message. The message must define following MIME headers. If # "filename=" does not exists, then the message will not be # handled. # # Mime-Version: # Content-Type: # Content-Disposition: attachment; filename="file.txt" # # The last line can also be in separate line, provided that it # is intended according to standard rules: # # Mime-Version: # Content-Type: # Content-Disposition: attachment; # filename="file.txt" # # Procmail is not very suitable for saving MIME attachments and # you should not think that this the right tool for you. # If you receive anything more than 1 attachment, this recipe # does nothing, because that's out of our league and you need some # more heavy weight mime tools. E.g. Perl CPAN has MIME libraries. # # _Note_: When the attachment is in the body, it is simply written # to a disk and the location in message is replaced with test: # # Extracted to file:/users/foo/junk/file.txt.1998-03-30 # # The existing mime headers that surrounded the attachment are # untouched, so don't try to press your Mail Agents MIME buttons # at that point any more. There is no such file in that spot # if you set `JA_MIME_SAVE_DEL' to `yes'). # # Required settings # # PMSRC must point to source directory of procmail code. This # subroutine includes library: # # o pm-javar.rc # o pm-jamime.rc # o pm-jamime-decode.rc # o pm-jadate.rc (which will call other pm-jadate*.rc files) # # Call arguments (variables to set before calling) # # o `JA_MIME_SAVE_DIR', point this to directory where you # want to store attachments. # o `JA_MIME_SAVE_DECODE', set this to "yes", if you want that # attachment is decoded before written to disk. This usually # opens quoted printable or base64 encoding. # o `JA_MIME_SAVE_DEL', set this to "yes", if you want to remove # the attachment from the body of the message after it has # been filed. Be vary careful if you use this option. If you # keep backup cache of incoming mail, then you might try "yes". # o `JA_MIME_SAVE_OVERWRITE', set this to "yes" if it's okay to # overwrite to an existing filename found from attachment. # If you get periodic attachments always with same name, then # you would want to set this to yes. # # Core dump note # # Because procmail uses LINEBUF when filtering messages, a core # dump may happen if the attachment being filtered is bigger than # the LINEBUF. The current setting accepts 524K attachments, but if # you expect to get bigger than that, you want to increase # `JA_MIME_SAVE_LINEBUF'. # # Possible conflict with your awk # # Awk is used because it is much more system load friendly than perl. # If you see an error message in the log file saying that awk failed: # # procmail: Executing awk, # ... # procmail: Error while writing to "awk" # procmail: Rescue of unfiltered data succeeded # # it means that the system's standard awk doesn't support the # variable passing syntax. To verify that this is the case, run # following test: # # % awk '{print VAR; exit}' VAR="value" /etc/passwd # # The proper awk should print "value". If not, then see if you have # `nawk' or `gawk' in your system, which should understand the # variable passing syntax. To change the AWK, you need to set # following variable somewhere at the top of your *.procmailrc* # # AWK = "gawk" # if that works better than standard "awk" # # Return values (none) # # Change Log (none) # .................................................... &initialising ... dummy = " ======================================================================== pm-jamime-save.rc: init:" :0 * ! WSPC ?? ( ) { INCLUDERC = $PMSRC/pm-javar.rc } :0 * ! MIME_VER ?? [0-9] { INCLUDERC = $PMSRC/pm-jamime.rc } :0 # if has no value, set it *$ ! LINEBUF ?? $d { LINEBUF = 1024 } MimeSaveLinebuf = $LINEBUF # .......................................................... &public ... # User configurable sections JA_MIME_SAVE_DIR = ${JA_MIME_SAVE_DIR:-"$HOME"} JA_MIME_SAVE_DECODE = ${JA_MIME_SAVE_DECODE:-"no"} JA_MIME_SAVE_DEL = ${JA_MIME_SAVE_DEL:-"no"} JA_MIME_SAVE_LINEBUF = ${JA_MIME_SAVE_LINEBUF:-524280} JA_MIME_SAVE_OVERWRITE = ${JA_MIME_SAVE_OVERWRITE:-"no"} # ........................................................... &do-it ... dummy = "pm-jamime-save.rc: HEADER; $MIME_H_ATTACHMENT" :0 * MIME_H_ATTACHMENT ?? [a-z] { file = $JA_MIME_SAVE_DIR/$MIME_H_ATTACHMENT :0 * JA_MIME_SAVE_DECODE ?? yes { # decode regardless of body content JA_MIME_DECODE_REGEXP = ".*" INCLUDERC = $RC_MIME_DECODE } # ..................................................... filename ... :0 * JA_MIME_SAVE_OVERWRITE ?? no *$ ? $IS_EXIST $file { :0 *$ ! YYYY ?? $d { INCLUDERC = $RC_DATE } file = "$file.$YYYY-$MM-$DD" # Still not unique? :0 *$ ? $IS_EXIST $file { :0 fhw | $FORMAIL -I "X-MimeSave-Error: (file exists) $file" # kill variable to prevent next recipe from running file } } # .................................................... write out ... :0 * file ?? [a-z] { :0 bwc: # do not modify body * JA_MIME_SAVE_DEL ?? no | $CAT > $file # Write out the attachment and replace body # with reference to the file. :0 E { LINEBUF = $JA_MIME_SAVE_LINEBUF :0 fbw: | ( $CAT > $file; echo "Saved to $file" ) LINEBUF = $MimeSaveLinebuf } } } dummy = "pm-jamime-save.rc: BODY check" :0 E * MIME_B_ATTACHMENT ?? [a-z] * MIME_B_ATTACHMENT_FILE_COUNT ?? ^^1^^ { dummy = "pm-jamime-save.rc: BODY ENTERED: can do only some limited operations." file = $JA_MIME_SAVE_DIR/$MIME_B_ATTACHMENT # Try to locate the header where the content type is defined for # file attachment. Note: there is no typo in the regexp, the # caret(^) matches newline. See http://pm-doc.sourceforge.net/ # # Content-Type: application/octet-stream # Content-Disposition: attachment; filename="file.txt" # Content-Transfer-Encoding: 7bit dummy = "pm-jamime-save.rc: Check contentType BODY 1" :0 *$ B ?? ^Content-Type:$s+\/.*\ ^Content-Disposition:.*($MIME_B_ATTACHMENT\ |^$s+.*$MIME_B_ATTACHMENT) * MATCH ?? \/.* { contentType = $MATCH } dummy = "pm-jamime-save.rc: Check contentType BODY 2" :0 E *$ B ?? ^Content-Type:$s+\/.*\ ^Content-Transfer-Encoding:.*\ ^Content-Disposition:.*($MIME_B_ATTACHMENT\ |^$s+.*$MIME_B_ATTACHMENT) * MATCH ?? \/.* { contentType = $MATCH # The last "Content-Disposition" match allows two cases # # 1. Content-Disposition: attachment; filename="file.txt" # # 2. Content-Disposition: attachment; # filename="file.txt" :0 *$ B ?? ^Content-Type:.*\ ^Content-Transfer-Encoding:$s\/.*\ ^Content-Disposition:.*($MIME_B_ATTACHMENT\ |^$s+.*$MIME_B_ATTACHMENT) * MATCH ?? \/.* { contentEncoding = $MATCH } } # We expect that the headers come in this order. # If they don't, then we can't know the encoding. # We don't even try anything else: Procmail is not the right tool # for complete MIME handling. dummy = "pm-jamime-save.rc: Check contentEncoding BODY" :0 * contentEncoding ?? ^^^^ *$ B ?? ^Content-Type:.*\ ^Content-Disposition:.*\ ^Content-Transfer-Encoding:$s+\/.* { contentEncoding = $MATCH } :0 E * contentEncoding ?? ^^^^ *$ B ?? ^Content-Type:.*\ ^Content-Transfer-Encoding:$s+\/.* { contentEncoding = $MATCH } :0 * contentEncoding ?? ^^^^ *$ B ?? ^Content-Type:.*\ ^Content-Transfer-Encoding:$s+\/.* { contentEncoding = $MATCH } # ............................................... fix mixed-part ... # In most typical message, sender "says" something in text/plain and # then adds an atatchement # # But, due to order of the MIME headers we may have picked the # text/plain. Change it to application/octet-stream if found. # # Mime-Version: 1.0 # Content-Type: multipart/mixed; boundary="----------118D218634724256" # # ------------118D218634724256 # Content-Type: text/plain; charset=us-ascii # Content-Transfer-Encoding: 7bit # # # ------------118D218634724256 # Content-Type: application/octet-stream; name="Ass_rake.dbf" # Content-Transfer-Encoding: base64 # Content-Disposition: attachment; filename="Ass_rake.dbf" dummy = "pm-jamime-save.rc: Check contentType ($contentType) text/plain => application" :0 * contentType ?? text/ *$ B ?? ^Content-Type:$s+\/application.* { contentType = $MATCH dummy = "pm-jamime-save.rc: finding Transfer-Encoding for application/*" :0 *$ B ?? ^Content-Type:$s+application.*\ ^Content-Transfer-Encoding:$s\/.* { contentEncoding = $MATCH } } # ............................................... check filename ... # Change filename if there is already that file dummy = "pm-jamime-save.rc: check filename" :0 * JA_MIME_SAVE_OVERWRITE ?? no *$ ? $IS_EXIST $file { :0 *$ ! YYYY ?? $d { INCLUDERC = $RC_DATE } file = "$file.$YYYY-$MM-$DD" # Still not unique? :0 *$ ? $IS_EXIST $file { :0 fhw | $FORMAIL -I "X-MimeSave-Error: (file exists) $file" # Kill variable to prevent next recipe from running file } } # ........................................................ &save ... # That nice backslash lines in the AWK are automatically handled # by Emacs procmail mode (tinyprocmail.el) # See http://tiny-tools.sourceforge.net/ # 1) Locate the positions where the attachment starts # 2) When found, start looking for empty line which endS the mime # headers. GO is set to 1, when attachment starts # 3) Attachment ends when mime boundary is hit. Actually # it ended one line before that, which was a empty line. MimeSaveShellmetas = $SHELLMETAS LINEBUF = $JA_MIME_SAVE_LINEBUF SHELLMETAS :0 fbw * COUNT ?? [1-9] * file ?? [a-z] | $AWK \ ' { BEGIN found = 0 } \ { \ if ( match($0,MATCH) > 0 ) \ { \ found++; \ } \ if ( (found = $COUNT - 1) && match($0, "^[ \t]*$") ) \ { \ go = 1; \ next; \ } \ if ( match($0, MIME) ) \ { \ go = 0; \ } \ if ( go ) \ { \ if ( DELETE == "yes") \ { \ if ( urlFlag == 0 ) \ { \ urlFlag = 1; \ printf "\extracted to file:%s\n\n", FILE; \ } \ } \ else \ { \ print; \ } \ print $0 >> FILE; \ next; \ } \ print; \ } \ ' DELETE="$JA_MIME_SAVE_DEL" \ MIME="$MIME_BOUNDARY" \ COUNT="$MIME_BOUNDARY_COUNT" \ MATCH="Disposition:.*$MIME_B_ATTACHMENT" \ FILE="$file" LINEBUF = $MimeSaveLinebuf SHELLMETAS = $MimeSaveShellmetas # ....................................................... decode ... dummy = "pm-jamime-save.rc: Encoding($contentEncoding) of $file" :0 *$ file ?? $a *$ contentEncoding ?? $a { file2 = "$file.raw"; :0 wc * contentEncoding ?? base64 | $CAT $file | $MIME_BIN_64 > $file2; $MV $file2 $file :0 Ewc * contentEncoding ?? quoted-printable | $CAT $file | $MIME_BIN_QP > $file2; $MV $file2 $file } } dummy = "pm-jamime-save.rc: end:" # End of file pm-jamime-save.rc