#!/bin/sh

######################################################################
#
# SENDJPMAIL - Send an E-mail Which Has UTF-8 Characters
#
# USAGE: sendjpmail <mailfile>
#
# FORMAT OF MAILFILE:
#   Mailfile file has to conform the following rules:
#   * Header part is requires. ("Subject:", "From:", "To:", "Cc:"...)
#   * Body part is following the head part lines with a empty line.
#     |<header line #1>
#     |<header line #2>
#     |       :
#     |<header line #n>
#     |                     <- a empty line inserted here
#     |<body line #1>
#     |<body line #2>
#     |       :
#   * Do not contain 0x80-0xFF characters any header part lines except
#     "Subject:", "From:", "To:", "Cc:", "Bcc:" and "Reply-To:"
#   * If the following headers is omitted, inserted by this function.
#     "Content-Type:", "Content-Transfer-Encoding:", "MIME-Version:"
#
# Written by Shell-Shoccar Japan (@shellshoccarjpn) on 2020-05-06
#
# This is a public-domain software (CC0). It means that all of the
# people can use this for any purposes with no restrictions at all.
# By the way, We are fed up with the side effects which are brought
# about by the major licenses.
#
# The latest version is distributed at the following page.
# https://github.com/ShellShoccar-jpn/misc-tools
#
######################################################################


######################################################################
# Initial configuration
######################################################################

# === Initialize shell environment ===================================
set -u
umask 0022
export LC_ALL=C
PATH="/usr/sbin:/sbin:/usr/local/sbin${PATH+:}${PATH-}"
PATH="$(command -p getconf PATH 2>/dev/null):$PATH"
case $PATH in :*) PATH=${PATH#?};; esac
export UNIX_STD=2003  # to make HP-UX conform to POSIX

# === Usage printing function ========================================
print_usage_and_exit () {
  cat <<-USAGE 1>&2
	Usage   : ${0##*/} <mailfile>
	Args    : <mailfile> ... Text data as an e-mail
	Version : 2020-05-06 22:42:19 JST
	          (POSIX Bourne Shell/POSIX commands/sendmail command/UTF-8)
	USAGE
  exit 1
}


######################################################################
# Prepare for the Main Routine
######################################################################

# === Define some chrs. to escape some special chrs. temporarily =====
CR=$( printf '\r'   )
LFs=$(printf '\\\n_'); LFs=${LFs%_}
ACK=$(printf '\006' )
NAK=$(printf '\025' )
TAB=$(printf '\t'   )

# === FUNC : which command also works on Just a POSIX env. ($1:command name)
which which >/dev/null 2>&1 || {
  which() {
    command -v "$1" 2>/dev/null | grep '^/' || {
      echo 'which: not found' 1>&2 && (exit 1)
    }
  }
}

# === FUNC : UTF-8 string cutter within required bytes ===============
cutstr_by_bytes () {
  awk -v maxbytes=$1 '
    BEGIN {
      OFS = ""; ORS = "";
      while (getline str) {
        bytes_thisline = 0;
        len = length(str);
        for (pos=1; pos<=len; pos+=bytes_a_letter) {
          c = substr(str, pos, 1);
          if      (c < "\200") {bytes_a_letter=1;}
          else if (c < "\300") {bytes_a_letter=1;} # invalid pattern!
          else if (c < "\340") {bytes_a_letter=2;}
          else if (c < "\360") {bytes_a_letter=3;}
          else if (c < "\370") {bytes_a_letter=4;}
          else if (c < "\374") {bytes_a_letter=5;}
          else if (c < "\376") {bytes_a_letter=6;}
          else                   {bytes_a_letter=1;} # invalid pattern!
          if (bytes_thisline+bytes_a_letter > maxbytes) {
            print "\n", substr(str, pos, bytes_a_letter);
            bytes_thisline  = bytes_a_letter;
          } else                               {
            print     substr(str, pos, bytes_a_letter);
            bytes_thisline += bytes_a_letter;
          }
        }
        print "\n";
      }
    }'
}

# === FUNC : base64 command also works on Just a POSIX env. ==========
if   CMD_BASE64=$(which base64 2>/dev/null)      ; then
  base64 () { "$CMD_BASE64" ${1:-}; }
elif uuencode -m dummy </dev/null >/dev/null 2>&1; then
  CMD_UUENCODE=$(which uuencode 2>/dev/null)
  base64 () {
    w=''
    case "${1:-}" in
      --wrap=*) printf '%s\n' "$1" | grep -q '^--wrap=[0-9]\{1,\}$' && {
                  w=${1#--wrap=}
                }
                ;;
    esac
    case "$w" in '') w=76;; esac
    "$CMD_UUENCODE" -m dummy                     |
    sed '1d;$d'                                  |
    case $w in                                   #
      76) cat                                 ;; #
       0) tr -d '\n'                 ; echo '';; #
       *) tr -d '\n' | fold -w $width; echo '';; #
    esac
  }
else
  base64 () {
    w=''
    case "${1:-}" in
      --wrap=*) printf '%s\n' "$1" | grep -q '^--wrap=[0-9]\{1,\}$' && {
                  w=${1#--wrap=}
                }
                ;;
    esac
    case "$w" in '') w=76;; esac
    od -A n -t x1 -v                                                         |
    awk 'BEGIN{OFS=""; ORS="";                                               #
               x2o["0"]="0000"; x2o["1"]="0001"; x2o["2"]="0010";            #
               x2o["3"]="0011"; x2o["4"]="0100"; x2o["5"]="0101";            #
               x2o["6"]="0110"; x2o["7"]="0111"; x2o["8"]="1000";            #
               x2o["9"]="1001"; x2o["a"]="1010"; x2o["b"]="1011";            #
               x2o["c"]="1100"; x2o["d"]="1101"; x2o["e"]="1110";            #
               x2o["f"]="1111";                                              #
               x2o["A"]="1010"; x2o["B"]="1011"; x2o["C"]="1100";            #
               x2o["D"]="1101"; x2o["E"]="1110"; x2o["F"]="1111";         }  #
         {     l=length($0);                                                 #
               for(i=1;i<=l;i++){print x2o[substr($0,i,1)];}                 #
               printf("\n");                                              }' |
    awk 'BEGIN{s="";                                                      }  #
         {     buf=buf $0;                                                   #
               l=length(buf);                                                #
               if(l<6){next;}                                                #
               u=int(l/6)*6;                                                 #
               for(p=1;p<u;p+=6){print substr(buf,p,6);}                     #
               buf=substr(buf,p);                                         }  #
         END  {if(length(buf)>0){print substr(buf "00000",1,6);}          }' |
    awk 'BEGIN{ORS=""; w='$w';                                               #
               o2b6["000000"]="A"; o2b6["000001"]="B"; o2b6["000010"]="C";   #
               o2b6["000011"]="D"; o2b6["000100"]="E"; o2b6["000101"]="F";   #
               o2b6["000110"]="G"; o2b6["000111"]="H"; o2b6["001000"]="I";   #
               o2b6["001001"]="J"; o2b6["001010"]="K"; o2b6["001011"]="L";   #
               o2b6["001100"]="M"; o2b6["001101"]="N"; o2b6["001110"]="O";   #
               o2b6["001111"]="P"; o2b6["010000"]="Q"; o2b6["010001"]="R";   #
               o2b6["010010"]="S"; o2b6["010011"]="T"; o2b6["010100"]="U";   #
               o2b6["010101"]="V"; o2b6["010110"]="W"; o2b6["010111"]="X";   #
               o2b6["011000"]="Y"; o2b6["011001"]="Z"; o2b6["011010"]="a";   #
               o2b6["011011"]="b"; o2b6["011100"]="c"; o2b6["011101"]="d";   #
               o2b6["011110"]="e"; o2b6["011111"]="f"; o2b6["100000"]="g";   #
               o2b6["100001"]="h"; o2b6["100010"]="i"; o2b6["100011"]="j";   #
               o2b6["100100"]="k"; o2b6["100101"]="l"; o2b6["100110"]="m";   #
               o2b6["100111"]="n"; o2b6["101000"]="o"; o2b6["101001"]="p";   #
               o2b6["101010"]="q"; o2b6["101011"]="r"; o2b6["101100"]="s";   #
               o2b6["101101"]="t"; o2b6["101110"]="u"; o2b6["101111"]="v";   #
               o2b6["110000"]="w"; o2b6["110001"]="x"; o2b6["110010"]="y";   #
               o2b6["110011"]="z"; o2b6["110100"]="0"; o2b6["110101"]="1";   #
               o2b6["110110"]="2"; o2b6["110111"]="3"; o2b6["111000"]="4";   #
               o2b6["111001"]="5"; o2b6["111010"]="6"; o2b6["111011"]="7";   #
               o2b6["111100"]="8"; o2b6["111101"]="9"; o2b6["111110"]="+";   #
               o2b6["111111"]="/";                                           #
               if (getline) {print o2b6[$0];n=1;}                         }  #
         n==w {printf("\n")  ; n=0;                                       }  #
         {     print o2b6[$0]; n++;                                       }  #
         END  {if(NR>0){printf("%s\n",substr("===",1,(4-(NR%4))%4));}     }'
  }
fi

# === Make sure that sendmail command exists =========================
CMD_SENDMAIL=$(which sendmail 2>/dev/null)
case "$CMD_SENDMAIL" in
  '') echo "${0##*/}: sendmail command is not found" 1>&2; exit 1;;
esac


######################################################################
# Parse Arguments
######################################################################

# === Get the options and the filepath ===============================
# --- initialize option parameters -----------------------------------
file=''
#
# --- get them -------------------------------------------------------
case $# in
     0) :                   ;;
  [12]) file=$1             ;;
        # supporting $#==2 is for compatibility with the previous version
        # even if given, ignored
     *) print_usage_and_exit;;
esac

# === Validate the arguments =========================================
if   [ "_$file" = '_'                ] ||
     [ "_$file" = '_-'               ] ||
     [ "_$file" = '_/dev/stdin'      ] ||
     [ "_$file" = '_/dev/fd/0'       ] ||
     [ "_$file" = '_/proc/self/fd/0' ]  ; then
  file=''
elif [ -f "$file"                    ] ||
     [ -c "$file"                    ] ||
     [ -p "$file"                    ]  ; then
  [ -r "$file" ] || error_exit 1 'Cannot open the file: '"$file"
else
  print_usage_and_exit
fi
case "$file" in ''|-|/*|./*|../*) :;; *) file="./$file";; esac


######################################################################
# Main Routine
######################################################################

# ===== Convert the header ===========================================
ct=0   # be set to 1 if the mail text has "Content-Type:" header
ct_m=0 # be set to 1 if the mail text has "Content-Type: multipart/*" header
cte=0  # be set to 1 if the mail text has "Content-Transfer-Encoding:" header
mv=0   # be set to 1 if the mail text has "MIME-Version:" header
IFS='
'
mode=''
case "$file" in '') :;; *) exec <"$file";; esac
s=`f=''                                                                        #
   while read -r line; do                                                      #
     case "$line" in                                                           #
       '')                                mode='endh'               ;;         #
       To:*|Cc:*|Bcc:*|Reply-To:*)        mode='addr'               ;;         #
       From:*)                            mode='addrF'              ;;         #
       Subject:*)                         mode='subj'               ;;         #
       "$TAB"*|' '*)                                                ;;         #
       Content-Type:*multipart/*)         mode='othr';  ct=1; ct_m=1;;         #
       Content-Type:*)                    mode='othr';  ct=1        ;;         #
       Content-Transfer-Encoding:*)       mode='othr'; cte=1        ;;         #
       MIME-Version:*)                    mode='othr';  mv=1        ;;         #
       *)                                 mode='othr'               ;;         #
     esac                                                                      #
     #                                                                         #
     case "$mode" in                                                           #
       # --- Subject header processing ----------------------------------      #
       'subj')                                                                 #
         # 0) check whether converting is required or not                      #
         printf '%s\n' "$line" | grep "^[${TAB} -~]\\{1,\\}\$" && continue     #
         # 1) print the label of the header (header name)                      #
         printf '%s\n' "$line"                 |                               #
         sed 's/^\([^:]*:['"$TAB"' ]*\).*/\1/' |                               #
         tr -d '\n'                                                            #
         # 2) convert and print header of the after part                       #
         printf '%s\n' "$line"     |                                           #
         sed "s/^[^:]*:[$TAB ]*//" |                                           #
         cutstr_by_bytes 42        |                                           #
         while read -r str; do     #                                           #
           printf '%s' "${str}" |  #                                           #
           base64 --wrap=0      |  #                                           #
           grep '^'                #                                           #
         done                      |                                           #
         sed 's/.*/=?UTF-8?B?&?=/' |                                           #
         sed '1!s/^/'"$TAB"'/'                                                 #
         ;;                                                                    #
       # --- address header (From, To, Cc, Bcc, Reply-To) processing ----      #
       'addr'*)                                                                #
         # 0) print the label of the header (header name)                      #
         hdrlabel=$(printf '%s\n' "$line"                 |                    #
                    sed 's/^\([^:]*:['"$TAB"' ]*\).*/\1/' )                    #
         printf '%s' "$hdrlabel"                                               #
         # 1) check whether converting is required or not                      #
         h=$(printf '%s\n' "$line"         |                                   #
             sed 's/^[^:]*:['"$TAB"' ]*//' |                                   #
             grep "^[${TAB} -~]\\{1,\\}\$" )                                   #
         case "$h" in                                                          #
           '') :                                                               #
               ;;                                                              #
            *) printf '%s\n' "$h";                                             #
               case "$mode" in 'addrF') f="$f$ACK$h";; esac                    #
               continue                                                        #
               ;;                                                              #
         esac                                                                  #
         # 2) convert and print the follwing part in the header                #
         h=$(indent=''                                                  #      #
             printf '%s\n' "$line"                                      |      #
             sed "s/^[^:]*:[$TAB ]*//"                                  |      #
             sed "s/\"/$LFs\"$LFs/g"                                    |      #
             awk 'BEGIN{attr = "RAW:";                               }  #      #
                  /^"/ {attr = (attr=="RAW:") ? "ESC:" : "RAW:";next;}  #      #
                       {print attr $0;                               }' |      #
             sed "/^ESC:/s/,/$ACK/g"                                    |      #
             sed "/^ESC:/s/;/$NAK/g"                                    |      #
             sed 's/^....//'                                            |      #
             tr '\n' '"'                                                |      #
             grep ''                                                    |      #
             sed 's/"$//'                                               |      #
             sed "s/\\([,;]\\)/\\1$LFs/g"                               |      #
             sed 's/^['"$TAB"' ]\{1,\}//'                               |      #
             grep -v '^$'                                               |      #
             tr "$ACK$NAK" ',;'                                         |      #
             while read -r str1; do                                     #      #
               printf "$indent"; indent=${TAB}                          #      #
               printf '%s\n' "$str1" |                                  #      #
               grep -E "^[$TAB ]*<?[!-?A-~]+@[A-Za-z0-9][A-Za-z0-9.-]+[A-Za-z0-9]>?[$TAB ]*,?[$TAB ]*\$" && continue # #
               printf '%s\n' "$str1"                            |       #      #
               sed "s/[$TAB ]*<\\{0,1\\}[!-?A-~]\\{1,\\}@[A-Za-z0-9][A-Za-z0-9.-]\\{1,\\}[A-Za-z0-9]>\\{0,1\\}[$TAB ]*,\\{0,1\\}[$TAB ]*\$//" | # #
               sed 's/^[^'"$TAB"' ]*"\(.*\)"[^'"$TAB"' ]*$/\1/' |       #      #
               cutstr_by_bytes 30                               |       #      #
               while read -r str2; do                           #       #      #
                 printf '%s' "${str2}" |                        #       #      #
                 base64 --wrap=0       |                        #       #      #
                 grep ''                                        #       #      #
               done                                             |       #      #
               sed 's/.*/=?UTF-8?B?&?=/'                        |       #      #
               sed '1!s/^/'"$TAB"'/'                            |       #      #
               tr '\n' "$ACK"                                   |       #      #
               grep ''                                          |       #      #
               sed "s/$ACK\$//"                                 |       #      #
               tr -d '\n'                                       |       #      #
               tr "$ACK" '\n'                                           #      #
               printf '%s\n' "$str1" |                                  #      #
               sed "s/\\([$TAB ]*<\\{0,1\\}[!-?A-~]\\{1,\\}@[A-Za-z0-9][A-Za-z0-9.-]\\{1,\\}[A-Za-z0-9]>\\{0,1\\}[$TAB ]*,\\{0,1\\}[$TAB ]*\\)\$/$ACK\\1/" | # #
               sed "s/^.*$ACK//"                                        #      #
             done                                                       )      #
         printf '%s\n' "$h"                                                    #
         case "$mode" in 'addrF') f="$f$ACK$h";; esac                          #
         ;;                                                                    #
       # --- another header processing ----------------------------------      #
       'othr')                                                                 #
         printf '%s\n' "$line"                                                 #
         ;;                                                                    #
       # --- routine after the end of the header part -------------------      #
       'endh')                                                                 #
         # 1) print headers which are printed yet                              #
         case $ct  in                                                          #
           '0') echo 'Content-Type: text/plain;charset="UTF-8"';;              #
         esac                                                                  #
         case "$((cte+ct_m))" in                                               #
           '0') echo 'Content-Transfer-Encoding: base64'       ;;              #
         esac                                                                  #
         case $mv  in                                                          #
           '0') echo 'MIME-Version: 1.0'                       ;;              #
         esac                                                                  #
         # 2) print a blank line, which means a boundary between header & body #
         echo                                                                  #
         # 3) Exit the header processing loop                                  #
         break                                                                 #
         ;;                                                                    #
       # --- when a invalid header line is given ------------------------      #
       '')                                                                     #
         printf '%s\n' "${0##*/}: Invalid header part" 1>&2                    #
         cat >/dev/nulll                                                       #
         CMD_SENDMAIL=':'                                                      #
         ;;                                                                    #
     esac                                                                      #
   done                                                                        #
   # --- attach "From:" header value after the header section with only 1 line #
   printf '_%s\n' "$f" | tr '\n' "$ACK"                                        #
   # --- return the variables "$ct_m" and "$cte"                               #
   printf ":$ct_m $cte"                                                        #
   `
ct_m=${s##*:}; ct_m=${ct_m% *}
cte=${s##* }
s=${s%:*}

# ===== Send the mail ================================================
# --- take out the "From:" header value  -----------------------------
addr=$(printf '%s\n' "$s" |
       tail -n 1          |
       sed 's/^_//'       |
       sed "s/[$TAB ]*<\\{0,1\\}\\([!-?A-~]\\{1,\\}@[A-Za-z0-9][A-Za-z0-9.-]\\{1,\\}[A-Za-z0-9]\\)>\\{0,1\\}.*\$/$ACK\\1/" |
       sed "s/^.*$ACK//"  )
name=$(printf '%s\n' "$s" |
       tail -n 1          |
       sed 's/^_//'       |
       tr "$ACK" ' '      |
       sed "s/[$TAB ]*//" |
       sed "s/[$TAB ]*<[!-?A-~]\\{1,\\}@[A-Za-z0-9][A-Za-z0-9.-]\\{1,\\}[A-Za-z0-9]>[$TAB ]*\$//" |
       tr -d "$TAB"       )
#
# --- send -----------------------------------------------------------
{                                                                       #
  printf '%s\n' "$s" | sed '$d'                                         #
  case "$((cte+ct_m))" in                                               #
    0) grep ''                   | # For the mailbody text,             #
       sed "/^\$/s/\$/$CR/"      | # it must be always put <CR>         #
       sed "/[^$CR]\$/s/\$/$CR/" | # on the end of every line           #
       base64 --wrap=76         ;;                                      #
    *) cat                      ;;                                      #
  esac                                                                  #
}                                                                       |
case "$addr$ACK$name" in
  $ACK*) "$CMD_SENDMAIL"                     -i -t;;
  *$ACK) "$CMD_SENDMAIL" -f"$addr"           -i -t;;
      *) "$CMD_SENDMAIL" -f"$addr" -F"$name" -i -t;;
esac