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
> [email protected]
> https://lists.nongnu.org/mailman/listinfo/nmh-workers
=----------------------
paul fox, [email protected] (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, [email protected], 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
[email protected]
https://lists.nongnu.org/mailman/listinfo/nmh-workers