encoder.go 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. package utf7
  2. import (
  3. "strings"
  4. "unicode/utf16"
  5. "unicode/utf8"
  6. )
  7. // Encode encodes a string with modified UTF-7.
  8. func Encode(src string) string {
  9. var sb strings.Builder
  10. sb.Grow(len(src))
  11. for i := 0; i < len(src); {
  12. ch := src[i]
  13. if min <= ch && ch <= max {
  14. sb.WriteByte(ch)
  15. if ch == '&' {
  16. sb.WriteByte('-')
  17. }
  18. i++
  19. } else {
  20. start := i
  21. // Find the next printable ASCII code point
  22. i++
  23. for i < len(src) && (src[i] < min || src[i] > max) {
  24. i++
  25. }
  26. sb.Write(encode([]byte(src[start:i])))
  27. }
  28. }
  29. return sb.String()
  30. }
  31. // Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64,
  32. // removes the padding, and adds UTF-7 shifts.
  33. func encode(s []byte) []byte {
  34. // len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no
  35. // control code points (see table below).
  36. b := make([]byte, 0, len(s)+4)
  37. for len(s) > 0 {
  38. r, size := utf8.DecodeRune(s)
  39. if r > utf8.MaxRune {
  40. r, size = utf8.RuneError, 1 // Bug fix (issue 3785)
  41. }
  42. s = s[size:]
  43. if r1, r2 := utf16.EncodeRune(r); r1 != utf8.RuneError {
  44. b = append(b, byte(r1>>8), byte(r1))
  45. r = r2
  46. }
  47. b = append(b, byte(r>>8), byte(r))
  48. }
  49. // Encode as base64
  50. n := b64Enc.EncodedLen(len(b)) + 2
  51. b64 := make([]byte, n)
  52. b64Enc.Encode(b64[1:], b)
  53. // Strip padding
  54. n -= 2 - (len(b)+2)%3
  55. b64 = b64[:n]
  56. // Add UTF-7 shifts
  57. b64[0] = '&'
  58. b64[n-1] = '-'
  59. return b64
  60. }
  61. // Escape passes through raw UTF-8 as-is and escapes the special UTF-7 marker
  62. // (the ampersand character).
  63. func Escape(src string) string {
  64. var sb strings.Builder
  65. sb.Grow(len(src))
  66. for _, ch := range src {
  67. sb.WriteRune(ch)
  68. if ch == '&' {
  69. sb.WriteByte('-')
  70. }
  71. }
  72. return sb.String()
  73. }