Parse content ranges

This commit is contained in:
Felix Geisendörfer 2013-03-17 16:13:32 +01:00
parent 3573790185
commit 836c5c69f0
3 changed files with 137 additions and 3 deletions

View File

@ -0,0 +1,74 @@
package main
import (
"errors"
"strconv"
"strings"
)
var errInvalidRange = errors.New("invalid Content-Range")
type contentRange struct {
Start int64
End int64
Size int64
}
// parseContentRange parse a Content-Range string like "5-10/100".
// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16 .
// Asterisks "*" will result in End/Size being set to -1.
func parseContentRange(s string) (*contentRange, error) {
const prefix = "bytes "
offset := strings.Index(s, prefix)
if offset != 0 {
return nil, errInvalidRange
}
s = s[len(prefix):]
parts := strings.Split(s, "/")
if len(parts) != 2 {
return nil, errInvalidRange
}
r := new(contentRange)
if parts[0] == "*" {
r.Start = 0
r.End = -1
} else {
offsets := strings.Split(parts[0], "-")
if len(offsets) != 2 {
return nil, errInvalidRange
}
if offset, err := strconv.ParseInt(offsets[0], 10, 64); err == nil {
r.Start = offset
} else {
return nil, errInvalidRange
}
if offset, err := strconv.ParseInt(offsets[1], 10, 64); err == nil {
r.End = offset
} else {
return nil, errInvalidRange
}
// A byte-content-range-spec with a byte-range-resp-spec whose last-
// byte-pos value is less than its first-byte-pos value, or whose
// instance-length value is less than or equal to its last-byte-pos value,
// is invalid. The recipient of an invalid byte-content-range- spec MUST
// ignore it and any content transferred along with it.
if r.End <= r.Start {
return nil, errInvalidRange
}
}
if parts[1] == "*" {
r.Size = -1
return r, nil
} else if size, err := strconv.ParseInt(parts[1], 10, 64); err == nil {
r.Size = size
return r, nil
}
return nil, errInvalidRange
}

View File

@ -0,0 +1,54 @@
package main
import (
"regexp"
"testing"
)
var ContentRangeTests = []struct {
s string
want contentRange
err string
}{
{s: "bytes 0-5/100", want: contentRange{Start: 0, End: 5, Size: 100}},
{s: "bytes 5-20/30", want: contentRange{Start: 5, End: 20, Size: 30}},
{s: "bytes */100", want: contentRange{Start: 0, End: -1, Size: 100}},
{s: "bytes 5-20/*", want: contentRange{Start: 5, End: 20, Size: -1}},
{s: "bytes */*", want: contentRange{Start: 0, End: -1, Size: -1}},
{s: "bytes 0-2147483647/2147483648", want: contentRange{Start: 0, End: 2147483647, Size: 2147483648}},
{s: "bytes 5-20", err: "invalid"},
{s: "bytes 5-5/100", err: "invalid"},
{s: "bytes 5-4/100", err: "invalid"},
{s: "bytes ", err: "invalid"},
{s: "", err: "invalid"},
}
func TestParseContentRange(t *testing.T) {
for _, test := range ContentRangeTests {
t.Logf("testing: %s", test.s)
r, err := parseContentRange(test.s)
if test.err != "" {
if err == nil {
t.Errorf("got no error, but expected: %s", test.err)
continue
}
errMatch := regexp.MustCompile(test.err)
if !errMatch.MatchString(err.Error()) {
t.Errorf("unexpected error: %s, wanted: %s", err, test.err)
continue
}
continue
} else if err != nil {
t.Errorf("unexpected error: %s, wanted: %+v", err, test.want)
continue
}
if *r != test.want {
t.Errorf("got: %+v, wanted: %+v", r, test.want)
continue
}
}
}

View File

@ -31,9 +31,14 @@ func reply(w http.ResponseWriter, code int, message string) {
}
func createFile(w http.ResponseWriter, r *http.Request) {
contentRange := r.Header.Get("Content-Range")
if contentRange == "" {
reply(w, http.StatusBadRequest, "Content-Range header is required")
contentRange, err := parseContentRange(r.Header.Get("Content-Range"))
if err != nil {
reply(w, http.StatusBadRequest, err.Error())
return
}
if contentRange.End != -1 {
reply(w, http.StatusNotImplemented, "File data in initial request.")
return
}
@ -43,4 +48,5 @@ func createFile(w http.ResponseWriter, r *http.Request) {
}
log.Printf("contentType: %s", contentType)
log.Printf("range: %#v", contentRange)
}