2020-05-06 15:59:40 +02:00
// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
2020-05-07 20:15:27 +02:00
// 🤖 Github Repository: https://github.com/gofiber/fiber
2020-05-06 15:59:40 +02:00
// 📌 API Documentation: https://docs.gofiber.io
2020-02-21 18:06:08 +01:00
2019-12-30 07:29:42 -05:00
package fiber
import (
2020-02-05 17:37:04 +01:00
"bytes"
2020-09-13 11:20:11 +02:00
"crypto/tls"
2024-02-19 05:33:10 -08:00
"errors"
2020-02-12 04:02:58 +01:00
"fmt"
2020-07-29 11:07:47 +08:00
"io"
2020-02-05 17:37:04 +01:00
"net"
2020-07-29 11:07:47 +08:00
"os"
"path/filepath"
2020-09-13 11:20:11 +02:00
"reflect"
2019-12-30 07:29:42 -05:00
"strings"
2024-02-19 06:28:06 -08:00
"sync"
2020-02-05 17:37:04 +01:00
"time"
2020-09-13 11:20:11 +02:00
"unsafe"
2020-05-16 05:13:01 +02:00
2023-11-07 20:22:31 +03:00
"github.com/gofiber/fiber/v3/log"
2023-08-05 22:02:01 +03:00
"github.com/gofiber/utils/v2"
2023-01-27 09:01:37 +01:00
2022-06-02 17:37:53 +03:00
"github.com/valyala/bytebufferpool"
2020-09-13 11:20:11 +02:00
"github.com/valyala/fasthttp"
2020-05-23 09:30:21 +02:00
)
2020-05-07 22:49:26 +02:00
2023-06-07 12:51:45 -03:00
// acceptType is a struct that holds the parsed value of an Accept header
2023-11-06 23:25:23 -08:00
// along with quality, specificity, parameters, and order.
// Used for sorting accept headers.
2023-06-07 12:51:45 -03:00
type acceptedType struct {
spec string
quality float64
specificity int
order int
2024-02-19 06:28:06 -08:00
params headerParams
2023-06-07 12:51:45 -03:00
}
2024-02-19 06:28:06 -08:00
type headerParams map [ string ] [ ] byte
2023-01-27 09:01:37 +01:00
// getTLSConfig returns a net listener's tls config
func getTLSConfig ( ln net . Listener ) * tls . Config {
2020-09-13 11:20:11 +02:00
// Get listener type
pointer := reflect . ValueOf ( ln )
// Is it a tls.listener?
2024-02-19 05:33:10 -08:00
if pointer . String ( ) != "<*tls.listener Value>" {
return nil
}
// Copy value from pointer
if val := reflect . Indirect ( pointer ) ; val . Type ( ) != nil {
// Get private field from value
if field := val . FieldByName ( "config" ) ; field . Type ( ) != nil {
// Copy value from pointer field (unsafe)
newval := reflect . NewAt ( field . Type ( ) , unsafe . Pointer ( field . UnsafeAddr ( ) ) ) //nolint:gosec // Probably the only way to extract the *tls.Config from a net.Listener. TODO: Verify there really is no easier way without using unsafe.
if newval . Type ( ) == nil {
return nil
}
// Get element from pointer
if elem := newval . Elem ( ) ; elem . Type ( ) != nil {
// Cast value to *tls.Config
c , ok := elem . Interface ( ) . ( * tls . Config )
if ! ok {
panic ( errors . New ( "failed to type-assert to *tls.Config" ) )
2020-09-13 11:20:11 +02:00
}
2024-02-19 05:33:10 -08:00
return c
2020-09-13 11:20:11 +02:00
}
}
}
2021-02-23 16:40:17 +08:00
return nil
2020-09-13 11:20:11 +02:00
}
2020-07-29 11:07:47 +08:00
// readContent opens a named file and read content from it
2023-01-27 09:01:37 +01:00
func readContent ( rf io . ReaderFrom , name string ) ( int64 , error ) {
2020-07-29 11:07:47 +08:00
// Read file
f , err := os . Open ( filepath . Clean ( name ) )
if err != nil {
2023-01-27 09:01:37 +01:00
return 0 , fmt . Errorf ( "failed to open: %w" , err )
2020-07-29 11:07:47 +08:00
}
2020-07-29 11:29:27 +08:00
defer func ( ) {
2021-10-18 08:46:02 +02:00
if err = f . Close ( ) ; err != nil {
2023-06-26 14:16:57 +08:00
log . Errorf ( "Error closing file: %s" , err )
2021-10-18 08:46:02 +02:00
}
2020-07-29 11:29:27 +08:00
} ( )
2023-01-27 09:01:37 +01:00
if n , err := rf . ReadFrom ( f ) ; err != nil {
return n , fmt . Errorf ( "failed to read: %w" , err )
}
return 0 , nil
2020-07-29 11:07:47 +08:00
}
2020-07-14 12:21:55 +02:00
// quoteString escape special characters in a given string
2021-05-05 17:24:26 -03:00
func ( app * App ) quoteString ( raw string ) string {
2020-07-13 12:37:50 +08:00
bb := bytebufferpool . Get ( )
2021-05-05 17:24:26 -03:00
quoted := app . getString ( fasthttp . AppendQuotedArg ( bb . B , app . getBytes ( raw ) ) )
2020-07-13 12:37:50 +08:00
bytebufferpool . Put ( bb )
return quoted
}
2020-09-13 11:20:11 +02:00
// Scan stack if other methods match the request
2023-02-05 23:43:42 +03:00
func ( app * App ) methodExist ( c * DefaultCtx ) bool {
2023-01-27 09:01:37 +01:00
var exists bool
2023-02-05 23:43:42 +03:00
2022-11-11 14:23:30 +07:00
methods := app . config . RequestMethods
for i := 0 ; i < len ( methods ) ; i ++ {
2022-08-07 14:53:01 +03:00
// Skip original method
if c . getMethodINT ( ) == i {
continue
}
// Reset stack index
c . setIndexRoute ( - 1 )
tree , ok := c . App ( ) . treeStack [ i ] [ c . getTreePath ( ) ]
if ! ok {
tree = c . App ( ) . treeStack [ i ] [ "" ]
}
// Get stack length
lenr := len ( tree ) - 1
// Loop over the route stack starting from previous index
for c . getIndexRoute ( ) < lenr {
// Increment route index
c . setIndexRoute ( c . getIndexRoute ( ) + 1 )
// Get *Route
route := tree [ c . getIndexRoute ( ) ]
// Skip use routes
if route . use {
continue
}
// Check if it matches the request path
match := route . match ( c . getDetectionPath ( ) , c . Path ( ) , c . getValues ( ) )
// No match, next route
if match {
// We matched
2023-01-27 09:01:37 +01:00
exists = true
2022-08-07 14:53:01 +03:00
// Add method to Allow header
2022-11-27 20:34:48 +03:00
c . Append ( HeaderAllow , methods [ i ] )
2022-08-07 14:53:01 +03:00
// Break stack loop
break
}
}
}
2023-02-05 23:43:42 +03:00
return exists
2022-08-07 14:53:01 +03:00
}
// Scan stack if other methods match the request
2023-02-05 23:43:42 +03:00
func ( app * App ) methodExistCustom ( c CustomCtx ) bool {
var exists bool
2022-11-27 20:34:48 +03:00
methods := app . config . RequestMethods
for i := 0 ; i < len ( methods ) ; i ++ {
2020-06-20 17:26:48 +02:00
// Skip original method
2022-07-13 08:48:29 +03:00
if c . getMethodINT ( ) == i {
2020-06-20 17:26:48 +02:00
continue
}
// Reset stack index
2022-07-13 08:48:29 +03:00
c . setIndexRoute ( - 1 )
tree , ok := c . App ( ) . treeStack [ i ] [ c . getTreePath ( ) ]
2020-08-09 21:53:15 +02:00
if ! ok {
2022-07-13 08:48:29 +03:00
tree = c . App ( ) . treeStack [ i ] [ "" ]
2020-08-09 21:53:15 +02:00
}
2020-06-20 17:26:48 +02:00
// Get stack length
2020-08-09 21:53:15 +02:00
lenr := len ( tree ) - 1
2021-11-05 08:00:03 +01:00
// Loop over the route stack starting from previous index
2022-07-13 08:48:29 +03:00
for c . getIndexRoute ( ) < lenr {
2020-06-21 10:47:03 +02:00
// Increment route index
2022-07-13 08:48:29 +03:00
c . setIndexRoute ( c . getIndexRoute ( ) + 1 )
2020-06-20 17:26:48 +02:00
// Get *Route
2022-07-13 08:48:29 +03:00
route := tree [ c . getIndexRoute ( ) ]
2020-07-07 23:25:02 +08:00
// Skip use routes
if route . use {
continue
}
2020-06-20 17:26:48 +02:00
// Check if it matches the request path
2022-07-13 08:48:29 +03:00
match := route . match ( c . getDetectionPath ( ) , c . Path ( ) , c . getValues ( ) )
2020-06-20 17:26:48 +02:00
// No match, next route
if match {
2020-07-10 14:15:41 +02:00
// We matched
2023-02-05 23:43:42 +03:00
exists = true
2020-06-21 12:17:22 +02:00
// Add method to Allow header
2022-11-27 20:34:48 +03:00
c . Append ( HeaderAllow , methods [ i ] )
2020-06-21 11:03:17 +02:00
// Break stack loop
2020-06-20 17:26:48 +02:00
break
}
}
}
2023-01-27 09:01:37 +01:00
return exists
2020-09-13 11:20:11 +02:00
}
// uniqueRouteStack drop all not unique routes from the slice
func uniqueRouteStack ( stack [ ] * Route ) [ ] * Route {
var unique [ ] * Route
m := make ( map [ * Route ] int )
for _ , v := range stack {
if _ , ok := m [ v ] ; ! ok {
// Unique key found. Record position and collect
// in result.
m [ v ] = len ( unique )
unique = append ( unique , v )
}
2020-06-21 12:17:22 +02:00
}
2020-09-13 11:20:11 +02:00
return unique
2020-06-20 17:26:48 +02:00
}
2020-07-04 10:11:23 +02:00
// defaultString returns the value or a default value if it is set
func defaultString ( value string , defaultValue [ ] string ) string {
if len ( value ) == 0 && len ( defaultValue ) > 0 {
return defaultValue [ 0 ]
}
return value
}
2020-05-07 17:57:21 +02:00
func getGroupPath ( prefix , path string ) string {
2022-09-07 17:05:37 +08:00
if len ( path ) == 0 {
2020-05-23 09:30:21 +02:00
return prefix
2020-05-16 05:13:01 +02:00
}
2020-12-24 14:14:49 +08:00
2020-12-28 10:26:56 +05:45
if path [ 0 ] != '/' {
2020-12-24 14:14:49 +08:00
path = "/" + path
}
2022-08-20 13:52:09 +08:00
return strings . TrimRight ( prefix , "/" ) + path
2020-04-12 14:58:05 +02:00
}
2023-03-27 15:55:41 +02:00
// 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.
2024-02-19 06:28:06 -08:00
func acceptsOffer ( spec , offer string , _ headerParams ) bool {
2023-03-27 15:55:41 +02:00
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.
2023-11-06 23:25:23 -08:00
// It checks if the offer contains every parameter present in the specification.
2023-03-27 15:55:41 +02:00
// Returns true if the offer type matches the specification, false otherwise.
2024-02-19 06:28:06 -08:00
func acceptsOfferType ( spec , offerType string , specParams headerParams ) bool {
2023-11-06 23:25:23 -08:00
var offerMime , offerParams string
if i := strings . IndexByte ( offerType , ';' ) ; i == - 1 {
offerMime = offerType
} else {
offerMime = offerType [ : i ]
offerParams = offerType [ i : ]
}
2023-03-27 15:55:41 +02:00
// Accept: */*
if spec == "*/*" {
2023-11-06 23:25:23 -08:00
return paramsMatch ( specParams , offerParams )
2023-03-27 15:55:41 +02:00
}
var mimetype string
2023-11-06 23:25:23 -08:00
if strings . IndexByte ( offerMime , '/' ) != - 1 {
mimetype = offerMime // MIME type
2023-03-27 15:55:41 +02:00
} else {
2023-11-06 23:25:23 -08:00
mimetype = utils . GetMIME ( offerMime ) // extension
2023-03-27 15:55:41 +02:00
}
if spec == mimetype {
// Accept: <MIME_type>/<MIME_subtype>
2023-11-06 23:25:23 -08:00
return paramsMatch ( specParams , offerParams )
2023-03-27 15:55:41 +02:00
}
s := strings . IndexByte ( mimetype , '/' )
// Accept: <MIME_type>/*
if strings . HasPrefix ( spec , mimetype [ : s ] ) && ( spec [ s : ] == "/*" || mimetype [ s : ] == "/*" ) {
2023-11-06 23:25:23 -08:00
return paramsMatch ( specParams , offerParams )
2023-03-27 15:55:41 +02:00
}
return false
}
2023-11-06 23:25:23 -08:00
// paramsMatch returns whether offerParams contains all parameters present in specParams.
// Matching is case insensitive, and surrounding quotes are stripped.
// To align with the behavior of res.format from Express, the order of parameters is
// ignored, and if a parameter is specified twice in the incoming Accept, the last
// provided value is given precedence.
// In the case of quoted values, RFC 9110 says that we must treat any character escaped
// by a backslash as equivalent to the character itself (e.g., "a\aa" is equivalent to "aaa").
// For the sake of simplicity, we forgo this and compare the value as-is. Besides, it would
// be highly unusual for a client to escape something other than a double quote or backslash.
// See https://www.rfc-editor.org/rfc/rfc9110#name-parameters
2024-02-19 06:28:06 -08:00
func paramsMatch ( specParamStr headerParams , offerParams string ) bool {
if len ( specParamStr ) == 0 {
2023-11-06 23:25:23 -08:00
return true
}
allSpecParamsMatch := true
2024-02-19 06:28:06 -08:00
for specParam , specVal := range specParamStr {
2023-11-06 23:25:23 -08:00
foundParam := false
2024-02-19 06:28:06 -08:00
fasthttp . VisitHeaderParams ( utils . UnsafeBytes ( offerParams ) , func ( key , value [ ] byte ) bool {
if utils . EqualFold ( specParam , string ( key ) ) {
2023-11-06 23:25:23 -08:00
foundParam = true
2024-02-19 06:28:06 -08:00
allSpecParamsMatch = utils . EqualFold ( specVal , value )
2023-11-06 23:25:23 -08:00
return false
}
return true
} )
if ! foundParam || ! allSpecParamsMatch {
return false
}
}
2024-02-19 06:28:06 -08:00
2023-11-06 23:25:23 -08:00
return allSpecParamsMatch
}
2023-08-06 12:23:37 -03:00
// getSplicedStrList function takes a string and a string slice as an argument, divides the string into different
// elements divided by ',' and stores these elements in the string slice.
// It returns the populated string slice as an output.
//
// If the given slice hasn't enough space, it will allocate more and return.
func getSplicedStrList ( headerValue string , dst [ ] string ) [ ] string {
if headerValue == "" {
return nil
}
var (
index int
character rune
lastElementEndsAt uint8
insertIndex int
)
for index , character = range headerValue + "$" {
if character == ',' || index == len ( headerValue ) {
if insertIndex >= len ( dst ) {
oldSlice := dst
dst = make ( [ ] string , len ( dst ) + ( len ( dst ) >> 1 ) + 2 )
copy ( dst , oldSlice )
}
2023-11-07 20:22:31 +03:00
dst [ insertIndex ] = strings . TrimLeft ( headerValue [ lastElementEndsAt : index ] , " " )
2023-08-06 12:23:37 -03:00
lastElementEndsAt = uint8 ( index + 1 )
insertIndex ++
}
}
if len ( dst ) > insertIndex {
dst = dst [ : insertIndex ]
}
return dst
}
2023-11-06 23:25:23 -08:00
// forEachMediaRange parses an Accept or Content-Type header, calling functor
// on each media range.
// See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
2024-02-19 06:28:06 -08:00
func forEachMediaRange ( header [ ] byte , functor func ( [ ] byte ) ) {
hasDQuote := bytes . IndexByte ( header , '"' ) != - 1
2023-11-06 23:25:23 -08:00
for len ( header ) > 0 {
n := 0
2024-02-19 06:28:06 -08:00
header = bytes . TrimLeft ( header , " " )
2023-11-06 23:25:23 -08:00
quotes := 0
escaping := false
if hasDQuote {
// Complex case. We need to keep track of quotes and quoted-pairs (i.e., characters escaped with \ )
loop :
for n < len ( header ) {
switch header [ n ] {
case ',' :
if quotes % 2 == 0 {
break loop
}
case '"' :
if ! escaping {
quotes ++
}
case '\\' :
if quotes % 2 == 1 {
escaping = ! escaping
}
}
n ++
}
} else {
// Simple case. Just look for the next comma.
2024-02-19 06:28:06 -08:00
if n = bytes . IndexByte ( header , ',' ) ; n == - 1 {
2023-11-06 23:25:23 -08:00
n = len ( header )
}
}
functor ( header [ : n ] )
if n >= len ( header ) {
return
}
header = header [ n + 1 : ]
}
}
2024-02-19 06:28:06 -08:00
// Pool for headerParams instances. The headerParams object *must*
// be cleared before being returned to the pool.
var headerParamPool = sync . Pool {
New : func ( ) any {
return make ( headerParams )
} ,
2023-11-06 23:25:23 -08:00
}
2024-02-19 06:28:06 -08:00
// getOffer return valid offer for header negotiation.
// Do not pass header using utils.UnsafeBytes - this can cause a panic due
// to the use of utils.ToLowerBytes.
func getOffer ( header [ ] byte , isAccepted func ( spec , offer string , specParams headerParams ) bool , offers ... string ) string {
2020-05-12 19:24:04 +02:00
if len ( offers ) == 0 {
return ""
2023-06-07 12:51:45 -03:00
}
2024-02-19 06:28:06 -08:00
if len ( header ) == 0 {
2020-05-12 19:24:04 +02:00
return offers [ 0 ]
}
2023-11-06 23:25:23 -08:00
acceptedTypes := make ( [ ] acceptedType , 0 , 8 )
order := 0
2023-06-07 12:51:45 -03:00
// Parse header and get accepted types with their quality and specificity
// See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
2024-02-19 06:28:06 -08:00
forEachMediaRange ( header , func ( accept [ ] byte ) {
2023-06-07 12:51:45 -03:00
order ++
2024-02-19 06:28:06 -08:00
spec , quality := accept , 1.0
var params headerParams
2023-06-07 12:51:45 -03:00
2024-02-19 06:28:06 -08:00
if i := bytes . IndexByte ( accept , ';' ) ; i != - 1 {
2023-11-06 23:25:23 -08:00
spec = accept [ : i ]
2020-05-23 09:30:21 +02:00
2023-11-06 23:25:23 -08:00
// The vast majority of requests will have only the q parameter with
// no whitespace. Check this first to see if we can skip
// the more involved parsing.
2024-02-19 06:28:06 -08:00
if bytes . HasPrefix ( accept [ i : ] , [ ] byte ( ";q=" ) ) && bytes . IndexByte ( accept [ i + 3 : ] , ';' ) == - 1 {
if q , err := fasthttp . ParseUfloat ( bytes . TrimRight ( accept [ i + 3 : ] , " " ) ) ; err == nil {
2023-06-07 12:51:45 -03:00
quality = q
}
} else {
2024-02-19 06:28:06 -08:00
params , _ = headerParamPool . Get ( ) . ( headerParams ) //nolint:errcheck // only contains headerParams
fasthttp . VisitHeaderParams ( accept [ i : ] , func ( key , value [ ] byte ) bool {
if string ( key ) == "q" {
if q , err := fasthttp . ParseUfloat ( value ) ; err == nil {
2023-11-06 23:25:23 -08:00
quality = q
}
return false
}
2024-02-19 06:28:06 -08:00
params [ utils . UnsafeString ( utils . ToLowerBytes ( key ) ) ] = value
2023-11-06 23:25:23 -08:00
return true
} )
}
2024-02-19 06:28:06 -08:00
2023-11-06 23:25:23 -08:00
// Skip this accept type if quality is 0.0
// See: https://www.rfc-editor.org/rfc/rfc9110#quality.values
if quality == 0.0 {
return
2020-05-12 19:24:04 +02:00
}
2023-06-07 12:51:45 -03:00
}
2024-02-19 06:28:06 -08:00
spec = bytes . TrimRight ( spec , " " )
2023-11-06 23:25:23 -08:00
2023-06-07 12:51:45 -03:00
// Get specificity
2023-11-06 23:25:23 -08:00
var specificity int
2023-06-07 12:51:45 -03:00
// check for wildcard this could be a mime */* or a wildcard character *
2024-03-18 06:50:40 -07:00
switch {
case string ( spec ) == "*/*" || string ( spec ) == "*" :
2023-06-07 12:51:45 -03:00
specificity = 1
2024-03-18 06:50:40 -07:00
case bytes . HasSuffix ( spec , [ ] byte ( "/*" ) ) :
2023-06-07 12:51:45 -03:00
specificity = 2
2024-03-18 06:50:40 -07:00
case bytes . IndexByte ( spec , '/' ) != - 1 :
2023-06-07 12:51:45 -03:00
specificity = 3
2024-03-18 06:50:40 -07:00
default :
2023-06-07 12:51:45 -03:00
specificity = 4
2020-05-12 19:24:04 +02:00
}
2023-06-07 12:51:45 -03:00
// Add to accepted types
2024-02-19 06:28:06 -08:00
acceptedTypes = append ( acceptedTypes , acceptedType { utils . UnsafeString ( spec ) , quality , specificity , order , params } )
2023-11-06 23:25:23 -08:00
} )
2023-06-07 12:51:45 -03:00
if len ( acceptedTypes ) > 1 {
// Sort accepted types by quality and specificity, preserving order of equal elements
sortAcceptedTypes ( & acceptedTypes )
}
// Find the first offer that matches the accepted types
2024-02-19 06:28:06 -08:00
ret := ""
done := false
2023-06-07 12:51:45 -03:00
for _ , acceptedType := range acceptedTypes {
2024-02-19 06:28:06 -08:00
if ! done {
for _ , offer := range offers {
if offer == "" {
continue
}
if isAccepted ( acceptedType . spec , offer , acceptedType . params ) {
ret = offer
done = true
break
}
2023-06-07 12:51:45 -03:00
}
2024-02-19 06:28:06 -08:00
}
if acceptedType . params != nil {
for p := range acceptedType . params {
delete ( acceptedType . params , p )
2023-03-27 15:55:41 +02:00
}
2024-02-19 06:28:06 -08:00
headerParamPool . Put ( acceptedType . params )
2020-05-23 09:30:21 +02:00
}
2020-05-12 19:24:04 +02:00
}
2020-05-23 09:30:21 +02:00
2024-02-19 06:28:06 -08:00
return ret
2020-05-12 19:24:04 +02:00
}
2023-06-07 12:51:45 -03:00
// sortAcceptedTypes sorts accepted types by quality and specificity, preserving order of equal elements
2023-11-06 23:25:23 -08:00
// A type with parameters has higher priority than an equivalent one without parameters.
// e.g., text/html;a=1;b=2 comes before text/html;a=1
2023-06-07 12:51:45 -03:00
// See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields
2023-11-06 23:25:23 -08:00
func sortAcceptedTypes ( acceptedTypes * [ ] acceptedType ) {
if acceptedTypes == nil || len ( * acceptedTypes ) < 2 {
2023-06-07 12:51:45 -03:00
return
}
2023-11-06 23:25:23 -08:00
at := * acceptedTypes
2023-06-07 12:51:45 -03:00
2023-11-06 23:25:23 -08:00
for i := 1 ; i < len ( at ) ; i ++ {
2023-06-07 12:51:45 -03:00
lo , hi := 0 , i - 1
for lo <= hi {
mid := ( lo + hi ) / 2
2023-11-06 23:25:23 -08:00
if at [ i ] . quality < at [ mid ] . quality ||
( at [ i ] . quality == at [ mid ] . quality && at [ i ] . specificity < at [ mid ] . specificity ) ||
( at [ i ] . quality == at [ mid ] . quality && at [ i ] . specificity < at [ mid ] . specificity && len ( at [ i ] . params ) < len ( at [ mid ] . params ) ) ||
( at [ i ] . quality == at [ mid ] . quality && at [ i ] . specificity == at [ mid ] . specificity && len ( at [ i ] . params ) == len ( at [ mid ] . params ) && at [ i ] . order > at [ mid ] . order ) {
2023-06-07 12:51:45 -03:00
lo = mid + 1
} else {
hi = mid - 1
}
}
for j := i ; j > lo ; j -- {
2023-11-06 23:25:23 -08:00
at [ j - 1 ] , at [ j ] = at [ j ] , at [ j - 1 ]
2023-06-07 12:51:45 -03:00
}
}
}
2023-01-27 09:01:37 +01:00
func matchEtag ( s , etag string ) bool {
2020-07-26 18:44:53 -07:00
if s == etag || s == "W/" + etag || "W/" + s == etag {
return true
}
return false
}
2021-05-05 17:24:26 -03:00
func ( app * App ) isEtagStale ( etag string , noneMatchBytes [ ] byte ) bool {
2020-07-26 18:44:53 -07:00
var start , end int
2020-07-26 17:48:24 -07:00
// Adapted from:
// https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L110
2020-04-29 00:15:52 +02:00
for i := range noneMatchBytes {
switch noneMatchBytes [ i ] {
2020-04-29 01:44:22 +08:00
case 0x20 :
if start == end {
start = i + 1
end = i + 1
}
case 0x2c :
2021-05-05 17:24:26 -03:00
if matchEtag ( app . getString ( noneMatchBytes [ start : end ] ) , etag ) {
2020-07-26 18:44:53 -07:00
return false
}
2020-04-29 01:44:22 +08:00
start = i + 1
end = i + 1
default :
end = i + 1
}
}
2021-05-05 17:24:26 -03:00
return ! matchEtag ( app . getString ( noneMatchBytes [ start : end ] ) , etag )
2020-04-29 01:44:22 +08:00
}
2023-01-27 09:01:37 +01:00
func parseAddr ( raw string ) ( string , string ) { //nolint:revive // Returns (host, port)
2020-07-14 03:53:58 +02:00
if i := strings . LastIndex ( raw , ":" ) ; i != - 1 {
return raw [ : i ] , raw [ i + 1 : ]
}
return raw , ""
}
2020-07-28 04:36:56 +02:00
const noCacheValue = "no-cache"
// isNoCache checks if the cacheControl header value is a `no-cache`.
func isNoCache ( cacheControl string ) bool {
2020-07-28 12:03:52 +08:00
i := strings . Index ( cacheControl , noCacheValue )
if i == - 1 {
2020-07-28 04:36:56 +02:00
return false
}
2020-07-28 12:03:52 +08:00
// Xno-cache
if i > 0 && ! ( cacheControl [ i - 1 ] == ' ' || cacheControl [ i - 1 ] == ',' ) {
return false
2020-07-28 04:36:56 +02:00
}
2020-07-28 12:03:52 +08:00
// bla bla, no-cache
if i + len ( noCacheValue ) == len ( cacheControl ) {
return true
}
// bla bla, no-cacheX
if cacheControl [ i + len ( noCacheValue ) ] != ',' {
return false
2020-07-28 04:36:56 +02:00
}
2020-07-28 12:03:52 +08:00
// OK
2020-07-28 04:36:56 +02:00
return true
}
2020-05-11 04:30:31 +02:00
type testConn struct {
r bytes . Buffer
w bytes . Buffer
}
2023-01-27 09:01:37 +01:00
func ( c * testConn ) Read ( b [ ] byte ) ( int , error ) { return c . r . Read ( b ) } //nolint:wrapcheck // This must not be wrapped
func ( c * testConn ) Write ( b [ ] byte ) ( int , error ) { return c . w . Write ( b ) } //nolint:wrapcheck // This must not be wrapped
func ( * testConn ) Close ( ) error { return nil }
2020-07-05 11:09:01 +02:00
2023-01-27 09:01:37 +01:00
func ( * testConn ) LocalAddr ( ) net . Addr { return & net . TCPAddr { Port : 0 , Zone : "" , IP : net . IPv4zero } }
func ( * testConn ) RemoteAddr ( ) net . Addr { return & net . TCPAddr { Port : 0 , Zone : "" , IP : net . IPv4zero } }
func ( * testConn ) SetDeadline ( _ time . Time ) error { return nil }
func ( * testConn ) SetReadDeadline ( _ time . Time ) error { return nil }
func ( * testConn ) SetWriteDeadline ( _ time . Time ) error { return nil }
2020-05-11 04:30:31 +02:00
2023-01-27 09:01:37 +01:00
func getStringImmutable ( b [ ] byte ) string {
2020-05-11 04:30:31 +02:00
return string ( b )
}
2023-01-27 09:01:37 +01:00
func getBytesImmutable ( s string ) [ ] byte {
2020-05-11 04:30:31 +02:00
return [ ] byte ( s )
}
2020-05-07 18:15:59 +02:00
// HTTP methods and their unique INTs
2022-11-11 14:23:30 +07:00
func ( app * App ) methodInt ( s string ) int {
// For better performance
2022-11-15 06:13:11 -05:00
if len ( app . configured . RequestMethods ) == 0 {
2023-02-02 15:57:40 +01:00
// TODO: Use iota instead
2022-11-11 14:23:30 +07:00
switch s {
case MethodGet :
return 0
case MethodHead :
return 1
case MethodPost :
return 2
case MethodPut :
return 3
case MethodDelete :
return 4
case MethodConnect :
return 5
case MethodOptions :
return 6
case MethodTrace :
return 7
case MethodPatch :
return 8
default :
return - 1
}
}
// For method customization
for i , v := range app . config . RequestMethods {
if s == v {
return i
}
2020-06-29 22:51:41 +02:00
}
2020-05-07 18:15:59 +02:00
2022-11-11 14:23:30 +07:00
return - 1
2020-06-21 11:02:17 +02:00
}
2023-01-06 08:46:17 +01:00
// IsMethodSafe reports whether the HTTP method is considered safe.
// See https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.1
func IsMethodSafe ( m string ) bool {
switch m {
case MethodGet ,
MethodHead ,
MethodOptions ,
MethodTrace :
return true
default :
return false
}
}
// IsMethodIdempotent reports whether the HTTP method is considered idempotent.
// See https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.2
func IsMethodIdempotent ( m string ) bool {
if IsMethodSafe ( m ) {
return true
}
switch m {
2023-01-27 09:01:37 +01:00
case MethodPut , MethodDelete :
2023-01-06 08:46:17 +01:00
return true
default :
return false
}
}
2020-05-16 05:13:01 +02:00
// HTTP methods were copied from net/http.
const (
MethodGet = "GET" // RFC 7231, 4.3.1
MethodHead = "HEAD" // RFC 7231, 4.3.2
MethodPost = "POST" // RFC 7231, 4.3.3
MethodPut = "PUT" // RFC 7231, 4.3.4
MethodPatch = "PATCH" // RFC 5789
MethodDelete = "DELETE" // RFC 7231, 4.3.5
MethodConnect = "CONNECT" // RFC 7231, 4.3.6
MethodOptions = "OPTIONS" // RFC 7231, 4.3.7
MethodTrace = "TRACE" // RFC 7231, 4.3.8
2020-07-12 23:54:22 +08:00
methodUse = "USE"
2020-05-16 05:13:01 +02:00
)
2020-03-22 01:51:53 +01:00
2020-06-06 07:30:30 +02:00
// MIME types that are commonly used
2020-03-22 01:51:53 +01:00
const (
2022-10-10 17:59:51 +05:30
MIMETextXML = "text/xml"
MIMETextHTML = "text/html"
MIMETextPlain = "text/plain"
MIMETextJavaScript = "text/javascript"
MIMEApplicationXML = "application/xml"
MIMEApplicationJSON = "application/json"
// Deprecated: use MIMETextJavaScript instead
2020-03-22 01:51:53 +01:00
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationForm = "application/x-www-form-urlencoded"
2020-05-07 17:57:21 +02:00
MIMEOctetStream = "application/octet-stream"
2020-06-06 07:30:30 +02:00
MIMEMultipartForm = "multipart/form-data"
2022-10-10 17:59:51 +05:30
MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
// Deprecated: use MIMETextJavaScriptCharsetUTF8 instead
2020-06-06 07:30:30 +02:00
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
2020-03-22 01:51:53 +01:00
)
2023-10-10 02:11:18 -04:00
// HTTP status codes were copied from net/http with the following updates:
2022-11-11 08:40:37 +01:00
// - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation
// - Add StatusSwitchProxy (306)
// NOTE: Keep this list in sync with statusMessage
2020-03-22 01:51:53 +01:00
const (
2022-11-11 08:40:37 +01:00
StatusContinue = 100 // RFC 9110, 15.2.1
StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2
StatusProcessing = 102 // RFC 2518, 10.1
StatusEarlyHints = 103 // RFC 8297
StatusOK = 200 // RFC 9110, 15.3.1
StatusCreated = 201 // RFC 9110, 15.3.2
StatusAccepted = 202 // RFC 9110, 15.3.3
StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4
StatusNoContent = 204 // RFC 9110, 15.3.5
StatusResetContent = 205 // RFC 9110, 15.3.6
StatusPartialContent = 206 // RFC 9110, 15.3.7
StatusMultiStatus = 207 // RFC 4918, 11.1
StatusAlreadyReported = 208 // RFC 5842, 7.1
StatusIMUsed = 226 // RFC 3229, 10.4.1
StatusMultipleChoices = 300 // RFC 9110, 15.4.1
StatusMovedPermanently = 301 // RFC 9110, 15.4.2
StatusFound = 302 // RFC 9110, 15.4.3
StatusSeeOther = 303 // RFC 9110, 15.4.4
StatusNotModified = 304 // RFC 9110, 15.4.5
StatusUseProxy = 305 // RFC 9110, 15.4.6
StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused)
StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8
StatusPermanentRedirect = 308 // RFC 9110, 15.4.9
StatusBadRequest = 400 // RFC 9110, 15.5.1
StatusUnauthorized = 401 // RFC 9110, 15.5.2
StatusPaymentRequired = 402 // RFC 9110, 15.5.3
StatusForbidden = 403 // RFC 9110, 15.5.4
StatusNotFound = 404 // RFC 9110, 15.5.5
StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6
StatusNotAcceptable = 406 // RFC 9110, 15.5.7
StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8
StatusRequestTimeout = 408 // RFC 9110, 15.5.9
StatusConflict = 409 // RFC 9110, 15.5.10
StatusGone = 410 // RFC 9110, 15.5.11
StatusLengthRequired = 411 // RFC 9110, 15.5.12
StatusPreconditionFailed = 412 // RFC 9110, 15.5.13
StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14
StatusRequestURITooLong = 414 // RFC 9110, 15.5.15
StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16
StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17
StatusExpectationFailed = 417 // RFC 9110, 15.5.18
StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused)
StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20
StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21
StatusLocked = 423 // RFC 4918, 11.3
StatusFailedDependency = 424 // RFC 4918, 11.4
StatusTooEarly = 425 // RFC 8470, 5.2.
StatusUpgradeRequired = 426 // RFC 9110, 15.5.22
StatusPreconditionRequired = 428 // RFC 6585, 3
StatusTooManyRequests = 429 // RFC 6585, 4
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
StatusInternalServerError = 500 // RFC 9110, 15.6.1
StatusNotImplemented = 501 // RFC 9110, 15.6.2
StatusBadGateway = 502 // RFC 9110, 15.6.3
StatusServiceUnavailable = 503 // RFC 9110, 15.6.4
StatusGatewayTimeout = 504 // RFC 9110, 15.6.5
StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6
2020-05-16 05:13:01 +02:00
StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
StatusInsufficientStorage = 507 // RFC 4918, 11.5
StatusLoopDetected = 508 // RFC 5842, 7.2
StatusNotExtended = 510 // RFC 2774, 7
StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
2020-03-22 01:51:53 +01:00
)
2020-06-06 18:53:51 +02:00
// Errors
var (
2022-11-11 08:40:37 +01:00
ErrBadRequest = NewError ( StatusBadRequest ) // 400
ErrUnauthorized = NewError ( StatusUnauthorized ) // 401
ErrPaymentRequired = NewError ( StatusPaymentRequired ) // 402
ErrForbidden = NewError ( StatusForbidden ) // 403
ErrNotFound = NewError ( StatusNotFound ) // 404
ErrMethodNotAllowed = NewError ( StatusMethodNotAllowed ) // 405
ErrNotAcceptable = NewError ( StatusNotAcceptable ) // 406
ErrProxyAuthRequired = NewError ( StatusProxyAuthRequired ) // 407
ErrRequestTimeout = NewError ( StatusRequestTimeout ) // 408
ErrConflict = NewError ( StatusConflict ) // 409
ErrGone = NewError ( StatusGone ) // 410
ErrLengthRequired = NewError ( StatusLengthRequired ) // 411
ErrPreconditionFailed = NewError ( StatusPreconditionFailed ) // 412
ErrRequestEntityTooLarge = NewError ( StatusRequestEntityTooLarge ) // 413
ErrRequestURITooLong = NewError ( StatusRequestURITooLong ) // 414
ErrUnsupportedMediaType = NewError ( StatusUnsupportedMediaType ) // 415
ErrRequestedRangeNotSatisfiable = NewError ( StatusRequestedRangeNotSatisfiable ) // 416
ErrExpectationFailed = NewError ( StatusExpectationFailed ) // 417
ErrTeapot = NewError ( StatusTeapot ) // 418
ErrMisdirectedRequest = NewError ( StatusMisdirectedRequest ) // 421
ErrUnprocessableEntity = NewError ( StatusUnprocessableEntity ) // 422
ErrLocked = NewError ( StatusLocked ) // 423
ErrFailedDependency = NewError ( StatusFailedDependency ) // 424
ErrTooEarly = NewError ( StatusTooEarly ) // 425
ErrUpgradeRequired = NewError ( StatusUpgradeRequired ) // 426
ErrPreconditionRequired = NewError ( StatusPreconditionRequired ) // 428
ErrTooManyRequests = NewError ( StatusTooManyRequests ) // 429
ErrRequestHeaderFieldsTooLarge = NewError ( StatusRequestHeaderFieldsTooLarge ) // 431
ErrUnavailableForLegalReasons = NewError ( StatusUnavailableForLegalReasons ) // 451
ErrInternalServerError = NewError ( StatusInternalServerError ) // 500
ErrNotImplemented = NewError ( StatusNotImplemented ) // 501
ErrBadGateway = NewError ( StatusBadGateway ) // 502
ErrServiceUnavailable = NewError ( StatusServiceUnavailable ) // 503
ErrGatewayTimeout = NewError ( StatusGatewayTimeout ) // 504
ErrHTTPVersionNotSupported = NewError ( StatusHTTPVersionNotSupported ) // 505
ErrVariantAlsoNegotiates = NewError ( StatusVariantAlsoNegotiates ) // 506
ErrInsufficientStorage = NewError ( StatusInsufficientStorage ) // 507
ErrLoopDetected = NewError ( StatusLoopDetected ) // 508
ErrNotExtended = NewError ( StatusNotExtended ) // 510
ErrNetworkAuthenticationRequired = NewError ( StatusNetworkAuthenticationRequired ) // 511
2020-06-06 18:53:51 +02:00
)
2020-03-22 01:51:53 +01:00
// HTTP Headers were copied from net/http.
const (
2024-03-22 16:18:12 +05:00
HeaderAuthorization = "Authorization"
HeaderProxyAuthenticate = "Proxy-Authenticate"
HeaderProxyAuthorization = "Proxy-Authorization"
HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderAge = "Age"
HeaderCacheControl = "Cache-Control"
HeaderClearSiteData = "Clear-Site-Data"
HeaderExpires = "Expires"
HeaderPragma = "Pragma"
HeaderWarning = "Warning"
HeaderAcceptCH = "Accept-CH"
HeaderAcceptCHLifetime = "Accept-CH-Lifetime"
HeaderContentDPR = "Content-DPR"
HeaderDPR = "DPR"
HeaderEarlyData = "Early-Data"
HeaderSaveData = "Save-Data"
HeaderViewportWidth = "Viewport-Width"
HeaderWidth = "Width"
HeaderETag = "ETag"
HeaderIfMatch = "If-Match"
HeaderIfModifiedSince = "If-Modified-Since"
HeaderIfNoneMatch = "If-None-Match"
HeaderIfUnmodifiedSince = "If-Unmodified-Since"
HeaderLastModified = "Last-Modified"
HeaderVary = "Vary"
HeaderConnection = "Connection"
HeaderKeepAlive = "Keep-Alive"
HeaderAccept = "Accept"
HeaderAcceptCharset = "Accept-Charset"
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAcceptLanguage = "Accept-Language"
HeaderCookie = "Cookie"
HeaderExpect = "Expect"
HeaderMaxForwards = "Max-Forwards"
HeaderSetCookie = "Set-Cookie"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderOrigin = "Origin"
HeaderTimingAllowOrigin = "Timing-Allow-Origin"
HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies"
HeaderDNT = "DNT"
HeaderTk = "Tk"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLanguage = "Content-Language"
HeaderContentLength = "Content-Length"
HeaderContentLocation = "Content-Location"
HeaderContentType = "Content-Type"
HeaderForwarded = "Forwarded"
HeaderVia = "Via"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedHost = "X-Forwarded-Host"
HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderLocation = "Location"
HeaderFrom = "From"
HeaderHost = "Host"
HeaderReferer = "Referer"
HeaderReferrerPolicy = "Referrer-Policy"
HeaderUserAgent = "User-Agent"
HeaderAllow = "Allow"
HeaderServer = "Server"
HeaderAcceptRanges = "Accept-Ranges"
HeaderContentRange = "Content-Range"
HeaderIfRange = "If-Range"
HeaderRange = "Range"
HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
HeaderExpectCT = "Expect-CT"
HeaderPermissionsPolicy = "Permissions-Policy"
HeaderPublicKeyPins = "Public-Key-Pins"
HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only"
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests"
HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXDownloadOptions = "X-Download-Options"
HeaderXFrameOptions = "X-Frame-Options"
HeaderXPoweredBy = "X-Powered-By"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderLastEventID = "Last-Event-ID"
HeaderNEL = "NEL"
HeaderPingFrom = "Ping-From"
HeaderPingTo = "Ping-To"
HeaderReportTo = "Report-To"
HeaderTE = "TE"
HeaderTrailer = "Trailer"
HeaderTransferEncoding = "Transfer-Encoding"
HeaderSecWebSocketAccept = "Sec-WebSocket-Accept"
HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions"
HeaderSecWebSocketKey = "Sec-WebSocket-Key"
HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol"
HeaderSecWebSocketVersion = "Sec-WebSocket-Version"
HeaderAcceptPatch = "Accept-Patch"
HeaderAcceptPushPolicy = "Accept-Push-Policy"
HeaderAcceptSignature = "Accept-Signature"
HeaderAltSvc = "Alt-Svc"
HeaderDate = "Date"
HeaderIndex = "Index"
HeaderLargeAllocation = "Large-Allocation"
HeaderLink = "Link"
HeaderPushPolicy = "Push-Policy"
HeaderRetryAfter = "Retry-After"
HeaderServerTiming = "Server-Timing"
HeaderSignature = "Signature"
HeaderSignedHeaders = "Signed-Headers"
HeaderSourceMap = "SourceMap"
HeaderUpgrade = "Upgrade"
HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control"
HeaderXPingback = "X-Pingback"
HeaderXRequestID = "X-Request-ID"
HeaderXRequestedWith = "X-Requested-With"
HeaderXRobotsTag = "X-Robots-Tag"
HeaderXUACompatible = "X-UA-Compatible"
HeaderAccessControlAllowPrivateNetwork = "Access-Control-Allow-Private-Network"
HeaderAccessControlRequestPrivateNetwork = "Access-Control-Request-Private-Network"
2020-03-22 01:51:53 +01:00
)
2021-02-08 11:35:17 +08:00
// Network types that are commonly used
const (
NetworkTCP = "tcp"
NetworkTCP4 = "tcp4"
NetworkTCP6 = "tcp6"
)
2021-06-24 08:47:21 +03:00
2021-11-05 08:00:03 +01:00
// Compression types
2021-06-24 08:47:21 +03:00
const (
StrGzip = "gzip"
StrBr = "br"
StrDeflate = "deflate"
StrBrotli = "brotli"
)
2021-07-17 02:08:17 +08:00
// Cookie SameSite
// https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7
const (
CookieSameSiteDisabled = "disabled" // not in RFC, just control "SameSite" attribute will not be set.
CookieSameSiteLaxMode = "lax"
CookieSameSiteStrictMode = "strict"
CookieSameSiteNoneMode = "none"
)
2022-08-16 09:05:50 +03:00
// Route Constraints
const (
2022-08-26 15:16:06 +03:00
ConstraintInt = "int"
ConstraintBool = "bool"
ConstraintFloat = "float"
ConstraintAlpha = "alpha"
2023-02-09 23:15:21 +03:00
ConstraintGUID = "guid"
2022-08-26 15:16:06 +03:00
ConstraintMinLen = "minLen"
ConstraintMaxLen = "maxLen"
ConstraintLen = "len"
ConstraintBetweenLen = "betweenLen"
ConstraintMinLenLower = "minlen"
ConstraintMaxLenLower = "maxlen"
ConstraintBetweenLenLower = "betweenlen"
ConstraintMin = "min"
ConstraintMax = "max"
ConstraintRange = "range"
ConstraintDatetime = "datetime"
ConstraintRegex = "regex"
2022-08-16 09:05:50 +03:00
)
2023-01-15 18:21:37 +03:00
func IndexRune ( str string , needle int32 ) bool {
for _ , b := range str {
if b == needle {
return true
}
}
return false
}