]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: send Content-Range if no byte range overlaps
authorSina Siadat <siadat@gmail.com>
Fri, 17 Jun 2016 16:32:59 +0000 (21:02 +0430)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 25 Aug 2016 17:52:35 +0000 (17:52 +0000)
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>
src/net/http/fs.go
src/net/http/fs_test.go
src/net/http/range_test.go

index 9ebc5582141e0a1513fb7a5950e8982a5dbcd2cf..ce674c42ed84482bc807cb9798c053ad57121607 100644 (file)
@@ -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
 }
 
index aa3323dd23b76563f1437245eabe597a9ff9f0bd..e39c3a83c7b3c3c4d7c9a37401b8f6c477436f0a 100644 (file)
@@ -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)
                }
index ef911af7b089cf692726b3edbb254b3d5c52009b..114987ed2c6984946a1165324d74d16e9af914a2 100644 (file)
@@ -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}}},