github.com/gofiber/fiber/v2@v2.47.0/helpers.go (about) 1 // ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ 2 // 🤖 Github Repository: https://github.com/gofiber/fiber 3 // 📌 API Documentation: https://docs.gofiber.io 4 5 package fiber 6 7 import ( 8 "bytes" 9 "crypto/tls" 10 "fmt" 11 "hash/crc32" 12 "io" 13 "log" 14 "net" 15 "os" 16 "path/filepath" 17 "reflect" 18 "strings" 19 "time" 20 "unsafe" 21 22 "github.com/gofiber/fiber/v2/utils" 23 24 "github.com/valyala/bytebufferpool" 25 "github.com/valyala/fasthttp" 26 ) 27 28 // acceptType is a struct that holds the parsed value of an Accept header 29 // along with quality, specificity, and order. 30 // used for sorting accept headers. 31 type acceptedType struct { 32 spec string 33 quality float64 34 specificity int 35 order int 36 } 37 38 // getTLSConfig returns a net listener's tls config 39 func getTLSConfig(ln net.Listener) *tls.Config { 40 // Get listener type 41 pointer := reflect.ValueOf(ln) 42 43 // Is it a tls.listener? 44 if pointer.String() == "<*tls.listener Value>" { 45 // Copy value from pointer 46 if val := reflect.Indirect(pointer); val.Type() != nil { 47 // Get private field from value 48 if field := val.FieldByName("config"); field.Type() != nil { 49 // Copy value from pointer field (unsafe) 50 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. 51 if newval.Type() != nil { 52 // Get element from pointer 53 if elem := newval.Elem(); elem.Type() != nil { 54 // Cast value to *tls.Config 55 c, ok := elem.Interface().(*tls.Config) 56 if !ok { 57 panic(fmt.Errorf("failed to type-assert to *tls.Config")) 58 } 59 return c 60 } 61 } 62 } 63 } 64 } 65 66 return nil 67 } 68 69 // readContent opens a named file and read content from it 70 func readContent(rf io.ReaderFrom, name string) (int64, error) { 71 // Read file 72 f, err := os.Open(filepath.Clean(name)) 73 if err != nil { 74 return 0, fmt.Errorf("failed to open: %w", err) 75 } 76 defer func() { 77 if err = f.Close(); err != nil { 78 log.Printf("Error closing file: %s\n", err) 79 } 80 }() 81 if n, err := rf.ReadFrom(f); err != nil { 82 return n, fmt.Errorf("failed to read: %w", err) 83 } 84 return 0, nil 85 } 86 87 // quoteString escape special characters in a given string 88 func (app *App) quoteString(raw string) string { 89 bb := bytebufferpool.Get() 90 // quoted := string(fasthttp.AppendQuotedArg(bb.B, getBytes(raw))) 91 quoted := app.getString(fasthttp.AppendQuotedArg(bb.B, app.getBytes(raw))) 92 bytebufferpool.Put(bb) 93 return quoted 94 } 95 96 // Scan stack if other methods match the request 97 func (app *App) methodExist(ctx *Ctx) bool { 98 var exists bool 99 methods := app.config.RequestMethods 100 for i := 0; i < len(methods); i++ { 101 // Skip original method 102 if ctx.methodINT == i { 103 continue 104 } 105 // Reset stack index 106 indexRoute := -1 107 tree, ok := ctx.app.treeStack[i][ctx.treePath] 108 if !ok { 109 tree = ctx.app.treeStack[i][""] 110 } 111 // Get stack length 112 lenr := len(tree) - 1 113 // Loop over the route stack starting from previous index 114 for indexRoute < lenr { 115 // Increment route index 116 indexRoute++ 117 // Get *Route 118 route := tree[indexRoute] 119 // Skip use routes 120 if route.use { 121 continue 122 } 123 // Check if it matches the request path 124 match := route.match(ctx.detectionPath, ctx.path, &ctx.values) 125 // No match, next route 126 if match { 127 // We matched 128 exists = true 129 // Add method to Allow header 130 ctx.Append(HeaderAllow, methods[i]) 131 // Break stack loop 132 break 133 } 134 } 135 } 136 return exists 137 } 138 139 // uniqueRouteStack drop all not unique routes from the slice 140 func uniqueRouteStack(stack []*Route) []*Route { 141 var unique []*Route 142 m := make(map[*Route]int) 143 for _, v := range stack { 144 if _, ok := m[v]; !ok { 145 // Unique key found. Record position and collect 146 // in result. 147 m[v] = len(unique) 148 unique = append(unique, v) 149 } 150 } 151 152 return unique 153 } 154 155 // defaultString returns the value or a default value if it is set 156 func defaultString(value string, defaultValue []string) string { 157 if len(value) == 0 && len(defaultValue) > 0 { 158 return defaultValue[0] 159 } 160 return value 161 } 162 163 const normalizedHeaderETag = "Etag" 164 165 // Generate and set ETag header to response 166 func setETag(c *Ctx, weak bool) { //nolint: revive // Accepting a bool param is fine here 167 // Don't generate ETags for invalid responses 168 if c.fasthttp.Response.StatusCode() != StatusOK { 169 return 170 } 171 body := c.fasthttp.Response.Body() 172 // Skips ETag if no response body is present 173 if len(body) == 0 { 174 return 175 } 176 // Get ETag header from request 177 clientEtag := c.Get(HeaderIfNoneMatch) 178 179 // Generate ETag for response 180 const pol = 0xD5828281 181 crc32q := crc32.MakeTable(pol) 182 etag := fmt.Sprintf("\"%d-%v\"", len(body), crc32.Checksum(body, crc32q)) 183 184 // Enable weak tag 185 if weak { 186 etag = "W/" + etag 187 } 188 189 // Check if client's ETag is weak 190 if strings.HasPrefix(clientEtag, "W/") { 191 // Check if server's ETag is weak 192 if clientEtag[2:] == etag || clientEtag[2:] == etag[2:] { 193 // W/1 == 1 || W/1 == W/1 194 if err := c.SendStatus(StatusNotModified); err != nil { 195 log.Printf("setETag: failed to SendStatus: %v\n", err) 196 } 197 c.fasthttp.ResetBody() 198 return 199 } 200 // W/1 != W/2 || W/1 != 2 201 c.setCanonical(normalizedHeaderETag, etag) 202 return 203 } 204 if strings.Contains(clientEtag, etag) { 205 // 1 == 1 206 if err := c.SendStatus(StatusNotModified); err != nil { 207 log.Printf("setETag: failed to SendStatus: %v\n", err) 208 } 209 c.fasthttp.ResetBody() 210 return 211 } 212 // 1 != 2 213 c.setCanonical(normalizedHeaderETag, etag) 214 } 215 216 func getGroupPath(prefix, path string) string { 217 if len(path) == 0 { 218 return prefix 219 } 220 221 if path[0] != '/' { 222 path = "/" + path 223 } 224 225 return utils.TrimRight(prefix, '/') + path 226 } 227 228 // acceptsOffer This function determines if an offer matches a given specification. 229 // It checks if the specification ends with a '*' or if the offer has the prefix of the specification. 230 // Returns true if the offer matches the specification, false otherwise. 231 func acceptsOffer(spec, offer string) bool { 232 if len(spec) >= 1 && spec[len(spec)-1] == '*' { 233 return true 234 } else if strings.HasPrefix(spec, offer) { 235 return true 236 } 237 return false 238 } 239 240 // acceptsOfferType This function determines if an offer type matches a given specification. 241 // It checks if the specification is equal to */* (i.e., all types are accepted). 242 // It gets the MIME type of the offer (either from the offer itself or by its file extension). 243 // 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. 244 // Returns true if the offer type matches the specification, false otherwise. 245 func acceptsOfferType(spec, offerType string) bool { 246 // Accept: */* 247 if spec == "*/*" { 248 return true 249 } 250 251 var mimetype string 252 if strings.IndexByte(offerType, '/') != -1 { 253 mimetype = offerType // MIME type 254 } else { 255 mimetype = utils.GetMIME(offerType) // extension 256 } 257 258 if spec == mimetype { 259 // Accept: <MIME_type>/<MIME_subtype> 260 return true 261 } 262 263 s := strings.IndexByte(mimetype, '/') 264 // Accept: <MIME_type>/* 265 if strings.HasPrefix(spec, mimetype[:s]) && (spec[s:] == "/*" || mimetype[s:] == "/*") { 266 return true 267 } 268 269 return false 270 } 271 272 // getOffer return valid offer for header negotiation 273 func getOffer(header string, isAccepted func(spec, offer string) bool, offers ...string) string { 274 if len(offers) == 0 { 275 return "" 276 } 277 if header == "" { 278 return offers[0] 279 } 280 281 // Parse header and get accepted types with their quality and specificity 282 // See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields 283 spec, commaPos, order := "", 0, 0 284 acceptedTypes := make([]acceptedType, 0, 20) 285 for len(header) > 0 { 286 order++ 287 288 // Skip spaces 289 header = utils.TrimLeft(header, ' ') 290 291 // Get spec 292 commaPos = strings.IndexByte(header, ',') 293 if commaPos != -1 { 294 spec = utils.Trim(header[:commaPos], ' ') 295 } else { 296 spec = utils.TrimLeft(header, ' ') 297 } 298 299 // Get quality 300 quality := 1.0 301 if factorSign := strings.IndexByte(spec, ';'); factorSign != -1 { 302 factor := utils.Trim(spec[factorSign+1:], ' ') 303 if strings.HasPrefix(factor, "q=") { 304 if q, err := fasthttp.ParseUfloat(utils.UnsafeBytes(factor[2:])); err == nil { 305 quality = q 306 } 307 } 308 spec = spec[:factorSign] 309 } 310 311 // Skip if quality is 0.0 312 // See: https://www.rfc-editor.org/rfc/rfc9110#quality.values 313 if quality == 0.0 { 314 if commaPos != -1 { 315 header = header[commaPos+1:] 316 } else { 317 break 318 } 319 continue 320 } 321 322 // Get specificity 323 specificity := 0 324 // check for wildcard this could be a mime */* or a wildcard character * 325 if spec == "*/*" || spec == "*" { 326 specificity = 1 327 } else if strings.HasSuffix(spec, "/*") { 328 specificity = 2 329 } else if strings.IndexByte(spec, '/') != -1 { 330 specificity = 3 331 } else { 332 specificity = 4 333 } 334 335 // Add to accepted types 336 acceptedTypes = append(acceptedTypes, acceptedType{spec, quality, specificity, order}) 337 338 // Next 339 if commaPos != -1 { 340 header = header[commaPos+1:] 341 } else { 342 break 343 } 344 } 345 346 if len(acceptedTypes) > 1 { 347 // Sort accepted types by quality and specificity, preserving order of equal elements 348 sortAcceptedTypes(&acceptedTypes) 349 } 350 351 // Find the first offer that matches the accepted types 352 for _, acceptedType := range acceptedTypes { 353 for _, offer := range offers { 354 if len(offer) == 0 { 355 continue 356 } 357 if isAccepted(acceptedType.spec, offer) { 358 return offer 359 } 360 } 361 } 362 363 return "" 364 } 365 366 // sortAcceptedTypes sorts accepted types by quality and specificity, preserving order of equal elements 367 // 368 // Parameters are not supported, they are ignored when sorting by specificity. 369 // 370 // See: https://www.rfc-editor.org/rfc/rfc9110#name-content-negotiation-fields 371 func sortAcceptedTypes(at *[]acceptedType) { 372 if at == nil || len(*at) < 2 { 373 return 374 } 375 acceptedTypes := *at 376 377 for i := 1; i < len(acceptedTypes); i++ { 378 lo, hi := 0, i-1 379 for lo <= hi { 380 mid := (lo + hi) / 2 381 if acceptedTypes[i].quality < acceptedTypes[mid].quality || 382 (acceptedTypes[i].quality == acceptedTypes[mid].quality && acceptedTypes[i].specificity < acceptedTypes[mid].specificity) || 383 (acceptedTypes[i].quality == acceptedTypes[mid].quality && acceptedTypes[i].specificity == acceptedTypes[mid].specificity && acceptedTypes[i].order > acceptedTypes[mid].order) { 384 lo = mid + 1 385 } else { 386 hi = mid - 1 387 } 388 } 389 for j := i; j > lo; j-- { 390 acceptedTypes[j-1], acceptedTypes[j] = acceptedTypes[j], acceptedTypes[j-1] 391 } 392 } 393 } 394 395 func matchEtag(s, etag string) bool { 396 if s == etag || s == "W/"+etag || "W/"+s == etag { 397 return true 398 } 399 400 return false 401 } 402 403 func (app *App) isEtagStale(etag string, noneMatchBytes []byte) bool { 404 var start, end int 405 406 // Adapted from: 407 // https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L110 408 for i := range noneMatchBytes { 409 switch noneMatchBytes[i] { 410 case 0x20: 411 if start == end { 412 start = i + 1 413 end = i + 1 414 } 415 case 0x2c: 416 if matchEtag(app.getString(noneMatchBytes[start:end]), etag) { 417 return false 418 } 419 start = i + 1 420 end = i + 1 421 default: 422 end = i + 1 423 } 424 } 425 426 return !matchEtag(app.getString(noneMatchBytes[start:end]), etag) 427 } 428 429 func parseAddr(raw string) (string, string) { //nolint:revive // Returns (host, port) 430 if i := strings.LastIndex(raw, ":"); i != -1 { 431 return raw[:i], raw[i+1:] 432 } 433 return raw, "" 434 } 435 436 const noCacheValue = "no-cache" 437 438 // isNoCache checks if the cacheControl header value is a `no-cache`. 439 func isNoCache(cacheControl string) bool { 440 i := strings.Index(cacheControl, noCacheValue) 441 if i == -1 { 442 return false 443 } 444 445 // Xno-cache 446 if i > 0 && !(cacheControl[i-1] == ' ' || cacheControl[i-1] == ',') { 447 return false 448 } 449 450 // bla bla, no-cache 451 if i+len(noCacheValue) == len(cacheControl) { 452 return true 453 } 454 455 // bla bla, no-cacheX 456 if cacheControl[i+len(noCacheValue)] != ',' { 457 return false 458 } 459 460 // OK 461 return true 462 } 463 464 type testConn struct { 465 r bytes.Buffer 466 w bytes.Buffer 467 } 468 469 func (c *testConn) Read(b []byte) (int, error) { return c.r.Read(b) } //nolint:wrapcheck // This must not be wrapped 470 func (c *testConn) Write(b []byte) (int, error) { return c.w.Write(b) } //nolint:wrapcheck // This must not be wrapped 471 func (*testConn) Close() error { return nil } 472 473 func (*testConn) LocalAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} } 474 func (*testConn) RemoteAddr() net.Addr { return &net.TCPAddr{Port: 0, Zone: "", IP: net.IPv4zero} } 475 func (*testConn) SetDeadline(_ time.Time) error { return nil } 476 func (*testConn) SetReadDeadline(_ time.Time) error { return nil } 477 func (*testConn) SetWriteDeadline(_ time.Time) error { return nil } 478 479 func getStringImmutable(b []byte) string { 480 return string(b) 481 } 482 483 func getBytesImmutable(s string) []byte { 484 return []byte(s) 485 } 486 487 // HTTP methods and their unique INTs 488 func (app *App) methodInt(s string) int { 489 // For better performance 490 if len(app.configured.RequestMethods) == 0 { 491 // TODO: Use iota instead 492 switch s { 493 case MethodGet: 494 return 0 495 case MethodHead: 496 return 1 497 case MethodPost: 498 return 2 499 case MethodPut: 500 return 3 501 case MethodDelete: 502 return 4 503 case MethodConnect: 504 return 5 505 case MethodOptions: 506 return 6 507 case MethodTrace: 508 return 7 509 case MethodPatch: 510 return 8 511 default: 512 return -1 513 } 514 } 515 516 // For method customization 517 for i, v := range app.config.RequestMethods { 518 if s == v { 519 return i 520 } 521 } 522 523 return -1 524 } 525 526 // IsMethodSafe reports whether the HTTP method is considered safe. 527 // See https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.1 528 func IsMethodSafe(m string) bool { 529 switch m { 530 case MethodGet, 531 MethodHead, 532 MethodOptions, 533 MethodTrace: 534 return true 535 default: 536 return false 537 } 538 } 539 540 // IsMethodIdempotent reports whether the HTTP method is considered idempotent. 541 // See https://datatracker.ietf.org/doc/html/rfc9110#section-9.2.2 542 func IsMethodIdempotent(m string) bool { 543 if IsMethodSafe(m) { 544 return true 545 } 546 547 switch m { 548 case MethodPut, MethodDelete: 549 return true 550 default: 551 return false 552 } 553 } 554 555 // HTTP methods were copied from net/http. 556 const ( 557 MethodGet = "GET" // RFC 7231, 4.3.1 558 MethodHead = "HEAD" // RFC 7231, 4.3.2 559 MethodPost = "POST" // RFC 7231, 4.3.3 560 MethodPut = "PUT" // RFC 7231, 4.3.4 561 MethodPatch = "PATCH" // RFC 5789 562 MethodDelete = "DELETE" // RFC 7231, 4.3.5 563 MethodConnect = "CONNECT" // RFC 7231, 4.3.6 564 MethodOptions = "OPTIONS" // RFC 7231, 4.3.7 565 MethodTrace = "TRACE" // RFC 7231, 4.3.8 566 methodUse = "USE" 567 ) 568 569 // MIME types that are commonly used 570 const ( 571 MIMETextXML = "text/xml" 572 MIMETextHTML = "text/html" 573 MIMETextPlain = "text/plain" 574 MIMETextJavaScript = "text/javascript" 575 MIMEApplicationXML = "application/xml" 576 MIMEApplicationJSON = "application/json" 577 // Deprecated: use MIMETextJavaScript instead 578 MIMEApplicationJavaScript = "application/javascript" 579 MIMEApplicationForm = "application/x-www-form-urlencoded" 580 MIMEOctetStream = "application/octet-stream" 581 MIMEMultipartForm = "multipart/form-data" 582 583 MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" 584 MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" 585 MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" 586 MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" 587 MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" 588 MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" 589 // Deprecated: use MIMETextJavaScriptCharsetUTF8 instead 590 MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8" 591 ) 592 593 // HTTP status codes were copied from https://github.com/nginx/nginx/blob/67d2a9541826ecd5db97d604f23460210fd3e517/conf/mime.types with the following updates: 594 // - Rename StatusNonAuthoritativeInfo to StatusNonAuthoritativeInformation 595 // - Add StatusSwitchProxy (306) 596 // NOTE: Keep this list in sync with statusMessage 597 const ( 598 StatusContinue = 100 // RFC 9110, 15.2.1 599 StatusSwitchingProtocols = 101 // RFC 9110, 15.2.2 600 StatusProcessing = 102 // RFC 2518, 10.1 601 StatusEarlyHints = 103 // RFC 8297 602 603 StatusOK = 200 // RFC 9110, 15.3.1 604 StatusCreated = 201 // RFC 9110, 15.3.2 605 StatusAccepted = 202 // RFC 9110, 15.3.3 606 StatusNonAuthoritativeInformation = 203 // RFC 9110, 15.3.4 607 StatusNoContent = 204 // RFC 9110, 15.3.5 608 StatusResetContent = 205 // RFC 9110, 15.3.6 609 StatusPartialContent = 206 // RFC 9110, 15.3.7 610 StatusMultiStatus = 207 // RFC 4918, 11.1 611 StatusAlreadyReported = 208 // RFC 5842, 7.1 612 StatusIMUsed = 226 // RFC 3229, 10.4.1 613 614 StatusMultipleChoices = 300 // RFC 9110, 15.4.1 615 StatusMovedPermanently = 301 // RFC 9110, 15.4.2 616 StatusFound = 302 // RFC 9110, 15.4.3 617 StatusSeeOther = 303 // RFC 9110, 15.4.4 618 StatusNotModified = 304 // RFC 9110, 15.4.5 619 StatusUseProxy = 305 // RFC 9110, 15.4.6 620 StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused) 621 StatusTemporaryRedirect = 307 // RFC 9110, 15.4.8 622 StatusPermanentRedirect = 308 // RFC 9110, 15.4.9 623 624 StatusBadRequest = 400 // RFC 9110, 15.5.1 625 StatusUnauthorized = 401 // RFC 9110, 15.5.2 626 StatusPaymentRequired = 402 // RFC 9110, 15.5.3 627 StatusForbidden = 403 // RFC 9110, 15.5.4 628 StatusNotFound = 404 // RFC 9110, 15.5.5 629 StatusMethodNotAllowed = 405 // RFC 9110, 15.5.6 630 StatusNotAcceptable = 406 // RFC 9110, 15.5.7 631 StatusProxyAuthRequired = 407 // RFC 9110, 15.5.8 632 StatusRequestTimeout = 408 // RFC 9110, 15.5.9 633 StatusConflict = 409 // RFC 9110, 15.5.10 634 StatusGone = 410 // RFC 9110, 15.5.11 635 StatusLengthRequired = 411 // RFC 9110, 15.5.12 636 StatusPreconditionFailed = 412 // RFC 9110, 15.5.13 637 StatusRequestEntityTooLarge = 413 // RFC 9110, 15.5.14 638 StatusRequestURITooLong = 414 // RFC 9110, 15.5.15 639 StatusUnsupportedMediaType = 415 // RFC 9110, 15.5.16 640 StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17 641 StatusExpectationFailed = 417 // RFC 9110, 15.5.18 642 StatusTeapot = 418 // RFC 9110, 15.5.19 (Unused) 643 StatusMisdirectedRequest = 421 // RFC 9110, 15.5.20 644 StatusUnprocessableEntity = 422 // RFC 9110, 15.5.21 645 StatusLocked = 423 // RFC 4918, 11.3 646 StatusFailedDependency = 424 // RFC 4918, 11.4 647 StatusTooEarly = 425 // RFC 8470, 5.2. 648 StatusUpgradeRequired = 426 // RFC 9110, 15.5.22 649 StatusPreconditionRequired = 428 // RFC 6585, 3 650 StatusTooManyRequests = 429 // RFC 6585, 4 651 StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5 652 StatusUnavailableForLegalReasons = 451 // RFC 7725, 3 653 654 StatusInternalServerError = 500 // RFC 9110, 15.6.1 655 StatusNotImplemented = 501 // RFC 9110, 15.6.2 656 StatusBadGateway = 502 // RFC 9110, 15.6.3 657 StatusServiceUnavailable = 503 // RFC 9110, 15.6.4 658 StatusGatewayTimeout = 504 // RFC 9110, 15.6.5 659 StatusHTTPVersionNotSupported = 505 // RFC 9110, 15.6.6 660 StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1 661 StatusInsufficientStorage = 507 // RFC 4918, 11.5 662 StatusLoopDetected = 508 // RFC 5842, 7.2 663 StatusNotExtended = 510 // RFC 2774, 7 664 StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 665 ) 666 667 // Errors 668 var ( 669 ErrBadRequest = NewError(StatusBadRequest) // 400 670 ErrUnauthorized = NewError(StatusUnauthorized) // 401 671 ErrPaymentRequired = NewError(StatusPaymentRequired) // 402 672 ErrForbidden = NewError(StatusForbidden) // 403 673 ErrNotFound = NewError(StatusNotFound) // 404 674 ErrMethodNotAllowed = NewError(StatusMethodNotAllowed) // 405 675 ErrNotAcceptable = NewError(StatusNotAcceptable) // 406 676 ErrProxyAuthRequired = NewError(StatusProxyAuthRequired) // 407 677 ErrRequestTimeout = NewError(StatusRequestTimeout) // 408 678 ErrConflict = NewError(StatusConflict) // 409 679 ErrGone = NewError(StatusGone) // 410 680 ErrLengthRequired = NewError(StatusLengthRequired) // 411 681 ErrPreconditionFailed = NewError(StatusPreconditionFailed) // 412 682 ErrRequestEntityTooLarge = NewError(StatusRequestEntityTooLarge) // 413 683 ErrRequestURITooLong = NewError(StatusRequestURITooLong) // 414 684 ErrUnsupportedMediaType = NewError(StatusUnsupportedMediaType) // 415 685 ErrRequestedRangeNotSatisfiable = NewError(StatusRequestedRangeNotSatisfiable) // 416 686 ErrExpectationFailed = NewError(StatusExpectationFailed) // 417 687 ErrTeapot = NewError(StatusTeapot) // 418 688 ErrMisdirectedRequest = NewError(StatusMisdirectedRequest) // 421 689 ErrUnprocessableEntity = NewError(StatusUnprocessableEntity) // 422 690 ErrLocked = NewError(StatusLocked) // 423 691 ErrFailedDependency = NewError(StatusFailedDependency) // 424 692 ErrTooEarly = NewError(StatusTooEarly) // 425 693 ErrUpgradeRequired = NewError(StatusUpgradeRequired) // 426 694 ErrPreconditionRequired = NewError(StatusPreconditionRequired) // 428 695 ErrTooManyRequests = NewError(StatusTooManyRequests) // 429 696 ErrRequestHeaderFieldsTooLarge = NewError(StatusRequestHeaderFieldsTooLarge) // 431 697 ErrUnavailableForLegalReasons = NewError(StatusUnavailableForLegalReasons) // 451 698 699 ErrInternalServerError = NewError(StatusInternalServerError) // 500 700 ErrNotImplemented = NewError(StatusNotImplemented) // 501 701 ErrBadGateway = NewError(StatusBadGateway) // 502 702 ErrServiceUnavailable = NewError(StatusServiceUnavailable) // 503 703 ErrGatewayTimeout = NewError(StatusGatewayTimeout) // 504 704 ErrHTTPVersionNotSupported = NewError(StatusHTTPVersionNotSupported) // 505 705 ErrVariantAlsoNegotiates = NewError(StatusVariantAlsoNegotiates) // 506 706 ErrInsufficientStorage = NewError(StatusInsufficientStorage) // 507 707 ErrLoopDetected = NewError(StatusLoopDetected) // 508 708 ErrNotExtended = NewError(StatusNotExtended) // 510 709 ErrNetworkAuthenticationRequired = NewError(StatusNetworkAuthenticationRequired) // 511 710 ) 711 712 // HTTP Headers were copied from net/http. 713 const ( 714 HeaderAuthorization = "Authorization" 715 HeaderProxyAuthenticate = "Proxy-Authenticate" 716 HeaderProxyAuthorization = "Proxy-Authorization" 717 HeaderWWWAuthenticate = "WWW-Authenticate" 718 HeaderAge = "Age" 719 HeaderCacheControl = "Cache-Control" 720 HeaderClearSiteData = "Clear-Site-Data" 721 HeaderExpires = "Expires" 722 HeaderPragma = "Pragma" 723 HeaderWarning = "Warning" 724 HeaderAcceptCH = "Accept-CH" 725 HeaderAcceptCHLifetime = "Accept-CH-Lifetime" 726 HeaderContentDPR = "Content-DPR" 727 HeaderDPR = "DPR" 728 HeaderEarlyData = "Early-Data" 729 HeaderSaveData = "Save-Data" 730 HeaderViewportWidth = "Viewport-Width" 731 HeaderWidth = "Width" 732 HeaderETag = "ETag" 733 HeaderIfMatch = "If-Match" 734 HeaderIfModifiedSince = "If-Modified-Since" 735 HeaderIfNoneMatch = "If-None-Match" 736 HeaderIfUnmodifiedSince = "If-Unmodified-Since" 737 HeaderLastModified = "Last-Modified" 738 HeaderVary = "Vary" 739 HeaderConnection = "Connection" 740 HeaderKeepAlive = "Keep-Alive" 741 HeaderAccept = "Accept" 742 HeaderAcceptCharset = "Accept-Charset" 743 HeaderAcceptEncoding = "Accept-Encoding" 744 HeaderAcceptLanguage = "Accept-Language" 745 HeaderCookie = "Cookie" 746 HeaderExpect = "Expect" 747 HeaderMaxForwards = "Max-Forwards" 748 HeaderSetCookie = "Set-Cookie" 749 HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" 750 HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" 751 HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" 752 HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" 753 HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" 754 HeaderAccessControlMaxAge = "Access-Control-Max-Age" 755 HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" 756 HeaderAccessControlRequestMethod = "Access-Control-Request-Method" 757 HeaderOrigin = "Origin" 758 HeaderTimingAllowOrigin = "Timing-Allow-Origin" 759 HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" 760 HeaderDNT = "DNT" 761 HeaderTk = "Tk" 762 HeaderContentDisposition = "Content-Disposition" 763 HeaderContentEncoding = "Content-Encoding" 764 HeaderContentLanguage = "Content-Language" 765 HeaderContentLength = "Content-Length" 766 HeaderContentLocation = "Content-Location" 767 HeaderContentType = "Content-Type" 768 HeaderForwarded = "Forwarded" 769 HeaderVia = "Via" 770 HeaderXForwardedFor = "X-Forwarded-For" 771 HeaderXForwardedHost = "X-Forwarded-Host" 772 HeaderXForwardedProto = "X-Forwarded-Proto" 773 HeaderXForwardedProtocol = "X-Forwarded-Protocol" 774 HeaderXForwardedSsl = "X-Forwarded-Ssl" 775 HeaderXUrlScheme = "X-Url-Scheme" 776 HeaderLocation = "Location" 777 HeaderFrom = "From" 778 HeaderHost = "Host" 779 HeaderReferer = "Referer" 780 HeaderReferrerPolicy = "Referrer-Policy" 781 HeaderUserAgent = "User-Agent" 782 HeaderAllow = "Allow" 783 HeaderServer = "Server" 784 HeaderAcceptRanges = "Accept-Ranges" 785 HeaderContentRange = "Content-Range" 786 HeaderIfRange = "If-Range" 787 HeaderRange = "Range" 788 HeaderContentSecurityPolicy = "Content-Security-Policy" 789 HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" 790 HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" 791 HeaderExpectCT = "Expect-CT" 792 // Deprecated: use HeaderPermissionsPolicy instead 793 HeaderFeaturePolicy = "Feature-Policy" 794 HeaderPermissionsPolicy = "Permissions-Policy" 795 HeaderPublicKeyPins = "Public-Key-Pins" 796 HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" 797 HeaderStrictTransportSecurity = "Strict-Transport-Security" 798 HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" 799 HeaderXContentTypeOptions = "X-Content-Type-Options" 800 HeaderXDownloadOptions = "X-Download-Options" 801 HeaderXFrameOptions = "X-Frame-Options" 802 HeaderXPoweredBy = "X-Powered-By" 803 HeaderXXSSProtection = "X-XSS-Protection" 804 HeaderLastEventID = "Last-Event-ID" 805 HeaderNEL = "NEL" 806 HeaderPingFrom = "Ping-From" 807 HeaderPingTo = "Ping-To" 808 HeaderReportTo = "Report-To" 809 HeaderTE = "TE" 810 HeaderTrailer = "Trailer" 811 HeaderTransferEncoding = "Transfer-Encoding" 812 HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" 813 HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" 814 HeaderSecWebSocketKey = "Sec-WebSocket-Key" 815 HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" 816 HeaderSecWebSocketVersion = "Sec-WebSocket-Version" 817 HeaderAcceptPatch = "Accept-Patch" 818 HeaderAcceptPushPolicy = "Accept-Push-Policy" 819 HeaderAcceptSignature = "Accept-Signature" 820 HeaderAltSvc = "Alt-Svc" 821 HeaderDate = "Date" 822 HeaderIndex = "Index" 823 HeaderLargeAllocation = "Large-Allocation" 824 HeaderLink = "Link" 825 HeaderPushPolicy = "Push-Policy" 826 HeaderRetryAfter = "Retry-After" 827 HeaderServerTiming = "Server-Timing" 828 HeaderSignature = "Signature" 829 HeaderSignedHeaders = "Signed-Headers" 830 HeaderSourceMap = "SourceMap" 831 HeaderUpgrade = "Upgrade" 832 HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" 833 HeaderXPingback = "X-Pingback" 834 HeaderXRequestID = "X-Request-ID" 835 HeaderXRequestedWith = "X-Requested-With" 836 HeaderXRobotsTag = "X-Robots-Tag" 837 HeaderXUACompatible = "X-UA-Compatible" 838 ) 839 840 // Network types that are commonly used 841 const ( 842 NetworkTCP = "tcp" 843 NetworkTCP4 = "tcp4" 844 NetworkTCP6 = "tcp6" 845 ) 846 847 // Compression types 848 const ( 849 StrGzip = "gzip" 850 StrBr = "br" 851 StrDeflate = "deflate" 852 StrBrotli = "brotli" 853 ) 854 855 // Cookie SameSite 856 // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7 857 const ( 858 CookieSameSiteDisabled = "disabled" // not in RFC, just control "SameSite" attribute will not be set. 859 CookieSameSiteLaxMode = "lax" 860 CookieSameSiteStrictMode = "strict" 861 CookieSameSiteNoneMode = "none" 862 ) 863 864 // Route Constraints 865 const ( 866 ConstraintInt = "int" 867 ConstraintBool = "bool" 868 ConstraintFloat = "float" 869 ConstraintAlpha = "alpha" 870 ConstraintGuid = "guid" //nolint:revive,stylecheck // TODO: Rename to "ConstraintGUID" in v3 871 ConstraintMinLen = "minLen" 872 ConstraintMaxLen = "maxLen" 873 ConstraintLen = "len" 874 ConstraintBetweenLen = "betweenLen" 875 ConstraintMinLenLower = "minlen" 876 ConstraintMaxLenLower = "maxlen" 877 ConstraintBetweenLenLower = "betweenlen" 878 ConstraintMin = "min" 879 ConstraintMax = "max" 880 ConstraintRange = "range" 881 ConstraintDatetime = "datetime" 882 ConstraintRegex = "regex" 883 ) 884 885 func IndexRune(str string, needle int32) bool { 886 for _, b := range str { 887 if b == needle { 888 return true 889 } 890 } 891 return false 892 }