package fiber
import (
"bytes"
"crypto/tls"
"fmt"
"hash/crc32"
"io"
"net"
"os"
"path/filepath"
"reflect"
"strings"
"time"
"unsafe"
"github.com/gofiber/fiber/v2/log"
"github.com/gofiber/fiber/v2/utils"
"github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp"
)
type acceptedType struct {
spec string
quality float64
specificity int
order int
}
func getTLSConfig(ln net .Listener ) *tls .Config {
pointer := reflect .ValueOf (ln )
if pointer .String () == "<*tls.listener Value>" {
if val := reflect .Indirect (pointer ); val .Type () != nil {
if field := val .FieldByName ("config" ); field .Type () != nil {
newval := reflect .NewAt (field .Type (), unsafe .Pointer (field .UnsafeAddr ()))
if newval .Type () != nil {
if elem := newval .Elem (); elem .Type () != nil {
c , ok := elem .Interface ().(*tls .Config )
if !ok {
panic (fmt .Errorf ("failed to type-assert to *tls.Config" ))
}
return c
}
}
}
}
}
return nil
}
func readContent(rf io .ReaderFrom , name string ) (int64 , error ) {
f , err := os .Open (filepath .Clean (name ))
if err != nil {
return 0 , fmt .Errorf ("failed to open: %w" , err )
}
defer func () {
if err = f .Close (); err != nil {
log .Errorf ("Error closing file: %s" , err )
}
}()
if n , err := rf .ReadFrom (f ); err != nil {
return n , fmt .Errorf ("failed to read: %w" , err )
}
return 0 , nil
}
func (app *App ) quoteString (raw string ) string {
bb := bytebufferpool .Get ()
quoted := app .getString (fasthttp .AppendQuotedArg (bb .B , app .getBytes (raw )))
bytebufferpool .Put (bb )
return quoted
}
func (app *App ) methodExist (ctx *Ctx ) bool {
var exists bool
methods := app .config .RequestMethods
for i := 0 ; i < len (methods ); i ++ {
if ctx .methodINT == i {
continue
}
indexRoute := -1
tree , ok := ctx .app .treeStack [i ][ctx .treePath ]
if !ok {
tree = ctx .app .treeStack [i ]["" ]
}
lenr := len (tree ) - 1
for indexRoute < lenr {
indexRoute ++
route := tree [indexRoute ]
if route .use {
continue
}
match := route .match (ctx .detectionPath , ctx .path , &ctx .values )
if match {
exists = true
ctx .Append (HeaderAllow , methods [i ])
break
}
}
}
return exists
}
func uniqueRouteStack(stack []*Route ) []*Route {
var unique []*Route
m := make (map [*Route ]int )
for _ , v := range stack {
if _ , ok := m [v ]; !ok {
m [v ] = len (unique )
unique = append (unique , v )
}
}
return unique
}
func defaultString(value string , defaultValue []string ) string {
if len (value ) == 0 && len (defaultValue ) > 0 {
return defaultValue [0 ]
}
return value
}
const normalizedHeaderETag = "Etag"
func setETag(c *Ctx , weak bool ) {
if c .fasthttp .Response .StatusCode () != StatusOK {
return
}
body := c .fasthttp .Response .Body ()
if len (body ) == 0 {
return
}
clientEtag := c .Get (HeaderIfNoneMatch )
const pol = 0xD5828281
crc32q := crc32 .MakeTable (pol )
etag := fmt .Sprintf ("\"%d-%v\"" , len (body ), crc32 .Checksum (body , crc32q ))
if weak {
etag = "W/" + etag
}
if strings .HasPrefix (clientEtag , "W/" ) {
if clientEtag [2 :] == etag || clientEtag [2 :] == etag [2 :] {
if err := c .SendStatus (StatusNotModified ); err != nil {
log .Errorf ("setETag: failed to SendStatus: %v" , err )
}
c .fasthttp .ResetBody ()
return
}
c .setCanonical (normalizedHeaderETag , etag )
return
}
if strings .Contains (clientEtag , etag ) {
if err := c .SendStatus (StatusNotModified ); err != nil {
log .Errorf ("setETag: failed to SendStatus: %v" , err )
}
c .fasthttp .ResetBody ()
return
}
c .setCanonical (normalizedHeaderETag , etag )
}
func getGroupPath(prefix , path string ) string {
if len (path ) == 0 {
return prefix
}
if path [0 ] != '/' {
path = "/" + path
}
return utils .TrimRight (prefix , '/' ) + path
}
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
}
func acceptsOfferType(spec , offerType string ) bool {
if spec == "*/*" {
return true
}
var mimetype string
if strings .IndexByte (offerType , '/' ) != -1 {
mimetype = offerType
} else {
mimetype = utils .GetMIME (offerType )
}
if spec == mimetype {
return true
}
s := strings .IndexByte (mimetype , '/' )
if strings .HasPrefix (spec , mimetype [:s ]) && (spec [s :] == "/*" || mimetype [s :] == "/*" ) {
return true
}
return false
}
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 )
}
dst [insertIndex ] = utils .TrimLeft (headerValue [lastElementEndsAt :index ], ' ' )
lastElementEndsAt = uint8 (index + 1 )
insertIndex ++
}
}
if len (dst ) > insertIndex {
dst = dst [:insertIndex ]
}
return dst
}
func getOffer(header string , isAccepted func (spec , offer string ) bool , offers ...string ) string {
if len (offers ) == 0 {
return ""
}
if header == "" {
return offers [0 ]
}
spec , commaPos , order := "" , 0 , 0
acceptedTypes := make ([]acceptedType , 0 , 20 )
for len (header ) > 0 {
order ++
header = utils .TrimLeft (header , ' ' )
commaPos = strings .IndexByte (header , ',' )
if commaPos != -1 {
spec = utils .Trim (header [:commaPos ], ' ' )
} else {
spec = utils .TrimLeft (header , ' ' )
}
quality := 1.0
if factorSign := strings .IndexByte (spec , ';' ); factorSign != -1 {
factor := utils .Trim (spec [factorSign +1 :], ' ' )
if strings .HasPrefix (factor , "q=" ) {
if q , err := fasthttp .ParseUfloat (utils .UnsafeBytes (factor [2 :])); err == nil {
quality = q
}
}
spec = spec [:factorSign ]
}
if quality == 0.0 {
if commaPos != -1 {
header = header [commaPos +1 :]
} else {
break
}
continue
}
specificity := 0
if spec == "*/*" || spec == "*" {
specificity = 1
} else if strings .HasSuffix (spec , "/*" ) {
specificity = 2
} else if strings .IndexByte (spec , '/' ) != -1 {
specificity = 3
} else {
specificity = 4
}
acceptedTypes = append (acceptedTypes , acceptedType {spec , quality , specificity , order })
if commaPos != -1 {
header = header [commaPos +1 :]
} else {
break
}
}
if len (acceptedTypes ) > 1 {
sortAcceptedTypes (&acceptedTypes )
}
for _ , acceptedType := range acceptedTypes {
for _ , offer := range offers {
if len (offer ) == 0 {
continue
}
if isAccepted (acceptedType .spec , offer ) {
return offer
}
}
}
return ""
}
func sortAcceptedTypes(at *[]acceptedType ) {
if at == nil || len (*at ) < 2 {
return
}
acceptedTypes := *at
for i := 1 ; i < len (acceptedTypes ); i ++ {
lo , hi := 0 , i -1
for lo <= hi {
mid := (lo + hi ) / 2
if acceptedTypes [i ].quality < acceptedTypes [mid ].quality ||
(acceptedTypes [i ].quality == acceptedTypes [mid ].quality && acceptedTypes [i ].specificity < acceptedTypes [mid ].specificity ) ||
(acceptedTypes [i ].quality == acceptedTypes [mid ].quality && acceptedTypes [i ].specificity == acceptedTypes [mid ].specificity && acceptedTypes [i ].order > acceptedTypes [mid ].order ) {
lo = mid + 1
} else {
hi = mid - 1
}
}
for j := i ; j > lo ; j -- {
acceptedTypes [j -1 ], acceptedTypes [j ] = acceptedTypes [j ], acceptedTypes [j -1 ]
}
}
}
func matchEtag(s , etag string ) bool {
if s == etag || s == "W/" +etag || "W/" +s == etag {
return true
}
return false
}
func (app *App ) isEtagStale (etag string , noneMatchBytes []byte ) bool {
var start , end int
for i := range noneMatchBytes {
switch noneMatchBytes [i ] {
case 0x20 :
if start == end {
start = i + 1
end = i + 1
}
case 0x2c :
if matchEtag (app .getString (noneMatchBytes [start :end ]), etag ) {
return false
}
start = i + 1
end = i + 1
default :
end = i + 1
}
}
return !matchEtag (app .getString (noneMatchBytes [start :end ]), etag )
}
func parseAddr(raw string ) (string , string ) {
if i := strings .LastIndex (raw , ":" ); i != -1 {
return raw [:i ], raw [i +1 :]
}
return raw , ""
}
const noCacheValue = "no-cache"
func isNoCache(cacheControl string ) bool {
i := strings .Index (cacheControl , noCacheValue )
if i == -1 {
return false
}
if i > 0 && !(cacheControl [i -1 ] == ' ' || cacheControl [i -1 ] == ',' ) {
return false
}
if i +len (noCacheValue ) == len (cacheControl ) {
return true
}
if cacheControl [i +len (noCacheValue )] != ',' {
return false
}
return true
}
type testConn struct {
r bytes .Buffer
w bytes .Buffer
}
func (c *testConn ) Read (b []byte ) (int , error ) { return c .r .Read (b ) }
func (c *testConn ) Write (b []byte ) (int , error ) { return c .w .Write (b ) }
func (*testConn ) Close () error { return nil }
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 }
func getStringImmutable(b []byte ) string {
return string (b )
}
func getBytesImmutable(s string ) []byte {
return []byte (s )
}
func (app *App ) methodInt (s string ) int {
if len (app .configured .RequestMethods ) == 0 {
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 i , v := range app .config .RequestMethods {
if s == v {
return i
}
}
return -1
}
func IsMethodSafe (m string ) bool {
switch m {
case MethodGet ,
MethodHead ,
MethodOptions ,
MethodTrace :
return true
default :
return false
}
}
func IsMethodIdempotent (m string ) bool {
if IsMethodSafe (m ) {
return true
}
switch m {
case MethodPut , MethodDelete :
return true
default :
return false
}
}
const (
MethodGet = "GET"
MethodHead = "HEAD"
MethodPost = "POST"
MethodPut = "PUT"
MethodPatch = "PATCH"
MethodDelete = "DELETE"
MethodConnect = "CONNECT"
MethodOptions = "OPTIONS"
MethodTrace = "TRACE"
methodUse = "USE"
)
const (
MIMETextXML = "text/xml"
MIMETextHTML = "text/html"
MIMETextPlain = "text/plain"
MIMETextJavaScript = "text/javascript"
MIMEApplicationXML = "application/xml"
MIMEApplicationJSON = "application/json"
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEOctetStream = "application/octet-stream"
MIMEMultipartForm = "multipart/form-data"
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"
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
)
const (
StatusContinue = 100
StatusSwitchingProtocols = 101
StatusProcessing = 102
StatusEarlyHints = 103
StatusOK = 200
StatusCreated = 201
StatusAccepted = 202
StatusNonAuthoritativeInformation = 203
StatusNoContent = 204
StatusResetContent = 205
StatusPartialContent = 206
StatusMultiStatus = 207
StatusAlreadyReported = 208
StatusIMUsed = 226
StatusMultipleChoices = 300
StatusMovedPermanently = 301
StatusFound = 302
StatusSeeOther = 303
StatusNotModified = 304
StatusUseProxy = 305
StatusSwitchProxy = 306
StatusTemporaryRedirect = 307
StatusPermanentRedirect = 308
StatusBadRequest = 400
StatusUnauthorized = 401
StatusPaymentRequired = 402
StatusForbidden = 403
StatusNotFound = 404
StatusMethodNotAllowed = 405
StatusNotAcceptable = 406
StatusProxyAuthRequired = 407
StatusRequestTimeout = 408
StatusConflict = 409
StatusGone = 410
StatusLengthRequired = 411
StatusPreconditionFailed = 412
StatusRequestEntityTooLarge = 413
StatusRequestURITooLong = 414
StatusUnsupportedMediaType = 415
StatusRequestedRangeNotSatisfiable = 416
StatusExpectationFailed = 417
StatusTeapot = 418
StatusMisdirectedRequest = 421
StatusUnprocessableEntity = 422
StatusLocked = 423
StatusFailedDependency = 424
StatusTooEarly = 425
StatusUpgradeRequired = 426
StatusPreconditionRequired = 428
StatusTooManyRequests = 429
StatusRequestHeaderFieldsTooLarge = 431
StatusUnavailableForLegalReasons = 451
StatusInternalServerError = 500
StatusNotImplemented = 501
StatusBadGateway = 502
StatusServiceUnavailable = 503
StatusGatewayTimeout = 504
StatusHTTPVersionNotSupported = 505
StatusVariantAlsoNegotiates = 506
StatusInsufficientStorage = 507
StatusLoopDetected = 508
StatusNotExtended = 510
StatusNetworkAuthenticationRequired = 511
)
var (
ErrBadRequest = NewError (StatusBadRequest )
ErrUnauthorized = NewError (StatusUnauthorized )
ErrPaymentRequired = NewError (StatusPaymentRequired )
ErrForbidden = NewError (StatusForbidden )
ErrNotFound = NewError (StatusNotFound )
ErrMethodNotAllowed = NewError (StatusMethodNotAllowed )
ErrNotAcceptable = NewError (StatusNotAcceptable )
ErrProxyAuthRequired = NewError (StatusProxyAuthRequired )
ErrRequestTimeout = NewError (StatusRequestTimeout )
ErrConflict = NewError (StatusConflict )
ErrGone = NewError (StatusGone )
ErrLengthRequired = NewError (StatusLengthRequired )
ErrPreconditionFailed = NewError (StatusPreconditionFailed )
ErrRequestEntityTooLarge = NewError (StatusRequestEntityTooLarge )
ErrRequestURITooLong = NewError (StatusRequestURITooLong )
ErrUnsupportedMediaType = NewError (StatusUnsupportedMediaType )
ErrRequestedRangeNotSatisfiable = NewError (StatusRequestedRangeNotSatisfiable )
ErrExpectationFailed = NewError (StatusExpectationFailed )
ErrTeapot = NewError (StatusTeapot )
ErrMisdirectedRequest = NewError (StatusMisdirectedRequest )
ErrUnprocessableEntity = NewError (StatusUnprocessableEntity )
ErrLocked = NewError (StatusLocked )
ErrFailedDependency = NewError (StatusFailedDependency )
ErrTooEarly = NewError (StatusTooEarly )
ErrUpgradeRequired = NewError (StatusUpgradeRequired )
ErrPreconditionRequired = NewError (StatusPreconditionRequired )
ErrTooManyRequests = NewError (StatusTooManyRequests )
ErrRequestHeaderFieldsTooLarge = NewError (StatusRequestHeaderFieldsTooLarge )
ErrUnavailableForLegalReasons = NewError (StatusUnavailableForLegalReasons )
ErrInternalServerError = NewError (StatusInternalServerError )
ErrNotImplemented = NewError (StatusNotImplemented )
ErrBadGateway = NewError (StatusBadGateway )
ErrServiceUnavailable = NewError (StatusServiceUnavailable )
ErrGatewayTimeout = NewError (StatusGatewayTimeout )
ErrHTTPVersionNotSupported = NewError (StatusHTTPVersionNotSupported )
ErrVariantAlsoNegotiates = NewError (StatusVariantAlsoNegotiates )
ErrInsufficientStorage = NewError (StatusInsufficientStorage )
ErrLoopDetected = NewError (StatusLoopDetected )
ErrNotExtended = NewError (StatusNotExtended )
ErrNetworkAuthenticationRequired = NewError (StatusNetworkAuthenticationRequired )
)
const (
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"
HeaderFeaturePolicy = "Feature-Policy"
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"
)
const (
NetworkTCP = "tcp"
NetworkTCP4 = "tcp4"
NetworkTCP6 = "tcp6"
)
const (
StrGzip = "gzip"
StrBr = "br"
StrDeflate = "deflate"
StrBrotli = "brotli"
)
const (
CookieSameSiteDisabled = "disabled"
CookieSameSiteLaxMode = "lax"
CookieSameSiteStrictMode = "strict"
CookieSameSiteNoneMode = "none"
)
const (
ConstraintInt = "int"
ConstraintBool = "bool"
ConstraintFloat = "float"
ConstraintAlpha = "alpha"
ConstraintGuid = "guid"
ConstraintMinLen = "minLen"
ConstraintMaxLen = "maxLen"
ConstraintLen = "len"
ConstraintBetweenLen = "betweenLen"
ConstraintMinLenLower = "minlen"
ConstraintMaxLenLower = "maxlen"
ConstraintBetweenLenLower = "betweenlen"
ConstraintMin = "min"
ConstraintMax = "max"
ConstraintRange = "range"
ConstraintDatetime = "datetime"
ConstraintRegex = "regex"
)
func IndexRune (str string , needle int32 ) bool {
for _ , b := range str {
if b == needle {
return true
}
}
return false
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .