You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
5.0 KiB

5 years ago
  1. package needle
  2. import (
  3. "crypto/md5"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "mime"
  8. "net/http"
  9. "path"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "github.com/chrislusf/seaweedfs/weed/glog"
  14. "github.com/chrislusf/seaweedfs/weed/util"
  15. )
  16. type ParsedUpload struct {
  17. FileName string
  18. Data []byte
  19. MimeType string
  20. PairMap map[string]string
  21. IsGzipped bool
  22. IsZstd bool
  23. OriginalDataSize int
  24. ModifiedTime uint64
  25. Ttl *TTL
  26. IsChunkedFile bool
  27. UncompressedData []byte
  28. }
  29. func ParseUpload(r *http.Request, sizeLimit int64) (pu *ParsedUpload, e error) {
  30. pu = &ParsedUpload{}
  31. pu.PairMap = make(map[string]string)
  32. for k, v := range r.Header {
  33. if len(v) > 0 && strings.HasPrefix(k, PairNamePrefix) {
  34. pu.PairMap[k] = v[0]
  35. }
  36. }
  37. if r.Method == "POST" {
  38. e = parseMultipart(r, sizeLimit, pu)
  39. } else {
  40. e = parsePut(r, sizeLimit, pu)
  41. }
  42. if e != nil {
  43. return
  44. }
  45. pu.ModifiedTime, _ = strconv.ParseUint(r.FormValue("ts"), 10, 64)
  46. pu.Ttl, _ = ReadTTL(r.FormValue("ttl"))
  47. pu.OriginalDataSize = len(pu.Data)
  48. pu.UncompressedData = pu.Data
  49. // println("received data", len(pu.Data), "isGzipped", pu.IsCompressed, "mime", pu.MimeType, "name", pu.FileName)
  50. if pu.MimeType == "" {
  51. pu.MimeType = http.DetectContentType(pu.Data)
  52. // println("detected mimetype to", pu.MimeType)
  53. if pu.MimeType == "application/octet-stream" {
  54. pu.MimeType = ""
  55. }
  56. }
  57. if pu.IsGzipped {
  58. if unzipped, e := util.DecompressData(pu.Data); e == nil {
  59. pu.OriginalDataSize = len(unzipped)
  60. pu.UncompressedData = unzipped
  61. // println("ungzipped data size", len(unzipped))
  62. }
  63. } else {
  64. ext := filepath.Base(pu.FileName)
  65. if shouldBeCompressed, iAmSure := util.IsCompressableFileType(ext, pu.MimeType); pu.MimeType == "" && !iAmSure || shouldBeCompressed && iAmSure {
  66. // println("ext", ext, "iAmSure", iAmSure, "shouldGzip", shouldGzip, "mimeType", pu.MimeType)
  67. if compressedData, err := util.GzipData(pu.Data); err == nil {
  68. if len(compressedData)*10 < len(pu.Data)*9 {
  69. pu.Data = compressedData
  70. pu.IsGzipped = true
  71. }
  72. // println("gzipped data size", len(compressedData))
  73. }
  74. }
  75. }
  76. if expectedChecksum := r.Header.Get("Content-MD5"); expectedChecksum != "" {
  77. h := md5.New()
  78. h.Write(pu.UncompressedData)
  79. if receivedChecksum := fmt.Sprintf("%x", h.Sum(nil)); expectedChecksum != receivedChecksum {
  80. e = fmt.Errorf("Content-MD5 did not match md5 of file data [%s] != [%s]", expectedChecksum, receivedChecksum)
  81. return
  82. }
  83. }
  84. return
  85. }
  86. func parsePut(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) {
  87. pu.IsGzipped = r.Header.Get("Content-Encoding") == "gzip"
  88. pu.IsZstd = r.Header.Get("Content-Encoding") == "zstd"
  89. pu.MimeType = r.Header.Get("Content-Type")
  90. pu.FileName = ""
  91. pu.Data, e = ioutil.ReadAll(io.LimitReader(r.Body, sizeLimit+1))
  92. if e == io.EOF || int64(pu.OriginalDataSize) == sizeLimit+1 {
  93. io.Copy(ioutil.Discard, r.Body)
  94. }
  95. r.Body.Close()
  96. return nil
  97. }
  98. func parseMultipart(r *http.Request, sizeLimit int64, pu *ParsedUpload) (e error) {
  99. defer func() {
  100. if e != nil && r.Body != nil {
  101. io.Copy(ioutil.Discard, r.Body)
  102. r.Body.Close()
  103. }
  104. }()
  105. form, fe := r.MultipartReader()
  106. if fe != nil {
  107. glog.V(0).Infoln("MultipartReader [ERROR]", fe)
  108. e = fe
  109. return
  110. }
  111. // first multi-part item
  112. part, fe := form.NextPart()
  113. if fe != nil {
  114. glog.V(0).Infoln("Reading Multi part [ERROR]", fe)
  115. e = fe
  116. return
  117. }
  118. pu.FileName = part.FileName()
  119. if pu.FileName != "" {
  120. pu.FileName = path.Base(pu.FileName)
  121. }
  122. pu.Data, e = ioutil.ReadAll(io.LimitReader(part, sizeLimit+1))
  123. if e != nil {
  124. glog.V(0).Infoln("Reading Content [ERROR]", e)
  125. return
  126. }
  127. if len(pu.Data) == int(sizeLimit)+1 {
  128. e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
  129. return
  130. }
  131. // if the filename is empty string, do a search on the other multi-part items
  132. for pu.FileName == "" {
  133. part2, fe := form.NextPart()
  134. if fe != nil {
  135. break // no more or on error, just safely break
  136. }
  137. fName := part2.FileName()
  138. // found the first <file type> multi-part has filename
  139. if fName != "" {
  140. data2, fe2 := ioutil.ReadAll(io.LimitReader(part2, sizeLimit+1))
  141. if fe2 != nil {
  142. glog.V(0).Infoln("Reading Content [ERROR]", fe2)
  143. e = fe2
  144. return
  145. }
  146. if len(data2) == int(sizeLimit)+1 {
  147. e = fmt.Errorf("file over the limited %d bytes", sizeLimit)
  148. return
  149. }
  150. // update
  151. pu.Data = data2
  152. pu.FileName = path.Base(fName)
  153. break
  154. }
  155. }
  156. pu.IsChunkedFile, _ = strconv.ParseBool(r.FormValue("cm"))
  157. if !pu.IsChunkedFile {
  158. dotIndex := strings.LastIndex(pu.FileName, ".")
  159. ext, mtype := "", ""
  160. if dotIndex > 0 {
  161. ext = strings.ToLower(pu.FileName[dotIndex:])
  162. mtype = mime.TypeByExtension(ext)
  163. }
  164. contentType := part.Header.Get("Content-Type")
  165. if contentType != "" && contentType != "application/octet-stream" && mtype != contentType {
  166. pu.MimeType = contentType // only return mime type if not deductable
  167. mtype = contentType
  168. }
  169. pu.IsGzipped = part.Header.Get("Content-Encoding") == "gzip"
  170. pu.IsZstd = part.Header.Get("Content-Encoding") == "zstd"
  171. }
  172. return
  173. }