mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00: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
3 changed files with 46 additions and 10 deletions
|
|
@ -140,6 +140,10 @@ func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time
|
||||||
// users.
|
// users.
|
||||||
var errSeeker = errors.New("seeker can't seek")
|
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 name is empty, filename is unknown. (used for mime type, before sniffing)
|
||||||
// if modtime.IsZero(), modtime is unknown.
|
// if modtime.IsZero(), modtime is unknown.
|
||||||
// content must be seeked to the beginning of the file.
|
// 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 {
|
if size >= 0 {
|
||||||
ranges, err := parseRange(rangeReq, size)
|
ranges, err := parseRange(rangeReq, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if err == errNoOverlap {
|
||||||
|
w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", size))
|
||||||
|
}
|
||||||
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
|
Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
|
||||||
return
|
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.
|
// 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) {
|
func parseRange(s string, size int64) ([]httpRange, error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil // header not present
|
return nil, nil // header not present
|
||||||
|
|
@ -552,6 +560,7 @@ func parseRange(s string, size int64) ([]httpRange, error) {
|
||||||
return nil, errors.New("invalid range")
|
return nil, errors.New("invalid range")
|
||||||
}
|
}
|
||||||
var ranges []httpRange
|
var ranges []httpRange
|
||||||
|
noOverlap := false
|
||||||
for _, ra := range strings.Split(s[len(b):], ",") {
|
for _, ra := range strings.Split(s[len(b):], ",") {
|
||||||
ra = strings.TrimSpace(ra)
|
ra = strings.TrimSpace(ra)
|
||||||
if ra == "" {
|
if ra == "" {
|
||||||
|
|
@ -577,9 +586,15 @@ func parseRange(s string, size int64) ([]httpRange, error) {
|
||||||
r.length = size - r.start
|
r.length = size - r.start
|
||||||
} else {
|
} else {
|
||||||
i, err := strconv.ParseInt(start, 10, 64)
|
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")
|
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
|
r.start = i
|
||||||
if end == "" {
|
if end == "" {
|
||||||
// If no end is specified, range extends to end of the file.
|
// 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)
|
ranges = append(ranges, r)
|
||||||
}
|
}
|
||||||
|
if noOverlap && len(ranges) == 0 {
|
||||||
|
// The specified ranges did not overlap with the content.
|
||||||
|
return nil, errNoOverlap
|
||||||
|
}
|
||||||
return ranges, nil
|
return ranges, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -765,6 +765,7 @@ func TestServeContent(t *testing.T) {
|
||||||
reqHeader map[string]string
|
reqHeader map[string]string
|
||||||
wantLastMod string
|
wantLastMod string
|
||||||
wantContentType string
|
wantContentType string
|
||||||
|
wantContentRange string
|
||||||
wantStatus int
|
wantStatus int
|
||||||
}
|
}
|
||||||
htmlModTime := mustStat(t, "testdata/index.html").ModTime()
|
htmlModTime := mustStat(t, "testdata/index.html").ModTime()
|
||||||
|
|
@ -822,6 +823,17 @@ func TestServeContent(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantStatus: StatusPartialContent,
|
wantStatus: StatusPartialContent,
|
||||||
wantContentType: "text/css; charset=utf-8",
|
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.
|
// An If-Range resource for entity "A", but entity "B" is now current.
|
||||||
// The Range request should be ignored.
|
// The Range request should be ignored.
|
||||||
|
|
@ -844,6 +856,7 @@ func TestServeContent(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantStatus: StatusPartialContent,
|
wantStatus: StatusPartialContent,
|
||||||
wantContentType: "text/css; charset=utf-8",
|
wantContentType: "text/css; charset=utf-8",
|
||||||
|
wantContentRange: "bytes 0-4/8",
|
||||||
wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
|
wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
|
||||||
},
|
},
|
||||||
"range_with_modtime_nanos": {
|
"range_with_modtime_nanos": {
|
||||||
|
|
@ -855,6 +868,7 @@ func TestServeContent(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantStatus: StatusPartialContent,
|
wantStatus: StatusPartialContent,
|
||||||
wantContentType: "text/css; charset=utf-8",
|
wantContentType: "text/css; charset=utf-8",
|
||||||
|
wantContentRange: "bytes 0-4/8",
|
||||||
wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
|
wantLastMod: "Wed, 25 Jun 2014 17:12:18 GMT",
|
||||||
},
|
},
|
||||||
"unix_zero_modtime": {
|
"unix_zero_modtime": {
|
||||||
|
|
@ -903,6 +917,9 @@ func TestServeContent(t *testing.T) {
|
||||||
if g, e := res.Header.Get("Content-Type"), tt.wantContentType; g != e {
|
if g, e := res.Header.Get("Content-Type"), tt.wantContentType; g != e {
|
||||||
t.Errorf("test %q: content-type = %q, want %q", testName, 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 {
|
if g, e := res.Header.Get("Last-Modified"), tt.wantLastMod; g != e {
|
||||||
t.Errorf("test %q: last-modified = %q, want %q", testName, 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=0-", 10, []httpRange{{0, 10}}},
|
||||||
{"bytes=5-", 10, []httpRange{{5, 5}}},
|
{"bytes=5-", 10, []httpRange{{5, 5}}},
|
||||||
{"bytes=0-20", 10, []httpRange{{0, 10}}},
|
{"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=1-2,5-", 10, []httpRange{{1, 2}, {5, 5}}},
|
||||||
{"bytes=-2 , 7-", 11, []httpRange{{9, 2}, {7, 4}}},
|
{"bytes=-2 , 7-", 11, []httpRange{{9, 2}, {7, 4}}},
|
||||||
{"bytes=0-0 ,2-2, 7-", 11, []httpRange{{0, 1}, {2, 1}, {7, 4}}},
|
{"bytes=0-0 ,2-2, 7-", 11, []httpRange{{0, 1}, {2, 1}, {7, 4}}},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue