From 836c5c69f02b7e90492b037cd22dda8b2310956c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisendo=CC=88rfer?= Date: Sun, 17 Mar 2013 16:13:32 +0100 Subject: [PATCH] Parse content ranges --- src/cmd/tusd/content_range.go | 74 ++++++++++++++++++++++++++++++ src/cmd/tusd/content_range_test.go | 54 ++++++++++++++++++++++ src/cmd/tusd/main.go | 12 +++-- 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 src/cmd/tusd/content_range.go create mode 100644 src/cmd/tusd/content_range_test.go diff --git a/src/cmd/tusd/content_range.go b/src/cmd/tusd/content_range.go new file mode 100644 index 0000000..c1ccc5d --- /dev/null +++ b/src/cmd/tusd/content_range.go @@ -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 +} diff --git a/src/cmd/tusd/content_range_test.go b/src/cmd/tusd/content_range_test.go new file mode 100644 index 0000000..680b7cd --- /dev/null +++ b/src/cmd/tusd/content_range_test.go @@ -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 + } + } +} diff --git a/src/cmd/tusd/main.go b/src/cmd/tusd/main.go index 3d3e28f..a5b071f 100644 --- a/src/cmd/tusd/main.go +++ b/src/cmd/tusd/main.go @@ -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) }