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.

115 lines
2.7 KiB

  1. package weed_server
  2. import (
  3. "errors"
  4. "fmt"
  5. "mime/multipart"
  6. "net/textproto"
  7. "strconv"
  8. "strings"
  9. )
  10. // copied from src/pkg/net/http/fs.go
  11. // httpRange specifies the byte range to be sent to the client.
  12. type httpRange struct {
  13. start, length int64
  14. }
  15. func (r httpRange) contentRange(size int64) string {
  16. return fmt.Sprintf("bytes %d-%d/%d", r.start, r.start+r.length-1, size)
  17. }
  18. func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHeader {
  19. return textproto.MIMEHeader{
  20. "Content-Range": {r.contentRange(size)},
  21. "Content-Type": {contentType},
  22. }
  23. }
  24. // parseRange parses a Range header string as per RFC 2616.
  25. func parseRange(s string, size int64) ([]httpRange, error) {
  26. if s == "" {
  27. return nil, nil // header not present
  28. }
  29. const b = "bytes="
  30. if !strings.HasPrefix(s, b) {
  31. return nil, errors.New("invalid range")
  32. }
  33. var ranges []httpRange
  34. for _, ra := range strings.Split(s[len(b):], ",") {
  35. ra = strings.TrimSpace(ra)
  36. if ra == "" {
  37. continue
  38. }
  39. i := strings.Index(ra, "-")
  40. if i < 0 {
  41. return nil, errors.New("invalid range")
  42. }
  43. start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
  44. var r httpRange
  45. if start == "" {
  46. // If no start is specified, end specifies the
  47. // range start relative to the end of the file.
  48. i, err := strconv.ParseInt(end, 10, 64)
  49. if err != nil {
  50. return nil, errors.New("invalid range")
  51. }
  52. if i > size {
  53. i = size
  54. }
  55. r.start = size - i
  56. r.length = size - r.start
  57. } else {
  58. i, err := strconv.ParseInt(start, 10, 64)
  59. if err != nil || i > size || i < 0 {
  60. return nil, errors.New("invalid range")
  61. }
  62. r.start = i
  63. if end == "" {
  64. // If no end is specified, range extends to end of the file.
  65. r.length = size - r.start
  66. } else {
  67. i, err := strconv.ParseInt(end, 10, 64)
  68. if err != nil || r.start > i {
  69. return nil, errors.New("invalid range")
  70. }
  71. if i >= size {
  72. i = size - 1
  73. }
  74. r.length = i - r.start + 1
  75. }
  76. }
  77. ranges = append(ranges, r)
  78. }
  79. return ranges, nil
  80. }
  81. // countingWriter counts how many bytes have been written to it.
  82. type countingWriter int64
  83. func (w *countingWriter) Write(p []byte) (n int, err error) {
  84. *w += countingWriter(len(p))
  85. return len(p), nil
  86. }
  87. // rangesMIMESize returns the nunber of bytes it takes to encode the
  88. // provided ranges as a multipart response.
  89. func rangesMIMESize(ranges []httpRange, contentType string, contentSize int64) (encSize int64) {
  90. var w countingWriter
  91. mw := multipart.NewWriter(&w)
  92. for _, ra := range ranges {
  93. mw.CreatePart(ra.mimeHeader(contentType, contentSize))
  94. encSize += ra.length
  95. }
  96. mw.Close()
  97. encSize += int64(w)
  98. return
  99. }
  100. func sumRangesSize(ranges []httpRange) (size int64) {
  101. for _, ra := range ranges {
  102. size += ra.length
  103. }
  104. return
  105. }