Skip to content

Commit

Permalink
added support for fasthttp (but broke compatibility with previous ver…
Browse files Browse the repository at this point in the history
…sions by moving stuff to different packages)

Should solve #1
  • Loading branch information
Per Abich committed Dec 29, 2020
1 parent bd05e79 commit eed21e8
Show file tree
Hide file tree
Showing 9 changed files with 814 additions and 384 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
*.iml
178 changes: 12 additions & 166 deletions contenttype.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package contenttype

import (
"errors"
"net/http"
"strings"
)

Expand Down Expand Up @@ -73,7 +72,7 @@ func isQuotedPairChar(c byte) bool {
isObsoleteTextChar(c)
}

func skipWhiteSpaces(s string) string {
func SkipWhiteSpaces(s string) string {
// RFC 7230, 3.2.3. Whitespace
for i := 0; i < len(s); i++ {
if !isWhiteSpaceChar(s[i]) {
Expand Down Expand Up @@ -116,9 +115,9 @@ func consumeQuotedString(s string) (token, remaining string, consumed bool) {
return strings.ToLower(stringBuilder.String()), s[index:], true
}

func consumeType(s string) (string, string, string, bool) {
func ConsumeType(s string) (string, string, string, bool) {
// RFC 7231, 3.1.1.1. Media Type
s = skipWhiteSpaces(s)
s = SkipWhiteSpaces(s)

var t, subt string
var consumed bool
Expand All @@ -142,14 +141,14 @@ func consumeType(s string) (string, string, string, bool) {
return "", "", s, false
}

s = skipWhiteSpaces(s)
s = SkipWhiteSpaces(s)

return t, subt, s, true
}

func consumeParameter(s string) (string, string, string, bool) {
func ConsumeParameter(s string) (string, string, string, bool) {
// RFC 7231, 3.1.1.1. Media Type
s = skipWhiteSpaces(s)
s = SkipWhiteSpaces(s)

var consumed bool
var key string
Expand Down Expand Up @@ -186,12 +185,12 @@ func consumeParameter(s string) (string, string, string, bool) {
}
}

s = skipWhiteSpaces(s)
s = SkipWhiteSpaces(s)

return key, value, s, true
}

func getWeight(s string) (int, bool) {
func GetWeight(s string) (int, bool) {
// RFC 7231, 5.3.1. Quality Values
result := 0
multiplier := 1000
Expand Down Expand Up @@ -230,7 +229,7 @@ func getWeight(s string) (int, bool) {
return result, true
}

func compareMediaTypes(checkMediaType, mediaType MediaType) bool {
func CompareMediaTypes(checkMediaType, mediaType MediaType) bool {
if (checkMediaType.Type == "*" || checkMediaType.Type == mediaType.Type) &&
(checkMediaType.Subtype == "*" || checkMediaType.Subtype == mediaType.Subtype) {

Expand All @@ -246,7 +245,7 @@ func compareMediaTypes(checkMediaType, mediaType MediaType) bool {
return false
}

func getPrecedence(checkMediaType, mediaType MediaType) bool {
func GetPrecedence(checkMediaType, mediaType MediaType) bool {
if len(mediaType.Type) == 0 || len(mediaType.Subtype) == 0 { // not set
return true
}
Expand All @@ -264,7 +263,7 @@ func getPrecedence(checkMediaType, mediaType MediaType) bool {
func NewMediaType(s string) MediaType {
mediaType := MediaType{}
var consumed bool
mediaType.Type, mediaType.Subtype, s, consumed = consumeType(s)
mediaType.Type, mediaType.Subtype, s, consumed = ConsumeType(s)
if !consumed {
return MediaType{}
}
Expand All @@ -274,7 +273,7 @@ func NewMediaType(s string) MediaType {
for len(s) > 0 && s[0] == ';' {
s = s[1:] // skip the semicolon

key, value, remaining, consumed := consumeParameter(s)
key, value, remaining, consumed := ConsumeParameter(s)
if !consumed {
return MediaType{}
}
Expand Down Expand Up @@ -306,156 +305,3 @@ func (mediaType *MediaType) String() string {

return stringBuilder.String()
}

// Gets the content of Content-Type header, parses it, and returns the parsed MediaType
// If the request does not contain the Content-Type header, an empty MediaType is returned
func GetMediaType(request *http.Request) (MediaType, error) {
// RFC 7231, 3.1.1.5. Content-Type
contentTypeHeaders := request.Header.Values("Content-Type")
if len(contentTypeHeaders) == 0 {
return MediaType{}, nil
}

s := contentTypeHeaders[0]
mediaType := MediaType{}
var consumed bool
mediaType.Type, mediaType.Subtype, s, consumed = consumeType(s)
if !consumed {
return MediaType{}, ErrInvalidMediaType
}

mediaType.Parameters = make(Parameters)

for len(s) > 0 && s[0] == ';' {
s = s[1:] // skip the semicolon

key, value, remaining, consumed := consumeParameter(s)
if !consumed {
return MediaType{}, ErrInvalidParameter
}

s = remaining

mediaType.Parameters[key] = value
}

// there must not be anything left after parsing the header
if len(s) > 0 {
return MediaType{}, ErrInvalidMediaType
}

return mediaType, nil
}

// Choses a media type from available media types according to the Accept
// Returns the most suitable media type or an error if no type can be selected
func GetAcceptableMediaType(request *http.Request, availableMediaTypes []MediaType) (MediaType, Parameters, error) {
// RFC 7231, 5.3.2. Accept
if len(availableMediaTypes) == 0 {
return MediaType{}, Parameters{}, ErrNoAvailableTypeGiven
}

acceptHeaders := request.Header.Values("Accept")
if len(acceptHeaders) == 0 {
return availableMediaTypes[0], Parameters{}, nil
}

s := acceptHeaders[0]

weights := make([]struct {
mediaType MediaType
extensionParameters Parameters
weight int
order int
}, len(availableMediaTypes))

for mediaTypeCount := 0; len(s) > 0; mediaTypeCount++ {
if mediaTypeCount > 0 {
// every media type after the first one must start with a comma
if s[0] != ',' {
break
}
s = s[1:] // skip the comma
}

acceptableMediaType := MediaType{}
var consumed bool
acceptableMediaType.Type, acceptableMediaType.Subtype, s, consumed = consumeType(s)
if !consumed {
return MediaType{}, Parameters{}, ErrInvalidMediaType
}

acceptableMediaType.Parameters = make(Parameters)
weight := 1000 // 1.000

// media type parameters
for len(s) > 0 && s[0] == ';' {
s = s[1:] // skip the semicolon

var key, value string
key, value, s, consumed = consumeParameter(s)
if !consumed {
return MediaType{}, Parameters{}, ErrInvalidParameter
}

if key == "q" {
weight, consumed = getWeight(value)
if !consumed {
return MediaType{}, Parameters{}, ErrInvalidWeight
}
break // "q" parameter separates media type parameters from Accept extension parameters
}

acceptableMediaType.Parameters[key] = value
}

extensionParameters := make(Parameters)
for len(s) > 0 && s[0] == ';' {
s = s[1:] // skip the semicolon

key, value, remaining, consumed := consumeParameter(s)
if !consumed {
return MediaType{}, Parameters{}, ErrInvalidParameter
}

s = remaining

extensionParameters[key] = value
}

for i := 0; i < len(availableMediaTypes); i++ {
if compareMediaTypes(acceptableMediaType, availableMediaTypes[i]) &&
getPrecedence(acceptableMediaType, weights[i].mediaType) {
weights[i].mediaType = acceptableMediaType
weights[i].extensionParameters = extensionParameters
weights[i].weight = weight
weights[i].order = mediaTypeCount
}
}

s = skipWhiteSpaces(s)
}

// there must not be anything left after parsing the header
if len(s) > 0 {
return MediaType{}, Parameters{}, ErrInvalidMediaRange
}

resultIndex := -1
for i := 0; i < len(availableMediaTypes); i++ {
if resultIndex != -1 {
if weights[i].weight > weights[resultIndex].weight ||
(weights[i].weight == weights[resultIndex].weight && weights[i].order < weights[resultIndex].order) {
resultIndex = i
}
} else if weights[i].weight > 0 {
resultIndex = i
}
}

if resultIndex == -1 {
return MediaType{}, Parameters{}, ErrNoAcceptableTypeFound
}

return availableMediaTypes[resultIndex], weights[resultIndex].extensionParameters, nil
}
Loading

0 comments on commit eed21e8

Please sign in to comment.