diff --git a/ctx.go b/ctx.go index 2a99b838..ea7d5bff 100644 --- a/ctx.go +++ b/ctx.go @@ -197,73 +197,22 @@ func (app *App) ReleaseCtx(c *Ctx) { // Accepts checks if the specified extensions or content types are acceptable. func (c *Ctx) Accepts(offers ...string) string { - if len(offers) == 0 { - 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: / - return offer - } - - s := strings.IndexByte(mimetype, '/') - // Accept: /* - if strings.HasPrefix(spec, mimetype[:s]) && (spec[s:] == "/*" || mimetype[s:] == "/*") { - return offer - } - } - if commaPos != -1 { - header = header[commaPos+1:] - } - } - - return "" + return getOffer(c.Get(HeaderAccept), acceptsOfferType, offers...) } // AcceptsCharsets checks if the specified charset is acceptable. 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. 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. 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 diff --git a/ctx_test.go b/ctx_test.go index 0bbd8351..2a67302f 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -66,14 +66,27 @@ func Benchmark_Ctx_Accepts(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - c.Request().Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9") - var res string - b.ReportAllocs() - b.ResetTimer() - for n := 0; n < b.N; n++ { - res = c.Accepts(".xml") + acceptHeader := "text/html,application/xhtml+xml,application/xml;q=0.9" + c.Request().Header.Set("Accept", acceptHeader) + acceptValues := [][]string{ + {".xml"}, + {"json", "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 diff --git a/helpers.go b/helpers.go index ec7bd239..d792b0dc 100644 --- a/helpers.go +++ b/helpers.go @@ -215,36 +215,82 @@ func getGroupPath(prefix, path string) string { return utils.TrimRight(prefix, '/') + path } -// return valid offer for header negotiation -func getOffer(header string, offers ...string) string { +// acceptsOffer This function determines if an offer matches a given specification. +// 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 /* 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: / + return true + } + + s := strings.IndexByte(mimetype, '/') + // Accept: /* + 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 { return "" } else 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 = header - } - if factorSign := strings.IndexByte(spec, ';'); factorSign != -1 { - spec = spec[:factorSign] + for _, offer := range offers { + if len(offer) == 0 { + continue } + 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 { - // has star prefix - if len(spec) >= 1 && spec[len(spec)-1] == '*' { - return offer - } else if strings.HasPrefix(spec, offer) { + // isAccepted if the current offer is accepted + if isAccepted(spec, offer) { return offer } - } - if commaPos != -1 { - header = header[commaPos+1:] + + if commaPos != -1 { + header = header[commaPos+1:] + } } } diff --git a/helpers_test.go b/helpers_test.go index 7bb394e5..389ff416 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -223,9 +223,9 @@ func Test_Utils_Parse_Address(t *testing.T) { func Test_Utils_GetOffset(t *testing.T) { t.Parallel() - utils.AssertEqual(t, "", getOffer("hello")) - utils.AssertEqual(t, "1", getOffer("", "1")) - utils.AssertEqual(t, "", getOffer("2", "1")) + utils.AssertEqual(t, "", getOffer("hello", acceptsOffer)) + utils.AssertEqual(t, "1", getOffer("", acceptsOffer, "1")) + utils.AssertEqual(t, "", getOffer("2", acceptsOffer, "1")) } func Test_Utils_TestConn_Deadline(t *testing.T) {