github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/internal/mime/header.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package mime
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/base64"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"strconv"
    15  	"strings"
    16  	"unicode"
    17  )
    18  
    19  // EncodeWord encodes a string into an RFC 2047 encoded-word.
    20  func EncodeWord(s string) string {
    21  	// UTF-8 "Q" encoding
    22  	b := bytes.NewBufferString("=?utf-8?q?")
    23  	for i := 0; i < len(s); i++ {
    24  		switch c := s[i]; {
    25  		case c == ' ':
    26  			b.WriteByte('_')
    27  		case isVchar(c) && c != '=' && c != '?' && c != '_':
    28  			b.WriteByte(c)
    29  		default:
    30  			fmt.Fprintf(b, "=%02X", c)
    31  		}
    32  	}
    33  	b.WriteString("?=")
    34  	return b.String()
    35  }
    36  
    37  // DecodeWord decodes an RFC 2047 encoded-word.
    38  func DecodeWord(s string) (string, error) {
    39  	fields := strings.Split(s, "?")
    40  	if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" {
    41  		return "", errors.New("address not RFC 2047 encoded")
    42  	}
    43  	charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2])
    44  	if charset != "us-ascii" && charset != "iso-8859-1" && charset != "utf-8" {
    45  		return "", fmt.Errorf("charset not supported: %q", charset)
    46  	}
    47  
    48  	in := bytes.NewBufferString(fields[3])
    49  	var r io.Reader
    50  	switch enc {
    51  	case "b":
    52  		r = base64.NewDecoder(base64.StdEncoding, in)
    53  	case "q":
    54  		r = qDecoder{r: in}
    55  	default:
    56  		return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc)
    57  	}
    58  
    59  	dec, err := ioutil.ReadAll(r)
    60  	if err != nil {
    61  		return "", err
    62  	}
    63  
    64  	switch charset {
    65  	case "us-ascii":
    66  		b := new(bytes.Buffer)
    67  		for _, c := range dec {
    68  			if c >= 0x80 {
    69  				b.WriteRune(unicode.ReplacementChar)
    70  			} else {
    71  				b.WriteRune(rune(c))
    72  			}
    73  		}
    74  		return b.String(), nil
    75  	case "iso-8859-1":
    76  		b := new(bytes.Buffer)
    77  		for _, c := range dec {
    78  			b.WriteRune(rune(c))
    79  		}
    80  		return b.String(), nil
    81  	case "utf-8":
    82  		return string(dec), nil
    83  	}
    84  	panic("unreachable")
    85  }
    86  
    87  type qDecoder struct {
    88  	r       io.Reader
    89  	scratch [2]byte
    90  }
    91  
    92  func (qd qDecoder) Read(p []byte) (n int, err error) {
    93  	// This method writes at most one byte into p.
    94  	if len(p) == 0 {
    95  		return 0, nil
    96  	}
    97  	if _, err := qd.r.Read(qd.scratch[:1]); err != nil {
    98  		return 0, err
    99  	}
   100  	switch c := qd.scratch[0]; {
   101  	case c == '=':
   102  		if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil {
   103  			return 0, err
   104  		}
   105  		x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64)
   106  		if err != nil {
   107  			return 0, fmt.Errorf("mime: invalid RFC 2047 encoding: %q", qd.scratch[:2])
   108  		}
   109  		p[0] = byte(x)
   110  	case c == '_':
   111  		p[0] = ' '
   112  	default:
   113  		p[0] = c
   114  	}
   115  	return 1, nil
   116  }
   117  
   118  // isVchar returns true if c is an RFC 5322 VCHAR character.
   119  func isVchar(c byte) bool {
   120  	// Visible (printing) characters.
   121  	return '!' <= c && c <= '~'
   122  }