#
# $Id: base64handler.icn,v 1.2 2006-07-10 13:43:32 rparlett Exp $
#

package mail

global base64_string, base64_cset

#
# Handles the base64 encoding of a message
#
class Base64Handler:EncodingHandler()
   method can_handle(enc)
      return ::map(enc) == "base64"
   end

   method decode_data(m, data)
      local s, t, w, x, y, z, res, pad

      static n64
      initial {
         n64 := ::string(&cset)[1+:64]
      }

      # Transform the data by stripping out all non base64 encoding chars.
      t := ""
      pad := 0
      data ? {
         while ::tab(::upto(base64_cset)) do
            t ||:= ::tab(::many(base64_cset))
         while ::tab(::upto('=')) do {
            s := ::tab(::many('='))
            pad +:= *s
            t ||:= ::repl("\x00", *s)
         }
      }

      if (pad > 2) | (*t % 4 ~= 0) then
         return error("Badly formatted Base64 data")

      t := ::map(t, base64_string, n64)

      res := ""
      t ? while ( w := ::ord(::move(1)), x := ::ord(::move(1)), y := ::ord(::move(1)), z := ::ord(::move(1)) ) do {
         res ||:= ::char( ::ior( ::ishift(w,2), ::ishift(x,-4) ) )
         res ||:= ::char( ::ior( ::iand(::ishift(x,4),255), ::ishift(y,-2) ) )
         res ||:= ::char( ::ior( ::iand(::ishift(y,6),255), z ) )
      }

      res[0 -: pad] := ""

      return res
   end

   method encode_data(m, data)
      local i, j, k, pad, res, ll

      res := ""
      ll := 0

      pad := (3 - (*data % 3)) % 3

      data ||:= ::repl("\x00", pad)

      data ? while (i := ::ord(::move(1)), j := ::ord(::move(1)), k := ::ord(::move(1))) do {
         res ||:= base64_string[ 1 + ::ishift(i,-2) ]
         res ||:= base64_string[ 1 + ::ior( ::ishift(::iand(i,3),4), ::ishift(j,-4) ) ]
         res ||:= base64_string[ 1 + ::ior( ::ishift(::iand(j,15),2), ::ishift(k,-6) ) ]
         res ||:= base64_string[ 1 + ::iand(k,63) ]
         ll +:= 1
         if ll = 19 then {
            res ||:= "\r\n"
            ll := 0
         }
      }
      res[ 0 -: pad ] := ::repl("=", pad) || "\r\n"

      return res
   end

   initially()
      initial {
         base64_string := &ucase || &lcase || &digits || "+/"
         base64_cset := ::cset(base64_string)
      }
end
