...
Tawesoft Logo

Source file src/tawesoft.co.uk/go/email/attachment.go

Documentation: src/tawesoft.co.uk/go/email/attachment.go

     1  package email
     2  
     3  import (
     4      "bytes"
     5      "encoding/base64"
     6      "encoding/json"
     7      "fmt"
     8      "io"
     9      "mime"
    10      "os"
    11      "path"
    12  )
    13  
    14  // Attachment defines an e-mail attachment. They are read lazily.
    15  type Attachment struct {
    16      // Filename is the name to give the attachment in the email
    17      Filename string
    18  
    19      // Mimetype e.g. "application/pdf".
    20      // If an empty string, then attempts to automatically detect based on filename extension.
    21      Mimetype string
    22  
    23      // Reader is a lazy reader. e.g. a function that returns the result of os.Open.
    24      Reader func() (io.ReadCloser, error)
    25  }
    26  
    27  type jsonAttachment struct {
    28      Filename string
    29      Mimetype string
    30      Content []byte
    31  }
    32  
    33  // Implements the json.Marshal interface. Note that the JSON content is ALWAYS
    34  // Base64 encoded (with whitespace).
    35  func (a *Attachment) MarshalJSON() ([]byte, error) {
    36      r, err := a.Reader()
    37      if err != nil {
    38          return nil, fmt.Errorf("attachment open error: %v", err)
    39      }
    40      defer r.Close()
    41  
    42      w := &bytes.Buffer{}
    43      encoder := base64.NewEncoder(base64.StdEncoding, lineBreaker{writer: w})
    44      defer encoder.Close()
    45  
    46      _, err = io.Copy(encoder, r)
    47      if err != nil {
    48          return nil, fmt.Errorf("attachment read error: %v", err)
    49      }
    50  
    51      return json.Marshal(jsonAttachment{
    52          Filename: a.Filename,
    53          Mimetype: a.Mimetype,
    54          Content:  w.Bytes(),
    55      })
    56  }
    57  
    58  // Implements the json.Unarshal interface. Note that the JSON content is ALWAYS
    59  // Base64 encoded (with whitespace).
    60  func (a *Attachment) UnmarshalJSON(data []byte) error {
    61      var j jsonAttachment
    62  
    63      err := json.Unmarshal(data, &j)
    64      if err != nil { return err }
    65  
    66      a.Filename = j.Filename
    67      a.Mimetype = j.Mimetype
    68      a.Reader = func() (io.ReadCloser, error) {
    69          r := bytes.NewReader(j.Content)
    70          decoder := base64.NewDecoder(base64.StdEncoding, r)
    71          return io.NopCloser(decoder), nil
    72      }
    73  
    74      return nil
    75  }
    76  
    77  // FileAttachment returns an Attachment from a file path. The file at that path is lazily opened at the time the
    78  // attachment is sent.
    79  func FileAttachment(src string) *Attachment {
    80  
    81      var mimetype string
    82      var base = path.Base(src)
    83  
    84      var reader = func() (io.ReadCloser, error) {
    85          return os.Open(src)
    86      }
    87  
    88      mimetype =  mime.TypeByExtension(path.Ext(src))
    89      if mimetype == "" {
    90          mimetype = "application/octet-stream"
    91      }
    92  
    93      return &Attachment{
    94          Filename: base,
    95          Mimetype: mimetype,
    96          Reader: reader,
    97      }
    98  }
    99  
   100  // write encodes an attachment as part of a RFC 2045 MIME Email
   101  func (a *Attachment) write(w io.Writer) error {
   102      fmt.Fprintf(w, "Content-Disposition: attachment; filename=\"%[1]s\"; filename*=\"%[1]s\"\r\n",
   103          optionalQEncode(a.Filename))
   104      fmt.Fprintf(w, "Content-Type: %s\r\n", a.Mimetype)
   105      fmt.Fprintf(w, "Content-Transfer-Encoding: base64\r\n")
   106      fmt.Fprintf(w, "\r\n")
   107  
   108      var reader, err = a.Reader()
   109      if err != nil {
   110          return fmt.Errorf("attachment open error: %v", err)
   111      }
   112      defer reader.Close()
   113  
   114      var encoder = base64.NewEncoder(base64.StdEncoding, lineBreaker{writer: w})
   115      defer encoder.Close()
   116  
   117      _, err = io.Copy(encoder, reader)
   118      if err != nil { return err }
   119  
   120      fmt.Fprintf(w, "\r\n")
   121      return nil
   122  }
   123  

View as plain text