mirror of
https://github.com/golang/go
synced 2024-11-12 10:20:27 -07:00
net/http: send Content-Range if no byte range overlaps
RFC 7233, section 4.4 says: >>> For byte ranges, failing to overlap the current extent means that the first-byte-pos of all of the byte-range-spec values were greater than the current length of the selected representation. When this status code is generated in response to a byte-range request, the sender SHOULD generate a Content-Range header field specifying the current length of the selected representation <<< Thus, we should send the Content-Range only if none of the ranges overlap. Fixes #15798. Change-Id: Ic9a3e1b3a8730398b4bdff877a8f2fd2e30149e3 Reviewed-on: https://go-review.googlesource.com/24212 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
0bc94a8864
commit
aa9b3d7014
@ -140,6 +140,10 @@ func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time
|
||||
// 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.
|
||||
@ -189,6 +193,9 @@ func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time,
|
||||
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
|
||||
}
|
||||
@ -543,6 +550,7 @@ func (r httpRange) mimeHeader(contentType string, size int64) textproto.MIMEHead
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -552,6 +560,7 @@ func parseRange(s string, size int64) ([]httpRange, error) {
|
||||
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 == "" {
|
||||
@ -577,9 +586,15 @@ func parseRange(s string, size int64) ([]httpRange, error) {
|
||||
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.
|
||||
@ -597,6 +612,10 @@ func parseRange(s string, size int64) ([]httpRange, error) {
|
||||
}
|
||||
ranges = append(ranges, r)
|
||||
}
|
||||
if noOverlap && len(ranges) == 0 {
|
||||
// The specified ranges did not overlap with the content.
|
||||
return nil, errNoOverlap
|
||||
}
|
||||
return ranges, nil
|
||||
}
|
||||
|
||||
|
@ -765,6 +765,7 @@ func TestServeContent(t *testing.T) {
|
||||
reqHeader map[string]string
|
||||
wantLastMod string
|
||||
wantContentType string
|
||||
wantContentRange string
|
||||
wantStatus int
|
||||
}
|
||||
htmlModTime := mustStat(t, "testdata/index.html").ModTime()
|
||||
@ -820,8 +821,19 @@ func TestServeContent(t *testing.T) {
|
||||
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.
|
||||
@ -842,9 +854,10 @@ func TestServeContent(t *testing.T) {
|
||||
"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",
|
||||
@ -853,9 +866,10 @@ func TestServeContent(t *testing.T) {
|
||||
"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"),
|
||||
@ -903,6 +917,9 @@ func TestServeContent(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ var ParseRangeTests = []struct {
|
||||
{"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}}},
|
||||
|
Loading…
Reference in New Issue
Block a user