// users.
var errSeeker = errors.New("seeker can't seek")
+// errNoOverlap is returned by serveContent's parseRange if first-byte-pos of
+// all of the byte-range-spec values is greater than the content size.
+var errNoOverlap = errors.New("invalid range: failed to overlap")
+
// if name is empty, filename is unknown. (used for mime type, before sniffing)
// if modtime.IsZero(), modtime is unknown.
// content must be seeked to the beginning of the file.
if size >= 0 {
ranges, err := parseRange(rangeReq, size)
if err != nil {
+ if err == errNoOverlap {
+ w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
+ }
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
return
}
}
// parseRange parses a Range header string as per RFC 2616.
+// errNoOverlap is returned if none of the ranges overlap.
func parseRange(s string, size int64) ([]httpRange, error) {
if s == "" {
return nil, nil // header not present
return nil, errors.New("invalid range")
}
var ranges []httpRange
+ noOverlap := false
for _, ra := range strings.Split(s[len(b):], ",") {
ra = strings.TrimSpace(ra)
if ra == "" {
r.length = size - r.start
} else {
i, err := strconv.ParseInt(start, 10, 64)
- if err != nil || i >= size || i < 0 {
+ if err != nil || i < 0 {
return nil, errors.New("invalid range")
}
+ if i >= size {
+ // If the range begins after the size of the content,
+ // then it does not overlap.
+ noOverlap = true
+ continue
+ }
r.start = i
if end == "" {
// If no end is specified, range extends to end of the file.
}
ranges = append(ranges, r)
}
+ if noOverlap && len(ranges) == 0 {
+ // The specified ranges did not overlap with the content.
+ return nil, errNoOverlap
+ }
return ranges, nil
}
reqHeader map[string]string
wantLastMod string
wantContentType string
+ wantContentRange string
wantStatus int
}
htmlModTime := mustStat(t, "testdata/index.html").ModTime()
reqHeader: map[string]string{
"Range": "bytes=0-4",
},
- wantStatus: StatusPartialContent,
- wantContentType: "text/css; charset=utf-8",
+ wantStatus: StatusPartialContent,
+ wantContentType: "text/css; charset=utf-8",
+ wantContentRange: "bytes 0-4/8",
+ },
+ "range_no_overlap": {
+ file: "testdata/style.css",
+ serveETag: `"A"`,
+ reqHeader: map[string]string{
+ "Range": "bytes=10-20",
+ },
+ wantStatus: StatusRequestedRangeNotSatisfiable,
+ wantContentType: "text/plain; charset=utf-8",
+ wantContentRange: "bytes */8",
},
// An If-Range resource for entity "A", but entity "B" is now current.
// The Range request should be ignored.
"Range": "bytes=0-4",
"If-Range": "Wed, 25 Jun 2014 17:12:18 GMT",
},
- wantStatus: StatusPartialContent,
- wantContentType: "text/css; charset=utf-8",
- wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
+ wantStatus: StatusPartialContent,
+ wantContentType: "text/css; charset=utf-8",
+ wantContentRange: "bytes 0-4/8",
+ wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
},
"range_with_modtime_nanos": {
file: "testdata/style.css",
"Range": "bytes=0-4",
"If-Range": "Wed, 25 Jun 2014 17:12:18 GMT",
},
- wantStatus: StatusPartialContent,
- wantContentType: "text/css; charset=utf-8",
- wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
+ wantStatus: StatusPartialContent,
+ wantContentType: "text/css; charset=utf-8",
+ wantContentRange: "bytes 0-4/8",
+ wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
},
"unix_zero_modtime": {
content: strings.NewReader("<html>foo"),
if g, e := res.Header.Get("Content-Type"), tt.wantContentType; g != e {
t.Errorf("test %q: content-type = %q, want %q", testName, g, e)
}
+ if g, e := res.Header.Get("Content-Range"), tt.wantContentRange; g != e {
+ t.Errorf("test %q: content-range = %q, want %q", testName, g, e)
+ }
if g, e := res.Header.Get("Last-Modified"), tt.wantLastMod; g != e {
t.Errorf("test %q: last-modified = %q, want %q", testName, g, e)
}
{"bytes=0-", 10, []httpRange{{0, 10}}},
{"bytes=5-", 10, []httpRange{{5, 5}}},
{"bytes=0-20", 10, []httpRange{{0, 10}}},
- {"bytes=15-,0-5", 10, nil},
+ {"bytes=15-,0-5", 10, []httpRange{{0, 6}}},
{"bytes=1-2,5-", 10, []httpRange{{1, 2}, {5, 5}}},
{"bytes=-2 , 7-", 11, []httpRange{{9, 2}, {7, 4}}},
{"bytes=0-0 ,2-2, 7-", 11, []httpRange{{0, 1}, {2, 1}, {7, 4}}},