paul vixie wrote: > Ken Hornstein wrote: > >> Well, it depends on the message. Sometimes I get a message with 20 photos > >> attached. I just want to be able to easily go from one to the next > >> without > >> having to type their part number. > > > > But ... what's wrong with doing something like: > > > > mhstore -type image > > > > Then you can deal with each of those photos as a seperate file. > > ui wise, i don't want files. i want to act on the attachments (view > them, in this case) and then leave them stored as attachments (without > having to delete any temporary files.) > > i don't know exactly how to match mime to the simplicity of show(1), and > i've been violently repulsed any time i tried to use mhshow(1), but i > think what i want would look a bit like whatnow(1) is to send(1). > (for many years i used "send -wa" because my sendmail.cf was a hack, > then i used "push" when i stopped being fascinated by SMTP traces.) > > the command set i'd suggest for whatnowmime(1) would include things like: > > ls | dir > shows the full message structure (like in ftp) > cd x.y.z > go to part x subpart y subsubpart z > next > go to next attachment at same level (like in gdb) > step > go to next attachment at or below the current one (like gdb) > prev > opposite of next > back > opposite of step > up > go up one level > view > launch an external viewer suitable for this mimetype > show > do the best you can in inline ascii for this mimetype > save [-r] [x.y.z] > save the current part (default) or part x subpart y subsubpart z, maybe > recursively (-r) > quit > exit from mhshow(1)
i wrote a script for myself (and my wife) that does much of this (but by no means all). the script i use to actually read a mail message invokes mhshow for the main parts, and then invokes my "mhdisp" script. mhdisp does the work of wandering through the parts of the message in order (as well as letting the user select a specific part), and either does the obvious thing (i.e., displays a text part) or asks the user what to do (as in the case of a pdf -- i use different readers at different times), or lets me save a part as a file, and prompts me with a default filename. it's made life with MH and MIME much more pleasant in our household. there's a bit of a chewing-gum-and-string aspect to it all -- for instance, to force some actions from mhshow i create custom profiles on the fly for it to use for that invocation. it was easier to do that than create a single profile that always did the right thing in all cases. other than being customized mainly for the mail i get (i never receive audio files, so there's no custom handler for those, for instance), the biggest missing piece currently is a fully recursive handler for message/rfc822 parts. i end up having to save those to a file, and then run mhdisp on them separately. i don't see anything terribly difficult about converting what i've done as a looping "what do you want to do next?" shell into separate commandline commands, except for the preservation of state between each such command, and the necessary reparsing of the original message for each one. all of the currently hard-coded actions would need to be sensibly configurable, too, probably with something different than the current mhn.defaults. so in principle what paul is asking for is SMOP (simple matter of programming), but it's definitely a project. (i just reread paul's message, above, and realize he wasn't describing separate commandline commands, but the sub-commands in a whatnowmime shell. that's more similar to what i've done than i realized.) i've attached mhdisp in case someone wants to see it. it's never been out in the wild before, so it might depend horribly on my environment -- no promises. paul > > -- > P Vixie > > _______________________________________________ > Nmh-workers mailing list > Nmh-workers@nongnu.org > https://lists.nongnu.org/mailman/listinfo/nmh-workers =---------------------- paul fox, p...@foxharp.boston.ma.us (arlington, ma, where it's 38.8 degrees)
#!/bin/bash # mhdisp (for 'dispatch', 'display', 'dispense', take your pick) # examines the mime attachments in an MH mail message, lists them for # the user, and lets one view or save the images/documents/whatever, # in some cases allowing selection of the application to use # (xpdf/okular/acroread), and allowing editing of the filename to be # used if saving. # # on invocation, text parts can be excluded from the list (on the # assumption that they've already be seen via a default invocation of # mhshow), just "interesting" attachments can be included, or all # attachments. ("uninteresting" attachments are currently just # various digital signatures.) # # the default response to the prompt asking for which part to # display attempts to be clever, and will auto-increment through the # available parts as each is dealt with. # # the default response to all prompts is editable using readline. # # paul fox, p...@foxharp.boston.ma.us, spring 2014 me=${0##*/} oursttysettings=$(stty -g) usage() { exec >&2 echo "usage: $me [options] <message-spec>" echo " -f|-file <pathname>" echo " will process the given file instead of using message-spec." echo " -s|-show {nontext,most,all}" echo " 'nontext' will only list interesting non-text parts" echo " 'most' adds parts of type text to the list (this the default)" echo " 'all' shows all parts (but not multipart wrappers)" exit 1 } #exec 2>/tmp/mhdisp.log #chmod a+rw /tmp/mhdisp.log #set -x Mail=$(mhpath +) parse_message() { local msg part_id therest local j partmap=( ) j=0 while read msg part_id therest do : debug: LINE: $msg $part_id $f1 $f2 $therest if [ $msg != '-' ] then doing_msg=$msg part_ids[$j]=0 split_ct_size_descrip partmap+=( [$part_id]=$j ) elif [ $part_id != '-' ] # have a new part to process then (( j++ )) : debug: j is $j, part_id is $part_id # and start gathering info on the new part part_ids[$j]=$part_id partmap+=( [$part_id]=$j ) split_ct_size_descrip else k="${therest%%=*}" v="${therest#*=}" case $k in name) name[$j]="$v" ;; filename) filename[$j]="$v" ;; esac fi done (( j++ )) numparts=$j } p_printf() { printf -v pt "$@" part_text+="$pt" } listparts() { part_text=; shown_parts=0 skipped_always_parts=0 skipped_text_parts=0 i=0 while (( i < numparts )) # i++ at bottom of loop do : debug: $showmode-${c_t[$i]} shown[$i]=; case $showmode-${c_t[$i]} in *-multipart/*) ;; show*-application/*-signature*) (( skipped_always_parts++ )) ;; show_non_text-text/*) (( skipped_text_parts++ )) ;; *) (( shown_parts++ )) shown[$i]=1 p_printf "%4s)" "${part_ids[$i]}" ct=${c_t[$i]} case $ct in image/*) ct=image ;; audio/*) ct=audio ;; application/octet-stream) ct=data ;; application/*) ct=${ct#*/} ;; text/html) ct=html ;; text/plain) ct=text ;; esac p_printf "\t%s" $ct if [ ! "${size[$i]}" ] then p_printf "\tMALFORMED-SIZE" elif [ "${size[$i]}" = 0 ] then p_printf "\tempty" else p_printf "\t%s" ${size[$i]} fi test "${name[$i]}" && p_printf "\t%s" \""${name[$i]}"\" if [ "${filename[$i]}" != "${name[$i]}" ] then test "${filename[$i]}" && p_printf " %s" "${filename[$i]}" fi if [ "${description[$i]}" != "${name[$i]}" -a \ "${description[$i]}" != "${filename[$i]}" ] then test "${description[$i]}" && p_printf " %s" "${description[$i]}" fi # record first text part, in case nothing's more interesting : debug: set first_part to $i ${first_part:=$i} case ${c_t[$i]} in show*-application/*-signature) ;; show_non_text-text/*) ;; *) case ${c_t[$i]} in text/*) ;; *) # default part will be first non-text part : debug: def_part becomes ${def_part:=$i} ;; esac ;; esac p_printf "\n" esac (( i++ )) done # if no non-text part, use first text part as default : debug: def_part becomes ${def_part:=$first_part} test "$part_text" || return 1 echo : $skipped_always_parts-$skipped_text_parts case $skipped_always_parts-$skipped_text_parts in 0-0) echo "All parts of message $doing_msg:" ;; 0-*) echo "Non-text parts of message $doing_msg:" ;; *-0) echo "Interesting parts of message $doing_msg:" ;; *-*) echo "Interesting non-text parts of message $doing_msg:" ;; esac printf "%s" "$part_text" } ask() { immed=; if [ "$1" = -i ] then immed="-n 1" shift fi echo -n "${1}? [N/y] " read $immed a case $a in [Yy]*) return 0 ;; *) return 1 ;; esac } savepart() { local i="$1" local part_id="$2" local msg="$3" savename="${filename[$i]}" # try filename : ${savename:="${name[$i]}"} # if still null, try name : ${savename:="${description[$i]}"} # if still null, try description if [ "$savename" ] then # reduce runs of, and convert, spaces savename="${savename// / }" savename="${savename// /_}" savename=/tmp/$savename else if [ "${c_t[$i]}" = "text/plain" ] then suffix=.txt elif [ "${c_t[$i]}" = "message/rfc822" ] then suffix=.mail else # pull a likely suffix out of mhn.defaults suffix=$(sed -n \ -e "s;mhshow-suffix-${c_t[$i]}: \(.*\);\1;p" \ $(mhparam etcdir)/mhn.defaults ) fi savename=/tmp/messagepart${suffix} fi while : do read -e -p "Filename: (leave empty, or 'q' to quit): " -i "$savename" savename 2>&1 case $savename in ""|q) echo No save; return ;; esac test -e "$savename" || break ask "Overwrite" && break done ct=${c_t[$i]} tmpmhn=/tmp/mhn.$(whoami).tmp echo "mhshow-show-$ct: cat '%f'" >$tmpmhn MHSHOW=$tmpmhn \ mhshow -noheader -form mhl.null -part $part_id $msg >$savename && echo -e '\nSaved.' rm -f $tmpmhn } showpdf() { local ct part_id msg choices prog ct="$1" part_id="$2" msg="$3" choices="xpdf, okular, or acroread? " read -e -p "$choices" ans 2>&1 case $ans in o*) prog=okular ;; a*) prog=acroread ;; *|x*) prog=xpdf ;; esac tmpmhn=/tmp/mhn.$(whoami).tmp echo "mhshow-show-$ct: $prog '%f'" >$tmpmhn MHSHOW=$tmpmhn \ mhshow -form mhl.null -part $part_id $msg | cat rm -f $tmpmhn } showimage() { local ct part_id msg choices prog ct="$1" part_id="$2" msg="$3" prog=xv #choices="xpdf, okular, or acroread? " #read -e -p "$choices" ans 2>&1 #case $ans in #o*) prog=okular ;; #a*) prog=acroread ;; #*|x*) prog=xpdf ;; #esac tmpmhn=/tmp/mhn.$(whoami).tmp echo "mhshow-show-$ct: $prog '%f'" >$tmpmhn MHSHOW=$tmpmhn \ mhshow -form mhl.null -part $part_id $msg | cat rm -f $tmpmhn } do_show() { local action="$1" local part_id="$2" local msg="$3" i=${partmap[$part_id]} case $action in next) ;; save) savepart $i $part_id "$msg" ;; view) # use trap to catch ^C. this lets viewer programs be killed # without dying ourselves. delay the trap until just before # invoking the viewer, however. ct=${c_t[$i]} case $ct in application/octet-stream|data) echo "Uh oh. Cannot determine what application to use to view" echo "the contents of part $part_id." filename="${filename[$i]}" : ${filename:="${name[$i]}"} # if still null, try name : ${filename:="${description[$i]}"} # if still null, try description case "$filename" in *.pdf|*.PDF) maybepdf=true ;; *.jpeg|*.jpg|*.PNG|*.png) maybeimage=true ;; esac if [ "$maybepdf" ] then echo read -e -i y -p "It may be a pdf. Try that? [Y/n] " ans 2>&1 case $ans in [yY]*) trap "" SIGINT showpdf "$ct" "$part_id" "$msg" trap - SIGINT return ;; esac fi if [ "$maybeimage" ] then echo read -e -i y -p "It may be an image. Try that? [Y/n] " ans 2>&1 case $ans in [yY]*) trap "" SIGINT showimage "$ct" "$part_id" "$msg" trap - SIGINT return ;; esac fi read -e -i y -p \ "Perhaps it's just text, and 'less' will work. Try that? [Y/n] "\ ans 2>&1 case $ans in [yY]*) less $part_id $filename tmpmhn=/tmp/mhn.$(whoami).tmp echo "mhshow-show-application/octet-stream: %pless '%f'" >$tmpmhn MHSHOW=$tmpmhn \ mhshow -form mhl.null -part $part_id $msg rm -f $tmpmhn return ;; esac ;; image/*|video/*|audio/*) trap "" SIGINT MHSHOW=$Mail/mhn.all \ mhshow -form mhl.null -part $part_id $msg | cat trap - SIGINT ;; application/pdf) trap "" SIGINT showpdf "$ct" "$part_id" "$msg" trap - SIGINT ;; application/msword) choices="antiword, or libreoffice? " read -e -p "$choices" ans 2>&1 case $ans in a*) prog=antiword; pager=less ;; l*) prog=libreoffice; pager=cat ;; esac tmpmhn=/tmp/mhn.$(whoami).tmp echo "mhshow-show-$ct: $prog '%f'" >$tmpmhn trap "" SIGINT MHSHOW=$tmpmhn \ mhshow -form mhl.null -part $part_id $msg | $pager trap - SIGINT rm -f $tmpmhn ;; message/rfc822) tmpmhn=/tmp/mhn.$(whoami).tmp echo "mhshow-show-$ct: mhshow -file '%f'" >$tmpmhn echo "mhshow-show-text/html: %p/usr/bin/elinks -force-html '%F' -dump" >>$tmpmhn trap "" SIGINT MHSHOW=$tmpmhn \ mhshow -form mhl.null -part $part_id $msg trap - SIGINT ;; *) #mhshow -form mhl.null -part $part_id $msg | less trap "" SIGINT mhshow -part $part_id $msg | less trap - SIGINT ;; esac ;; # untested below text) MHSHOW=$Mail/mhn.block_html \ mhshow -form mhl.null -part $part_id $msg | less ;; html) MHSHOW=$Mail/mhn.html_only \ mhshow -form mhl.null -part $part_id $msg ;; browser) #tmpfile=/tmp/$(whoami)-mh.html #echo "mhshow-show-text/html: cat '%f' >$tmpfile" >$Mail/mhn.tmp #MHSHOW=$Mail/mhn.tmp chromium-browser %s >/dev/null 2>&1 & #rm $Mail/mhn.tmp ;; graphical) ;; esac } prompt() { local act=v; # default to 's'tore for application/*, but 'v'iew for all others case "$def_part" in "") act=; ;; *) case ${c_t[$def_part]} in application/*) act=s ;; esac ;; esac echo # this trap wasn't necessary in the ubuntu 12.04 version of bash -- # ^C caused an exit by default. but bash 4.3.11(1) requires it. trap "stty $oursttysettings; exit" SIGINT txt="Enter <partno>[sv] or h/? or q: " default="${part_ids[${def_part}]}${act}" read -e -p "$txt" -i "$default" ans 2>&1 test "$ans" && partcmd="$ans" } interact_help() { cat <<-EOF Enter a part number followed by 's' or 'v' to save or view the part's content, or 'q' to quit. Only the last part number and command are considered, so there's no need to erase the answer offered by default. i.e.,: 1.2v will view part 1.2 1.2v2s will store part 2 1.2vq will quit 1.2v3 will view part 3 (because view is the default if there's no command present. EOF read -p "Enter to continue..." a } interact() { local part_id action i suspect local msg="$1" suspect=$(MHSHOW=$Mail/mhn.html_dump_wide \ mhshow -noconcat -type text/html "$@" | egrep ' [[:digit:]]+ of [[:digit:]]+ File\(s\)') if [ "$(mhlist -noheaders -type text/html)" ] then suspect="$suspect$(MHSHOW=$Mail/mhn.html_dump_wide \ mhshow -noconcat -type text/html "$@" | egrep '^ *Attachment.* from .*View')" fi if [ "$suspect" ] then echo echo '***WARNING!!! Message may contain web-based so-called "attachments"!!!'*** refno=$(MHSHOW=$Mail/mhn.html_dump_wide \ mhshow -noconcat -type text/html "$@" | sed -n -e 's/[[:space:]]*Attachment.* from .*\[\([[:digit:]]\)\]View .*/\1/p') refurl=$(MHSHOW=$Mail/mhn.html_dump_wide \ mhshow -noconcat -type text/html "$@" | sed -n -e "s/^[[:space:]]*${refno}\. \(.*\)/\1/p") if [ "$refurl" ] then echo echo Attachments might be found here: echo "$refurl" else echo "Hmmm... can't find url for the web-based attachments." fi fi while : do # listparts refers to the parsed info built by parse_message listparts || exit 0 # don't ask questions if stdout is redirected, just list. test -t 1 || return 0 # sets $response prompt $showmode action=view case $partcmd in *q) break ;; *a) showmode=show_most ;; *A) showmode=all ;; *h|*\?|*H) interact_help ;; [0-9]*) case $partcmd in *s) action=save ;; *v) action=view ;; *n) action=next ;; #*t) action=text ;; #*h) action=html ;; #*b) action=browser ;; #*g) action=graphical ;; esac re='([0-9.]+)[a-z]*$' # extract the last part number [[ "$partcmd" =~ $re ]] part_id=${BASH_REMATCH[1]} # first subexpression match do_show $action $part_id "$msg" # if we've just shown the only part, then quit if [ "$shown_parts" = 1 ] then break fi : debug: $part_id ${partmap[$part_id]} # loop through all the visible parts i=${partmap[$part_id]} while : do (( ++i >= numparts )) && i=1 test "${shown[$i]}" && break done def_part=$i : debug: def_part became $def_part ;; *) : big default; interact_help break ;; esac done } do_message() { local msg="$1" : ============ initial msg is "$msg" declare -A partmap declare -a part_ids name filename ct size description # parse_message reads processed mhlist info from stdin parse_message # but interact reads the original message from "$msg", # which might be "-file path" exec 0<&3 # restore the original stdin : ============ later msg is "$msg" interact "$msg" } massage_mhlist() { # use sed to make mhlist output more readily parseable: # - the first three expressions ensure the first two columns always # have some content (namely, at least a '-' character). # (the first takes care of an empty second column. the second # takes care of an empty first column, helped by the third, which # kicks in if the msg number is wider than 4 digits.) # - we don't care about the boundary marker or the disposition, # and disposition is malformed (no '=') anyway, so delete them. # - truncation by mhlist corrupts some content-types, so fix them. # - finally, we strip double-quotes from name=value parameters. expand | sed -e 's/^ / - /' \ -e 's/^\(....\) /\1 - /' \ -e 's/^\([0-9]\+\) /\1 - /' \ -e '/ boundary="/d' \ -e '/ disposition "/d' \ -e 's;application/pkcs7-signat;application/pkcs7-signature;' \ -e 's;application/pgp-signatur;application/pgp-signature;' \ -e 's;application/x-zip-compre;application/x-zip-compressed;' \ -e 's/\( [[:alnum:]]\+\)="\([^"]\+\)"$/\1=\2/' } main() { local arg usefile fname declare -a args showmode=show_most # choices: "show_non_text", "show_most", "all" while : do case $1 in -s|-show|--show) case $2 in n*|nontext) showmode=show_non_text ;; m*|most) showmode=show_most ;; a*|all) showmode=all ;; *) usage ;; esac shift 2 ;; -f|-file|--file) usefile=true fname="$2" : <"$fname" || exit 1 args[0]="-file $fname" shift 2 break ;; -*) usage ;; *) break ;; esac done # we need to be able to do user interaction from within do_message(), # which is getting stdin from a pipe. so save stdin on fd 3. exec 3<&0 if [ "$usefile" ] then : elif ! args=( $(pick -noseq "${@:-cur}" 2>/dev/null) ) then echo $me: no such message >&2 exit 0 fi for arg in "${args[@]}" do mhlist -noheaders -verbose -disposition $arg | massage_mhlist | do_message "$arg" || exit done } # odd positioning of this routine in the file is purely because the # syntax colorizer in older versions of vile doesn't grok '<<<', and # the coloring is messed up for the rest of the edit buffer after it's # seen. split_ct_size_descrip() { # wow: doing it in one line, like this, is *hugely* expensive for # some reason: # read c_t[$j] size[$j] description[$j] <<< "$therest" # so split up the read from the array assignments: read c s d <<< "$therest" c_t[$j]="$c" size[$j]="$s" description[$j]="$d" } main "$@"
_______________________________________________ Nmh-workers mailing list Nmh-workers@nongnu.org https://lists.nongnu.org/mailman/listinfo/nmh-workers