Fix #2383, accepts mimeType (#2386)

* Fix #2383, accepts mimeType

* Fix #2383, accepts mimeType

* Fix #2383, accepts mimeType
pull/2391/head
RW 2023-03-27 15:55:41 +02:00 committed by GitHub
parent c6e86ac906
commit 28d9abb71b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 85 deletions

59
ctx.go
View File

@ -197,73 +197,22 @@ func (app *App) ReleaseCtx(c *Ctx) {
// Accepts checks if the specified extensions or content types are acceptable. // Accepts checks if the specified extensions or content types are acceptable.
func (c *Ctx) Accepts(offers ...string) string { func (c *Ctx) Accepts(offers ...string) string {
if len(offers) == 0 { return getOffer(c.Get(HeaderAccept), acceptsOfferType, offers...)
return ""
}
header := c.Get(HeaderAccept)
if header == "" {
return offers[0]
}
spec, commaPos := "", 0
for len(header) > 0 && commaPos != -1 {
commaPos = strings.IndexByte(header, ',')
if commaPos != -1 {
spec = utils.Trim(header[:commaPos], ' ')
} else {
spec = utils.TrimLeft(header, ' ')
}
if factorSign := strings.IndexByte(spec, ';'); factorSign != -1 {
spec = spec[:factorSign]
}
var mimetype string
for _, offer := range offers {
if len(offer) == 0 {
continue
// Accept: */*
} else if spec == "*/*" {
return offer
}
if strings.IndexByte(offer, '/') != -1 {
mimetype = offer // MIME type
} else {
mimetype = utils.GetMIME(offer) // extension
}
if spec == mimetype {
// Accept: <MIME_type>/<MIME_subtype>
return offer
}
s := strings.IndexByte(mimetype, '/')
// Accept: <MIME_type>/*
if strings.HasPrefix(spec, mimetype[:s]) && (spec[s:] == "/*" || mimetype[s:] == "/*") {
return offer
}
}
if commaPos != -1 {
header = header[commaPos+1:]
}
}
return ""
} }
// AcceptsCharsets checks if the specified charset is acceptable. // AcceptsCharsets checks if the specified charset is acceptable.
func (c *Ctx) AcceptsCharsets(offers ...string) string { func (c *Ctx) AcceptsCharsets(offers ...string) string {
return getOffer(c.Get(HeaderAcceptCharset), offers...) return getOffer(c.Get(HeaderAcceptCharset), acceptsOffer, offers...)
} }
// AcceptsEncodings checks if the specified encoding is acceptable. // AcceptsEncodings checks if the specified encoding is acceptable.
func (c *Ctx) AcceptsEncodings(offers ...string) string { func (c *Ctx) AcceptsEncodings(offers ...string) string {
return getOffer(c.Get(HeaderAcceptEncoding), offers...) return getOffer(c.Get(HeaderAcceptEncoding), acceptsOffer, offers...)
} }
// AcceptsLanguages checks if the specified language is acceptable. // AcceptsLanguages checks if the specified language is acceptable.
func (c *Ctx) AcceptsLanguages(offers ...string) string { func (c *Ctx) AcceptsLanguages(offers ...string) string {
return getOffer(c.Get(HeaderAcceptLanguage), offers...) return getOffer(c.Get(HeaderAcceptLanguage), acceptsOffer, offers...)
} }
// App returns the *App reference to the instance of the Fiber application // App returns the *App reference to the instance of the Fiber application

View File

@ -66,14 +66,27 @@ func Benchmark_Ctx_Accepts(b *testing.B) {
app := New() app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c) defer app.ReleaseCtx(c)
c.Request().Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9") acceptHeader := "text/html,application/xhtml+xml,application/xml;q=0.9"
var res string c.Request().Header.Set("Accept", acceptHeader)
b.ReportAllocs() acceptValues := [][]string{
b.ResetTimer() {".xml"},
for n := 0; n < b.N; n++ { {"json", "xml"},
res = c.Accepts(".xml") {"application/json", "application/xml"},
}
expectedResults := []string{".xml", "xml", "application/xml"}
for i := 0; i < len(acceptValues); i++ {
b.Run(fmt.Sprintf("run-%#v", acceptValues[i]), func(bb *testing.B) {
var res string
bb.ReportAllocs()
bb.ResetTimer()
for n := 0; n < bb.N; n++ {
res = c.Accepts(acceptValues[i]...)
}
utils.AssertEqual(bb, expectedResults[i], res)
})
} }
utils.AssertEqual(b, ".xml", res)
} }
// go test -run Test_Ctx_Accepts_EmptyAccept // go test -run Test_Ctx_Accepts_EmptyAccept

View File

@ -215,36 +215,82 @@ func getGroupPath(prefix, path string) string {
return utils.TrimRight(prefix, '/') + path return utils.TrimRight(prefix, '/') + path
} }
// return valid offer for header negotiation // acceptsOffer This function determines if an offer matches a given specification.
func getOffer(header string, offers ...string) string { // It checks if the specification ends with a '*' or if the offer has the prefix of the specification.
// Returns true if the offer matches the specification, false otherwise.
func acceptsOffer(spec, offer string) bool {
if len(spec) >= 1 && spec[len(spec)-1] == '*' {
return true
} else if strings.HasPrefix(spec, offer) {
return true
}
return false
}
// acceptsOfferType This function determines if an offer type matches a given specification.
// It checks if the specification is equal to */* (i.e., all types are accepted).
// It gets the MIME type of the offer (either from the offer itself or by its file extension).
// It checks if the offer MIME type matches the specification MIME type or if the specification is of the form <MIME_type>/* and the offer MIME type has the same MIME type.
// Returns true if the offer type matches the specification, false otherwise.
func acceptsOfferType(spec, offerType string) bool {
// Accept: */*
if spec == "*/*" {
return true
}
var mimetype string
if strings.IndexByte(offerType, '/') != -1 {
mimetype = offerType // MIME type
} else {
mimetype = utils.GetMIME(offerType) // extension
}
if spec == mimetype {
// Accept: <MIME_type>/<MIME_subtype>
return true
}
s := strings.IndexByte(mimetype, '/')
// Accept: <MIME_type>/*
if strings.HasPrefix(spec, mimetype[:s]) && (spec[s:] == "/*" || mimetype[s:] == "/*") {
return true
}
return false
}
// getOffer return valid offer for header negotiation
func getOffer(header string, isAccepted func(spec, offer string) bool, offers ...string) string {
if len(offers) == 0 { if len(offers) == 0 {
return "" return ""
} else if header == "" { } else if header == "" {
return offers[0] return offers[0]
} }
spec, commaPos := "", 0 for _, offer := range offers {
for len(header) > 0 && commaPos != -1 { if len(offer) == 0 {
commaPos = strings.IndexByte(header, ',') continue
if commaPos != -1 {
spec = utils.Trim(header[:commaPos], ' ')
} else {
spec = header
}
if factorSign := strings.IndexByte(spec, ';'); factorSign != -1 {
spec = spec[:factorSign]
} }
spec, commaPos := "", 0
for len(header) > 0 && commaPos != -1 {
commaPos = strings.IndexByte(header, ',')
if commaPos != -1 {
spec = utils.Trim(header[:commaPos], ' ')
} else {
spec = utils.TrimLeft(header, ' ')
}
if factorSign := strings.IndexByte(spec, ';'); factorSign != -1 {
spec = spec[:factorSign]
}
for _, offer := range offers { // isAccepted if the current offer is accepted
// has star prefix if isAccepted(spec, offer) {
if len(spec) >= 1 && spec[len(spec)-1] == '*' {
return offer
} else if strings.HasPrefix(spec, offer) {
return offer return offer
} }
}
if commaPos != -1 { if commaPos != -1 {
header = header[commaPos+1:] header = header[commaPos+1:]
}
} }
} }

View File

@ -223,9 +223,9 @@ func Test_Utils_Parse_Address(t *testing.T) {
func Test_Utils_GetOffset(t *testing.T) { func Test_Utils_GetOffset(t *testing.T) {
t.Parallel() t.Parallel()
utils.AssertEqual(t, "", getOffer("hello")) utils.AssertEqual(t, "", getOffer("hello", acceptsOffer))
utils.AssertEqual(t, "1", getOffer("", "1")) utils.AssertEqual(t, "1", getOffer("", acceptsOffer, "1"))
utils.AssertEqual(t, "", getOffer("2", "1")) utils.AssertEqual(t, "", getOffer("2", acceptsOffer, "1"))
} }
func Test_Utils_TestConn_Deadline(t *testing.T) { func Test_Utils_TestConn_Deadline(t *testing.T) {