Skip to content
This repository has been archived by the owner on Dec 8, 2020. It is now read-only.

Commit

Permalink
Merge pull request #3 from puppetlabs/range
Browse files Browse the repository at this point in the history
Add `ScanRangeHeader` API.
  • Loading branch information
brimworks authored Jul 26, 2019
2 parents 2ae58ea + 254a9dc commit 0c5b550
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 0 deletions.
108 changes: 108 additions & 0 deletions httputil/api/range.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package api

import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)

type RangeErrorCode string

const (
InvalidRangeHeader RangeErrorCode = "InvalidRangeHeader"
UnsupportedRangeUnit RangeErrorCode = "UnsupportedRangeUnit"
UnsatisfiableRange RangeErrorCode = "UnsatisfiableRange"
)

type RangeError struct {
Code RangeErrorCode
Message string
}

func (e *RangeError) Error() string {
return e.Message
}

var ErrRangeHeaderInvalid = errors.New("Invalid Range header")

type RangeSpec struct {
First *int64
Last *int64
// If non-null First/Last are null.
SuffixLength *int64
}

type RangeHeader struct {
Unit string // always "bytes" for now
Specs []RangeSpec
}

var rangeSpecRegex = regexp.MustCompile(`^\s*(\d*)\s*-\s*(\d*)\s*$`)

func ScanRangeHeader(header string) (*RangeHeader, error) {
if len(header) == 0 {
return nil, nil
}
eq := strings.Index(header, "=")
if eq < 1 {
return nil, &RangeError{
Code: InvalidRangeHeader,
Message: "Expected Range header to begin with `bytes=`",
}
}
unit := strings.TrimSpace(header[0:eq])
if "bytes" != unit {
return nil, &RangeError{
Code: UnsupportedRangeUnit,
Message: fmt.Sprintf("Unsupported Range header unit=%s", unit),
}
}
specStrs := strings.Split(header[eq+1:], ",")
specs := make([]RangeSpec, len(specStrs))
for i, specStr := range specStrs {
matches := rangeSpecRegex.FindStringSubmatch(specStr)
if len(matches) <= 0 {
return nil, &RangeError{
Code: InvalidRangeHeader,
Message: fmt.Sprintf(
"Invalid Range header, expected %s to be digits followed by '-' followed by digits", specStr),
}
}
var first, last, suffixLength *int64
if len(matches[1]) > 0 {
v, _ := strconv.ParseInt(matches[1], 10, 64)
first = &v
if len(matches[2]) > 0 {
v2, _ := strconv.ParseInt(matches[2], 10, 64)
if v2 < *first {
return nil, &RangeError{
Code: UnsatisfiableRange,
Message: fmt.Sprintf(
`Unsatisfiable byte range %s`, specStr),
}
}
last = &v2
}
} else if len(matches[2]) > 0 {
v, _ := strconv.ParseInt(matches[2], 10, 64)
suffixLength = &v
} else {
return nil, &RangeError{
Code: InvalidRangeHeader,
Message: "Invalid Range header, expected more than just a '-'",
}
}
specs[i] = RangeSpec{
First: first,
Last: last,
SuffixLength: suffixLength,
}

}
return &RangeHeader{
Unit: unit,
Specs: specs,
}, nil
}
73 changes: 73 additions & 0 deletions httputil/api/range_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package api

import (
"testing"

"github.com/stretchr/testify/assert"
)

func newInt64(x int64) *int64 {
return &x
}

func TestScanRangeHeader(t *testing.T) {
tests := []struct {
Header string
Error error
Expected *RangeHeader
}{
{
Header: ` bytes = 1 - 3 , 10 - `,
Expected: &RangeHeader{
Unit: "bytes",
Specs: []RangeSpec{
{
First: newInt64(1),
Last: newInt64(3),
},
{
First: newInt64(10),
},
},
},
},
{
Header: ` bytes = - 1000`,
Expected: &RangeHeader{
Unit: "bytes",
Specs: []RangeSpec{
{
SuffixLength: newInt64(1000),
},
},
},
},
{
Header: `bytes=10-1`,
Error: &RangeError{
Code: UnsatisfiableRange,
Message: `Unsatisfiable byte range 10-1`,
},
},
{
Header: ` lines =1-2`,
Error: &RangeError{
Code: UnsupportedRangeUnit,
Message: `Unsupported Range header unit=lines`,
},
},
{
Header: `bytes=-`,
Error: &RangeError{
Code: InvalidRangeHeader,
Message: `Invalid Range header, expected more than just a '-'`,
},
},
}
for _, test := range tests {
spec, err := ScanRangeHeader(test.Header)

assert.Equal(t, test.Error, err, "for header %q", test.Header)
assert.Equal(t, test.Expected, spec, "for header %q", test.Header)
}
}

0 comments on commit 0c5b550

Please sign in to comment.