chunk.go 4.0 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
  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. }
  55. var bp sync.Pool // bytes.buffer pool
  56. // ChunkedPart contains header information about a mime-part, including keys pointing to where the data is stored at
  57. type ChunkedPart struct {
  58. PartId string `json:"i"`
  59. Size uint `json:"s"`
  60. ChunkHash []HashKey `json:"h"` // sequence of hashes the data is stored at
  61. ContentType string `json:"t"`
  62. Charset string `json:"c"`
  63. TransferEncoding string `json:"e"`
  64. ContentDisposition string `json:"d"`
  65. ContentBoundary int `json:"cb"` // index to the CBoundaries list in PartsInfo
  66. }
  67. func NewPartsInfo() *PartsInfo {
  68. pi := new(PartsInfo)
  69. bp = sync.Pool{
  70. // if not available, then create a new one
  71. New: func() interface{} {
  72. var b bytes.Buffer
  73. return &b
  74. },
  75. }
  76. return pi
  77. }
  78. // boundary takes a string and returns the index of the string in the info.CBoundaries slice
  79. func (info *PartsInfo) boundary(cb string) int {
  80. for i := range info.CBoundaries {
  81. if info.CBoundaries[i] == cb {
  82. return i
  83. }
  84. }
  85. info.CBoundaries = append(info.CBoundaries, cb)
  86. return len(info.CBoundaries) - 1
  87. }
  88. // UnmarshalJSON unmarshals the JSON and decompresses using zlib
  89. func (info *PartsInfo) UnmarshalJSONZlib(b []byte) error {
  90. r, err := zlib.NewReader(bytes.NewReader(b[1 : len(b)-1]))
  91. if err != nil {
  92. return err
  93. }
  94. all, err := ioutil.ReadAll(r)
  95. if err != nil {
  96. return err
  97. }
  98. err = json.Unmarshal(all, info)
  99. if err != nil {
  100. return err
  101. }
  102. return nil
  103. }
  104. // MarshalJSONZlib marshals and compresses the bytes using zlib
  105. func (info *PartsInfo) MarshalJSONZlib() ([]byte, error) {
  106. if len(info.Parts) == 0 {
  107. return []byte{}, errors.New("message contained no parts, was mime analyzer")
  108. }
  109. buf, err := json.Marshal(info)
  110. if err != nil {
  111. return buf, err
  112. }
  113. // borrow a buffer form the pool
  114. compressed := bp.Get().(*bytes.Buffer)
  115. // put back in the pool
  116. defer func() {
  117. compressed.Reset()
  118. bp.Put(compressed)
  119. }()
  120. zlibw, err := zlib.NewWriterLevel(compressed, 9)
  121. if err != nil {
  122. return buf, err
  123. }
  124. if _, err := zlibw.Write(buf); err != nil {
  125. return buf, err
  126. }
  127. if err := zlibw.Close(); err != nil {
  128. return buf, err
  129. }
  130. return []byte(`"` + compressed.String() + `"`), nil
  131. }