chunk.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package chunk
  2. import (
  3. "bytes"
  4. "compress/zlib"
  5. "encoding/base64"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io/ioutil"
  10. "sync"
  11. )
  12. const chunkMaxBytes = 1024 * 16
  13. const hashByteSize = 16
  14. type HashKey [hashByteSize]byte
  15. // Pack takes a slice and copies each byte to HashKey internal representation
  16. func (h *HashKey) Pack(b []byte) {
  17. if len(b) < hashByteSize {
  18. return
  19. }
  20. copy(h[:], b[0:hashByteSize])
  21. }
  22. // String implements the Stringer interface from fmt.Stringer
  23. func (h HashKey) String() string {
  24. return base64.RawStdEncoding.EncodeToString(h[0:hashByteSize])
  25. }
  26. // Hex returns the hash, encoded in hexadecimal
  27. func (h HashKey) Hex() string {
  28. return fmt.Sprintf("%x", h[:])
  29. }
  30. // UnmarshalJSON implements the Unmarshaler interface from encoding/json
  31. func (h *HashKey) UnmarshalJSON(b []byte) error {
  32. dbuf := make([]byte, base64.RawStdEncoding.DecodedLen(len(b[1:len(b)-1])))
  33. _, err := base64.RawStdEncoding.Decode(dbuf, b[1:len(b)-1])
  34. if err != nil {
  35. return err
  36. }
  37. h.Pack(dbuf)
  38. return nil
  39. }
  40. // MarshalJSON implements the Marshaler interface from encoding/json
  41. // The value is marshaled as a raw base64 to save some bytes
  42. // eg. instead of typically using hex, de17038001170380011703ff01170380 would be represented as 3hcDgAEXA4ABFwP/ARcDgA
  43. func (h *HashKey) MarshalJSON() ([]byte, error) {
  44. return []byte(`"` + h.String() + `"`), nil
  45. }
  46. // PartsInfo describes the mime-parts contained in the email
  47. type PartsInfo struct {
  48. Count uint32 `json:"c"` // number of parts
  49. TextPart int `json:"tp"` // index of the main text part to display
  50. HTMLPart int `json:"hp"` // index of the main html part to display (if any)
  51. HasAttach bool `json:"a"` // is there an attachment?
  52. Parts []ChunkedPart `json:"p"` // info describing a mime-part
  53. CBoundaries []string `json:"cbl"` // content boundaries list
  54. Err error `json:"e"` // any error encountered (mimeparse.MimeError)
  55. }
  56. var bp sync.Pool // bytes.buffer pool
  57. // ChunkedPart contains header information about a mime-part, including keys pointing to where the data is stored at
  58. type ChunkedPart struct {
  59. PartId string `json:"i"`
  60. Size uint `json:"s"`
  61. ChunkHash []HashKey `json:"h"` // sequence of hashes the data is stored at
  62. ContentType string `json:"t"`
  63. Charset string `json:"c"`
  64. TransferEncoding string `json:"e"`
  65. ContentDisposition string `json:"d"`
  66. ContentBoundary int `json:"cb"` // index to the CBoundaries list in PartsInfo
  67. }
  68. func NewPartsInfo() *PartsInfo {
  69. pi := new(PartsInfo)
  70. bp = sync.Pool{
  71. // if not available, then create a new one
  72. New: func() interface{} {
  73. var b bytes.Buffer
  74. return &b
  75. },
  76. }
  77. return pi
  78. }
  79. // boundary takes a string and returns the index of the string in the info.CBoundaries slice
  80. func (info *PartsInfo) boundary(cb string) int {
  81. for i := range info.CBoundaries {
  82. if info.CBoundaries[i] == cb {
  83. return i
  84. }
  85. }
  86. info.CBoundaries = append(info.CBoundaries, cb)
  87. return len(info.CBoundaries) - 1
  88. }
  89. // UnmarshalJSON unmarshals the JSON and decompresses using zlib
  90. func (info *PartsInfo) UnmarshalJSONZlib(b []byte) error {
  91. r, err := zlib.NewReader(bytes.NewReader(b[1 : len(b)-1]))
  92. if err != nil {
  93. return err
  94. }
  95. all, err := ioutil.ReadAll(r)
  96. if err != nil {
  97. return err
  98. }
  99. err = json.Unmarshal(all, info)
  100. if err != nil {
  101. return err
  102. }
  103. return nil
  104. }
  105. // MarshalJSONZlib marshals and compresses the bytes using zlib
  106. func (info *PartsInfo) MarshalJSONZlib() ([]byte, error) {
  107. if len(info.Parts) == 0 {
  108. return []byte{}, errors.New("message contained no parts, was mime analyzer")
  109. }
  110. buf, err := json.Marshal(info)
  111. if err != nil {
  112. return buf, err
  113. }
  114. // borrow a buffer form the pool
  115. compressed := bp.Get().(*bytes.Buffer)
  116. // put back in the pool
  117. defer func() {
  118. compressed.Reset()
  119. bp.Put(compressed)
  120. }()
  121. zlibw, err := zlib.NewWriterLevel(compressed, 9)
  122. if err != nil {
  123. return buf, err
  124. }
  125. if _, err := zlibw.Write(buf); err != nil {
  126. return buf, err
  127. }
  128. if err := zlibw.Close(); err != nil {
  129. return buf, err
  130. }
  131. return []byte(`"` + compressed.String() + `"`), nil
  132. }