Parse content ranges
This commit is contained in:
parent
3573790185
commit
836c5c69f0
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue