From: Sina Siadat Date: Fri, 17 Jun 2016 16:32:59 +0000 (+0430) Subject: net/http: send Content-Range if no byte range overlaps X-Git-Tag: go1.8beta1~1640 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=aa9b3d70142afb75a510c2c92b8c387fce10b2c9;p=gostls13.git 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 TryBot-Result: Gobot Gobot Reviewed-by: Brad Fitzpatrick --- diff --git a/src/net/http/fs.go b/src/net/http/fs.go index 9ebc558214..ce674c42ed 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -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 } diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go index aa3323dd23..e39c3a83c7 100644 --- a/src/net/http/fs_test.go +++ b/src/net/http/fs_test.go @@ -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("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) } diff --git a/src/net/http/range_test.go b/src/net/http/range_test.go index ef911af7b0..114987ed2c 100644 --- a/src/net/http/range_test.go +++ b/src/net/http/range_test.go @@ -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}}},