mirror of
https://github.com/soypat/lneto.git
synced 2026-08-21 23:19:03 +00:00
begin working on httpx
This commit is contained in:
@@ -0,0 +1,270 @@
|
|||||||
|
package httpx
|
||||||
|
|
||||||
|
const (
|
||||||
|
slashChar = '/'
|
||||||
|
rChar = '\r'
|
||||||
|
nChar = '\n'
|
||||||
|
defaultServerName = "fasthttp"
|
||||||
|
defaultUserAgent = "fasthttp"
|
||||||
|
defaultContentType = "text/plain; charset=utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
strSlashSlash = "//"
|
||||||
|
strSlashDotDot = "/.."
|
||||||
|
strSlashDotSlash = "/./"
|
||||||
|
strSlashDotDotSlash = "/../"
|
||||||
|
strBackSlashDotDot = `\..`
|
||||||
|
strBackSlashDotBackSlash = `\.\`
|
||||||
|
strSlashDotDotBackSlash = `/..\`
|
||||||
|
strBackSlashDotDotBackSlash = `\..\`
|
||||||
|
strCRLF = "\r\n"
|
||||||
|
strHTTP = "http"
|
||||||
|
strHTTPS = "https"
|
||||||
|
strHTTP10 = "HTTP/1.0"
|
||||||
|
strHTTP11 = "HTTP/1.1"
|
||||||
|
strColon = ":"
|
||||||
|
strColonSlashSlash = "://"
|
||||||
|
strColonSpace = ": "
|
||||||
|
strCommaSpace = ", "
|
||||||
|
strGMT = "GMT"
|
||||||
|
|
||||||
|
strResponseContinue = "HTTP/1.1 100 Continue\r\n\r\n"
|
||||||
|
|
||||||
|
strExpect = HeaderExpect
|
||||||
|
strConnection = HeaderConnection
|
||||||
|
strContentLength = HeaderContentLength
|
||||||
|
strContentType = HeaderContentType
|
||||||
|
strDate = HeaderDate
|
||||||
|
strHost = HeaderHost
|
||||||
|
strReferer = HeaderReferer
|
||||||
|
strServer = HeaderServer
|
||||||
|
strTransferEncoding = HeaderTransferEncoding
|
||||||
|
strContentEncoding = HeaderContentEncoding
|
||||||
|
strAcceptEncoding = HeaderAcceptEncoding
|
||||||
|
strUserAgent = HeaderUserAgent
|
||||||
|
strCookie = HeaderCookie
|
||||||
|
strSetCookie = HeaderSetCookie
|
||||||
|
strLocation = HeaderLocation
|
||||||
|
strIfModifiedSince = HeaderIfModifiedSince
|
||||||
|
strLastModified = HeaderLastModified
|
||||||
|
strAcceptRanges = HeaderAcceptRanges
|
||||||
|
strRange = HeaderRange
|
||||||
|
strContentRange = HeaderContentRange
|
||||||
|
strAuthorization = HeaderAuthorization
|
||||||
|
strTE = HeaderTE
|
||||||
|
strTrailer = HeaderTrailer
|
||||||
|
strMaxForwards = HeaderMaxForwards
|
||||||
|
strProxyConnection = HeaderProxyConnection
|
||||||
|
strProxyAuthenticate = HeaderProxyAuthenticate
|
||||||
|
strProxyAuthorization = HeaderProxyAuthorization
|
||||||
|
strWWWAuthenticate = HeaderWWWAuthenticate
|
||||||
|
strVary = HeaderVary
|
||||||
|
|
||||||
|
strCookieExpires = "expires"
|
||||||
|
strCookieDomain = "domain"
|
||||||
|
strCookiePath = "path"
|
||||||
|
strCookieHTTPOnly = "HttpOnly"
|
||||||
|
strCookieSecure = "secure"
|
||||||
|
strCookieMaxAge = "max-age"
|
||||||
|
strCookieSameSite = "SameSite"
|
||||||
|
strCookieSameSiteLax = "Lax"
|
||||||
|
strCookieSameSiteStrict = "Strict"
|
||||||
|
strCookieSameSiteNone = "None"
|
||||||
|
|
||||||
|
strClose = "close"
|
||||||
|
strGzip = "gzip"
|
||||||
|
strBr = "br"
|
||||||
|
strDeflate = "deflate"
|
||||||
|
strKeepAlive = "keep-alive"
|
||||||
|
strUpgrade = "Upgrade"
|
||||||
|
strChunked = "chunked"
|
||||||
|
strIdentity = "identity"
|
||||||
|
str100Continue = "100-continue"
|
||||||
|
strPostArgsContentType = "application/x-www-form-urlencoded"
|
||||||
|
strDefaultContentType = "application/octet-stream"
|
||||||
|
strMultipartFormData = "multipart/form-data"
|
||||||
|
strBoundary = "boundary"
|
||||||
|
strBytes = "bytes"
|
||||||
|
strBasicSpace = "Basic "
|
||||||
|
|
||||||
|
strApplicationSlash = "application/"
|
||||||
|
strImageSVG = "image/svg"
|
||||||
|
strImageIcon = "image/x-icon"
|
||||||
|
strFontSlash = "font/"
|
||||||
|
strMultipartSlash = "multipart/"
|
||||||
|
strTextSlash = "text/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Headers.
|
||||||
|
const (
|
||||||
|
// Authentication.
|
||||||
|
HeaderAuthorization = "Authorization"
|
||||||
|
HeaderProxyAuthenticate = "Proxy-Authenticate"
|
||||||
|
HeaderProxyAuthorization = "Proxy-Authorization"
|
||||||
|
HeaderWWWAuthenticate = "WWW-Authenticate"
|
||||||
|
|
||||||
|
// Caching.
|
||||||
|
HeaderAge = "Age"
|
||||||
|
HeaderCacheControl = "Cache-Control"
|
||||||
|
HeaderClearSiteData = "Clear-Site-Data"
|
||||||
|
HeaderExpires = "Expires"
|
||||||
|
HeaderPragma = "Pragma"
|
||||||
|
HeaderWarning = "Warning"
|
||||||
|
|
||||||
|
// Client hints.
|
||||||
|
HeaderAcceptCH = "Accept-CH"
|
||||||
|
HeaderAcceptCHLifetime = "Accept-CH-Lifetime"
|
||||||
|
HeaderContentDPR = "Content-DPR"
|
||||||
|
HeaderDPR = "DPR"
|
||||||
|
HeaderEarlyData = "Early-Data"
|
||||||
|
HeaderSaveData = "Save-Data"
|
||||||
|
HeaderViewportWidth = "Viewport-Width"
|
||||||
|
HeaderWidth = "Width"
|
||||||
|
|
||||||
|
// Conditionals.
|
||||||
|
HeaderETag = "ETag"
|
||||||
|
HeaderIfMatch = "If-Match"
|
||||||
|
HeaderIfModifiedSince = "If-Modified-Since"
|
||||||
|
HeaderIfNoneMatch = "If-None-Match"
|
||||||
|
HeaderIfUnmodifiedSince = "If-Unmodified-Since"
|
||||||
|
HeaderLastModified = "Last-Modified"
|
||||||
|
HeaderVary = "Vary"
|
||||||
|
|
||||||
|
// Connection management.
|
||||||
|
HeaderConnection = "Connection"
|
||||||
|
HeaderKeepAlive = "Keep-Alive"
|
||||||
|
HeaderProxyConnection = "Proxy-Connection"
|
||||||
|
|
||||||
|
// Content negotiation.
|
||||||
|
HeaderAccept = "Accept"
|
||||||
|
HeaderAcceptCharset = "Accept-Charset"
|
||||||
|
HeaderAcceptEncoding = "Accept-Encoding"
|
||||||
|
HeaderAcceptLanguage = "Accept-Language"
|
||||||
|
|
||||||
|
// Controls.
|
||||||
|
HeaderCookie = "Cookie"
|
||||||
|
HeaderExpect = "Expect"
|
||||||
|
HeaderMaxForwards = "Max-Forwards"
|
||||||
|
HeaderSetCookie = "Set-Cookie"
|
||||||
|
|
||||||
|
// CORS.
|
||||||
|
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"
|
||||||
|
|
||||||
|
// Do Not Track.
|
||||||
|
HeaderDNT = "DNT"
|
||||||
|
HeaderTk = "Tk"
|
||||||
|
|
||||||
|
// Downloads.
|
||||||
|
HeaderContentDisposition = "Content-Disposition"
|
||||||
|
|
||||||
|
// Message body information.
|
||||||
|
HeaderContentEncoding = "Content-Encoding"
|
||||||
|
HeaderContentLanguage = "Content-Language"
|
||||||
|
HeaderContentLength = "Content-Length"
|
||||||
|
HeaderContentLocation = "Content-Location"
|
||||||
|
HeaderContentType = "Content-Type"
|
||||||
|
|
||||||
|
// Proxies.
|
||||||
|
HeaderForwarded = "Forwarded"
|
||||||
|
HeaderVia = "Via"
|
||||||
|
HeaderXForwardedFor = "X-Forwarded-For"
|
||||||
|
HeaderXForwardedHost = "X-Forwarded-Host"
|
||||||
|
HeaderXForwardedProto = "X-Forwarded-Proto"
|
||||||
|
|
||||||
|
// Redirects.
|
||||||
|
HeaderLocation = "Location"
|
||||||
|
|
||||||
|
// Request context.
|
||||||
|
HeaderFrom = "From"
|
||||||
|
HeaderHost = "Host"
|
||||||
|
HeaderReferer = "Referer"
|
||||||
|
HeaderReferrerPolicy = "Referrer-Policy"
|
||||||
|
HeaderUserAgent = "User-Agent"
|
||||||
|
|
||||||
|
// Response context.
|
||||||
|
HeaderAllow = "Allow"
|
||||||
|
HeaderServer = "Server"
|
||||||
|
|
||||||
|
// Range requests.
|
||||||
|
HeaderAcceptRanges = "Accept-Ranges"
|
||||||
|
HeaderContentRange = "Content-Range"
|
||||||
|
HeaderIfRange = "If-Range"
|
||||||
|
HeaderRange = "Range"
|
||||||
|
|
||||||
|
// Security.
|
||||||
|
HeaderContentSecurityPolicy = "Content-Security-Policy"
|
||||||
|
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
|
||||||
|
HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
|
||||||
|
HeaderExpectCT = "Expect-CT"
|
||||||
|
HeaderFeaturePolicy = "Feature-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"
|
||||||
|
|
||||||
|
// Server-sent event.
|
||||||
|
HeaderLastEventID = "Last-Event-ID"
|
||||||
|
HeaderNEL = "NEL"
|
||||||
|
HeaderPingFrom = "Ping-From"
|
||||||
|
HeaderPingTo = "Ping-To"
|
||||||
|
HeaderReportTo = "Report-To"
|
||||||
|
|
||||||
|
// Transfer coding.
|
||||||
|
HeaderTE = "TE"
|
||||||
|
HeaderTrailer = "Trailer"
|
||||||
|
HeaderTransferEncoding = "Transfer-Encoding"
|
||||||
|
|
||||||
|
// WebSockets.
|
||||||
|
HeaderSecWebSocketAccept = "Sec-WebSocket-Accept"
|
||||||
|
HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" /* #nosec G101 */
|
||||||
|
HeaderSecWebSocketKey = "Sec-WebSocket-Key"
|
||||||
|
HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol"
|
||||||
|
HeaderSecWebSocketVersion = "Sec-WebSocket-Version"
|
||||||
|
|
||||||
|
// Other.
|
||||||
|
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"
|
||||||
|
HeaderXRequestedWith = "X-Requested-With"
|
||||||
|
HeaderXRobotsTag = "X-Robots-Tag"
|
||||||
|
HeaderXUACompatible = "X-UA-Compatible"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Probably replace these with short functions to take up less program memory
|
||||||
|
const (
|
||||||
|
hex2intTable = "\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x00\x01\x02\x03\x04\x05\x06\a\b\t\x10\x10\x10\x10\x10\x10\x10\n\v\f\r\x0e\x0f\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\n\v\f\r\x0e\x0f\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10"
|
||||||
|
toLowerTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
|
||||||
|
toUpperTable = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~\u007f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
|
||||||
|
quotedArgShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
|
||||||
|
quotedPathShouldEscapeTable = "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"
|
||||||
|
)
|
||||||
@@ -0,0 +1,768 @@
|
|||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNeedMore = errors.New("need more data: cannot find trailing lf")
|
||||||
|
errInvalidName = errors.New("invalid header name")
|
||||||
|
errSmallBuffer = errors.New("small read buffer. Increase ReadBufferSize")
|
||||||
|
errNonNumericChars = errors.New("non-numeric chars found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type headerScanner struct {
|
||||||
|
b []byte
|
||||||
|
key []byte
|
||||||
|
value []byte
|
||||||
|
err error
|
||||||
|
|
||||||
|
// hLen stores header subslice len
|
||||||
|
hLen int
|
||||||
|
|
||||||
|
disableNormalizing bool
|
||||||
|
|
||||||
|
// by checking whether the next line contains a colon or not to tell
|
||||||
|
// it's a header entry or a multi line value of current header entry.
|
||||||
|
// the side effect of this operation is that we know the index of the
|
||||||
|
// next colon and new line, so this can be used during next iteration,
|
||||||
|
// instead of find them again.
|
||||||
|
nextColon int
|
||||||
|
nextNewLine int
|
||||||
|
|
||||||
|
initialized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerValueScanner struct {
|
||||||
|
b string
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) parse(buf []byte) (int, error) {
|
||||||
|
m, err := h.parseFirstLine(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.rawHeaders, _, err = readRawHeaders(h.rawHeaders[:0], b2s(buf[m:]))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var n int
|
||||||
|
n, err = h.parseHeaders(buf[m:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return m + n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hb *headerBuf) offBuf() []byte {
|
||||||
|
return hb.buf[hb.off:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hb *headerBuf) scanLine() []byte {
|
||||||
|
buf := hb.scanUntilByte('\n')
|
||||||
|
if len(buf) > 0 && buf[len(buf)-1] == '\r' {
|
||||||
|
buf = buf[:len(buf)-1] // exclude carriage return.
|
||||||
|
}
|
||||||
|
if hb.off < len(hb.buf) {
|
||||||
|
hb.off++ // consume newline.
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hb *headerBuf) scanUntilByte(c byte) []byte {
|
||||||
|
buf := hb.offBuf()
|
||||||
|
idx := bytes.IndexByte(buf, c)
|
||||||
|
if idx >= 0 {
|
||||||
|
buf = buf[:idx]
|
||||||
|
}
|
||||||
|
hb.off += len(buf)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hb *headerBuf) parseFirstLine() (method, uri, proto headerSlice, flags flags, err error) {
|
||||||
|
var b []byte
|
||||||
|
for len(b) == 0 {
|
||||||
|
b = hb.scanLine()
|
||||||
|
}
|
||||||
|
if len(b) < 5 {
|
||||||
|
return method, uri, proto, 0, errors.New("too short first HTTP line")
|
||||||
|
}
|
||||||
|
|
||||||
|
methodEnd := max(0, bytes.IndexByte(b, ' '))
|
||||||
|
reqURIEnd := bytes.IndexByte(b[methodEnd+1:], ' ')
|
||||||
|
switch {
|
||||||
|
case reqURIEnd < 0:
|
||||||
|
flags |= noHTTP11
|
||||||
|
reqURIEnd = methodEnd + 1
|
||||||
|
case reqURIEnd == 0:
|
||||||
|
return method, uri, proto, flags, errors.New("empty URI")
|
||||||
|
case b2s(b[reqURIEnd+1:]) != strHTTP11:
|
||||||
|
flags |= noHTTP11
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
proto = hb.slice(b[reqURIEnd+1:])
|
||||||
|
}
|
||||||
|
uri = hb.slice(b[methodEnd+1 : reqURIEnd])
|
||||||
|
method = hb.slice(b[:methodEnd])
|
||||||
|
return method, uri, proto, flags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRawHeaders(dst []byte, buf string) ([]byte, int, error) {
|
||||||
|
n := strings.IndexByte(buf, nChar)
|
||||||
|
if n < 0 {
|
||||||
|
return dst[:0], 0, errNeedMore
|
||||||
|
}
|
||||||
|
if (n == 1 && buf[0] == rChar) || n == 0 {
|
||||||
|
// empty headers
|
||||||
|
return dst, n + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n++
|
||||||
|
b := buf
|
||||||
|
m := n
|
||||||
|
for {
|
||||||
|
b = b[m:]
|
||||||
|
m = strings.IndexByte(b, nChar)
|
||||||
|
if m < 0 {
|
||||||
|
return dst, 0, errNeedMore
|
||||||
|
}
|
||||||
|
m++
|
||||||
|
n += m
|
||||||
|
if (m == 2 && b[0] == rChar) || m == 1 {
|
||||||
|
dst = append(dst, buf[:n]...)
|
||||||
|
return dst, n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) parseHeaders(buf []byte) (int, error) {
|
||||||
|
h.contentLength = -2
|
||||||
|
h.scanner = headerScanner{}
|
||||||
|
s := &h.scanner
|
||||||
|
s.b = buf
|
||||||
|
s.disableNormalizing = h.disableNormalizing
|
||||||
|
var err error
|
||||||
|
for s.next() {
|
||||||
|
key := b2s(s.key)
|
||||||
|
value := b2s(s.value)
|
||||||
|
if len(key) > 0 {
|
||||||
|
// Spaces between the header key and colon are not allowed.
|
||||||
|
// See RFC 7230, Section 3.2.4.
|
||||||
|
|
||||||
|
if strings.IndexByte(key, ' ') != -1 || strings.IndexByte(key, '\t') != -1 {
|
||||||
|
err = fmt.Errorf("invalid header key %q", s.key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.disableSpecialHeader {
|
||||||
|
h.h = appendArg(h.h, key, value, argsHasValue)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.key[0] | 0x20 {
|
||||||
|
case 'h':
|
||||||
|
if caseInsensitiveCompare(key, strHost) {
|
||||||
|
h.host = append(h.host[:0], value...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case 'u':
|
||||||
|
if caseInsensitiveCompare(key, strUserAgent) {
|
||||||
|
h.userAgent = append(h.userAgent[:0], value...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case 'c':
|
||||||
|
if caseInsensitiveCompare(key, strContentType) {
|
||||||
|
h.contentType = append(h.contentType[:0], value...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if caseInsensitiveCompare(key, strContentLength) {
|
||||||
|
if h.contentLength != -1 {
|
||||||
|
var nerr error
|
||||||
|
if h.contentLength, nerr = parseContentLength(b2s(s.value)); nerr != nil {
|
||||||
|
if err == nil {
|
||||||
|
err = nerr
|
||||||
|
}
|
||||||
|
h.contentLength = -2
|
||||||
|
} else {
|
||||||
|
h.contentLengthBytes = append(h.contentLengthBytes[:0], value...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if caseInsensitiveCompare(key, strConnection) {
|
||||||
|
if b2s(s.value) == strClose {
|
||||||
|
h.connectionClose = true
|
||||||
|
} else {
|
||||||
|
h.connectionClose = false
|
||||||
|
h.h = appendArg(h.h, key, value, argsHasValue)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case 't':
|
||||||
|
if caseInsensitiveCompare(key, strTransferEncoding) {
|
||||||
|
if value != strIdentity {
|
||||||
|
h.contentLength = -1
|
||||||
|
h.h = setArg(h.h, strTransferEncoding, strChunked, argsHasValue)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if caseInsensitiveCompare(key, strTrailer) {
|
||||||
|
if nerr := h.SetTrailer(value); nerr != nil {
|
||||||
|
if err == nil {
|
||||||
|
err = nerr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.h = appendArg(h.h, key, value, argsHasValue)
|
||||||
|
}
|
||||||
|
if s.err != nil && err == nil {
|
||||||
|
err = s.err
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
h.connectionClose = true
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.contentLength < 0 {
|
||||||
|
h.contentLengthBytes = h.contentLengthBytes[:0]
|
||||||
|
}
|
||||||
|
if h.noHTTP11 && !h.connectionClose {
|
||||||
|
// close connection for non-http/1.1 request unless 'Connection: keep-alive' is set.
|
||||||
|
v := peekArgStr(h.h, strConnection)
|
||||||
|
h.connectionClose = !hasHeaderValue(b2s(v), strKeepAlive)
|
||||||
|
}
|
||||||
|
return s.hLen, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *headerScanner) next() bool {
|
||||||
|
if !s.initialized {
|
||||||
|
s.nextColon = -1
|
||||||
|
s.nextNewLine = -1
|
||||||
|
s.initialized = true
|
||||||
|
}
|
||||||
|
bLen := len(s.b)
|
||||||
|
if bLen >= 2 && s.b[0] == rChar && s.b[1] == nChar {
|
||||||
|
s.b = s.b[2:]
|
||||||
|
s.hLen += 2
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bLen >= 1 && s.b[0] == nChar {
|
||||||
|
s.b = s.b[1:]
|
||||||
|
s.hLen++
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
if s.nextColon >= 0 {
|
||||||
|
n = s.nextColon
|
||||||
|
s.nextColon = -1
|
||||||
|
} else {
|
||||||
|
n = bytes.IndexByte(s.b, ':')
|
||||||
|
|
||||||
|
// There can't be a \n inside the header name, check for this.
|
||||||
|
x := bytes.IndexByte(s.b, nChar)
|
||||||
|
if x < 0 {
|
||||||
|
// A header name should always at some point be followed by a \n
|
||||||
|
// even if it's the one that terminates the header block.
|
||||||
|
s.err = errNeedMore
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if x < n {
|
||||||
|
// There was a \n before the :
|
||||||
|
s.err = errInvalidName
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
s.err = errNeedMore
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.key = s.b[:n]
|
||||||
|
normalizeHeaderKey(s.key, s.disableNormalizing)
|
||||||
|
n++
|
||||||
|
for len(s.b) > n && s.b[n] == ' ' {
|
||||||
|
n++
|
||||||
|
// the newline index is a relative index, and lines below trimmed `s.b` by `n`,
|
||||||
|
// so the relative newline index also shifted forward. it's safe to decrease
|
||||||
|
// to a minus value, it means it's invalid, and will find the newline again.
|
||||||
|
s.nextNewLine--
|
||||||
|
}
|
||||||
|
s.hLen += n
|
||||||
|
s.b = s.b[n:]
|
||||||
|
if s.nextNewLine >= 0 {
|
||||||
|
n = s.nextNewLine
|
||||||
|
s.nextNewLine = -1
|
||||||
|
} else {
|
||||||
|
n = bytes.IndexByte(s.b, nChar)
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
s.err = errNeedMore
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
isMultiLineValue := false
|
||||||
|
for {
|
||||||
|
if n+1 >= len(s.b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s.b[n+1] != ' ' && s.b[n+1] != '\t' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d := bytes.IndexByte(s.b[n+1:], nChar)
|
||||||
|
if d <= 0 {
|
||||||
|
break
|
||||||
|
} else if d == 1 && s.b[n+1] == rChar {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e := n + d + 1
|
||||||
|
if c := bytes.IndexByte(s.b[n+1:e], ':'); c >= 0 {
|
||||||
|
s.nextColon = c
|
||||||
|
s.nextNewLine = d - c - 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
isMultiLineValue = true
|
||||||
|
n = e
|
||||||
|
}
|
||||||
|
if n >= len(s.b) {
|
||||||
|
s.err = errNeedMore
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
oldB := s.b
|
||||||
|
s.value = s.b[:n]
|
||||||
|
s.hLen += n + 1
|
||||||
|
s.b = s.b[n+1:]
|
||||||
|
|
||||||
|
if n > 0 && s.value[n-1] == rChar {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
for n > 0 && s.value[n-1] == ' ' {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
s.value = s.value[:n]
|
||||||
|
if isMultiLineValue {
|
||||||
|
s.value, s.b, s.hLen = normalizeHeaderValue(s.value, oldB, s.hLen)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeHeaderKey(b []byte, disableNormalizing bool) {
|
||||||
|
if disableNormalizing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(b)
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = toUpperTable[b[0]]
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
p := &b[i]
|
||||||
|
if *p == '-' {
|
||||||
|
i++
|
||||||
|
if i < n {
|
||||||
|
b[i] = toUpperTable[b[i]]
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
*p = toLowerTable[*p]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeHeaderValue(ov, ob []byte, headerLength int) (nv, nb []byte, nhl int) {
|
||||||
|
nv = ov
|
||||||
|
length := len(ov)
|
||||||
|
if length <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
write := 0
|
||||||
|
shrunk := 0
|
||||||
|
lineStart := false
|
||||||
|
for read := 0; read < length; read++ {
|
||||||
|
c := ov[read]
|
||||||
|
switch {
|
||||||
|
case c == rChar || c == nChar:
|
||||||
|
shrunk++
|
||||||
|
if c == nChar {
|
||||||
|
lineStart = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case lineStart && c == '\t':
|
||||||
|
c = ' '
|
||||||
|
default:
|
||||||
|
lineStart = false
|
||||||
|
}
|
||||||
|
nv[write] = c
|
||||||
|
write++
|
||||||
|
}
|
||||||
|
|
||||||
|
nv = nv[:write]
|
||||||
|
copy(ob[write:], ob[write+shrunk:])
|
||||||
|
|
||||||
|
// Check if we need to skip \r\n or just \n
|
||||||
|
skip := 0
|
||||||
|
if ob[write] == rChar {
|
||||||
|
if ob[write+1] == nChar {
|
||||||
|
skip += 2
|
||||||
|
} else {
|
||||||
|
skip++
|
||||||
|
}
|
||||||
|
} else if ob[write] == nChar {
|
||||||
|
skip++
|
||||||
|
}
|
||||||
|
|
||||||
|
nb = ob[write+skip : len(ob)-shrunk]
|
||||||
|
nhl = headerLength - shrunk
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseContentLength(b string) (int, error) {
|
||||||
|
v, n, err := parseUintBuf(b)
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("cannot parse Content-Length: %w", err)
|
||||||
|
}
|
||||||
|
if n != len(b) {
|
||||||
|
return -1, fmt.Errorf("cannot parse Content-Length: %w", errNonNumericChars)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasHeaderValue(s, value string) bool {
|
||||||
|
var vs headerValueScanner
|
||||||
|
vs.b = s
|
||||||
|
for vs.next() {
|
||||||
|
if caseInsensitiveCompare(vs.value, value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *headerValueScanner) next() bool {
|
||||||
|
b := s.b
|
||||||
|
if len(b) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n := strings.IndexByte(b, ',')
|
||||||
|
if n < 0 {
|
||||||
|
s.value = stripSpace(b)
|
||||||
|
s.b = b[len(b):]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s.value = stripSpace(b[:n])
|
||||||
|
s.b = b[n+1:]
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextLine(b []byte) ([]byte, []byte, error) {
|
||||||
|
nNext := bytes.IndexByte(b, nChar)
|
||||||
|
if nNext < 0 {
|
||||||
|
return nil, nil, errNeedMore
|
||||||
|
}
|
||||||
|
n := nNext
|
||||||
|
if n > 0 && b[n-1] == rChar {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
return b[:n], b[nNext+1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripSpace(b string) string {
|
||||||
|
for len(b) > 0 && b[0] == ' ' {
|
||||||
|
b = b[1:]
|
||||||
|
}
|
||||||
|
for len(b) > 0 && b[len(b)-1] == ' ' {
|
||||||
|
b = b[:len(b)-1]
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errEmptyInt = errors.New("empty integer")
|
||||||
|
errUnexpectedFirstChar = errors.New("unexpected first char found. Expecting 0-9")
|
||||||
|
errUnexpectedTrailingChar = errors.New("unexpected trailing char found. Expecting 0-9")
|
||||||
|
errTooLongInt = errors.New("too long int")
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseUintBuf(b string) (int, int, error) {
|
||||||
|
n := len(b)
|
||||||
|
if n == 0 {
|
||||||
|
return -1, 0, errEmptyInt
|
||||||
|
}
|
||||||
|
v := 0
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
c := b[i]
|
||||||
|
k := c - '0'
|
||||||
|
if k > 9 {
|
||||||
|
if i == 0 {
|
||||||
|
return -1, i, errUnexpectedFirstChar
|
||||||
|
}
|
||||||
|
return v, i, nil
|
||||||
|
}
|
||||||
|
vNew := 10*v + int(k)
|
||||||
|
// Test for overflow.
|
||||||
|
if vNew < v {
|
||||||
|
return -1, i, errTooLongInt
|
||||||
|
}
|
||||||
|
v = vNew
|
||||||
|
}
|
||||||
|
return v, n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Request Parsing
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Read reads request header from r.
|
||||||
|
//
|
||||||
|
// io.EOF is returned if r is closed before reading the first header byte.
|
||||||
|
func (h *header) Read(r *bufio.Reader) error {
|
||||||
|
return h.readLoop(r, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readLoop reads request header from r optionally loops until it has enough data.
|
||||||
|
//
|
||||||
|
// io.EOF is returned if r is closed before reading the first header byte.
|
||||||
|
func (h *header) readLoop(r *bufio.Reader, waitForMore bool) error {
|
||||||
|
n := 1
|
||||||
|
for {
|
||||||
|
err := h.tryRead(r, n)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !waitForMore || err != errNeedMore {
|
||||||
|
h.resetSkipNormalize()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n = r.Buffered() + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) tryRead(r *bufio.Reader, n int) error {
|
||||||
|
h.resetSkipNormalize()
|
||||||
|
b, err := r.Peek(n)
|
||||||
|
if len(b) == 0 {
|
||||||
|
if err == io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
panic("bufio.Reader.Peek() returned nil, nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .
|
||||||
|
if err == bufio.ErrBufferFull {
|
||||||
|
return &ErrSmallBuffer{
|
||||||
|
error: fmt.Errorf("error when reading request headers: %w (n=%d, r.Buffered()=%d)", errSmallBuffer, n, r.Buffered()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// n == 1 on the first read for the request.
|
||||||
|
if n == 1 {
|
||||||
|
// We didn't read a single byte.
|
||||||
|
return ErrNothingRead{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("error when reading request headers: %w", err)
|
||||||
|
}
|
||||||
|
b = mustPeekBuffered(r)
|
||||||
|
headersLen, errParse := h.parse(b)
|
||||||
|
if errParse != nil {
|
||||||
|
return headerError("request", err, errParse, b, false)
|
||||||
|
}
|
||||||
|
mustDiscard(r, headersLen)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) resetSkipNormalize() {
|
||||||
|
h.noHTTP11 = false
|
||||||
|
h.connectionClose = false
|
||||||
|
|
||||||
|
h.contentLength = 0
|
||||||
|
h.contentLengthBytes = h.contentLengthBytes[:0]
|
||||||
|
|
||||||
|
h.method = h.method[:0]
|
||||||
|
h.proto = h.proto[:0]
|
||||||
|
h.requestURI = h.requestURI[:0]
|
||||||
|
h.host = h.host[:0]
|
||||||
|
h.contentType = h.contentType[:0]
|
||||||
|
h.userAgent = h.userAgent[:0]
|
||||||
|
h.trailer = h.trailer[:0]
|
||||||
|
h.mulHeader = h.mulHeader[:0]
|
||||||
|
|
||||||
|
h.h = h.h[:0]
|
||||||
|
h.cookies = h.cookies[:0]
|
||||||
|
h.cookiesCollected = false
|
||||||
|
|
||||||
|
h.rawHeaders = h.rawHeaders[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func headerError(typ string, err, errParse error, b []byte, secureErrorLogMessage bool) error {
|
||||||
|
if errParse != errNeedMore {
|
||||||
|
return headerErrorMsg(typ, errParse, b, secureErrorLogMessage)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return errNeedMore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buggy servers may leave trailing CRLFs after http body.
|
||||||
|
// Treat this case as EOF.
|
||||||
|
if isOnlyCRLF(b) {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != bufio.ErrBufferFull {
|
||||||
|
return headerErrorMsg(typ, err, b, secureErrorLogMessage)
|
||||||
|
}
|
||||||
|
return &ErrSmallBuffer{
|
||||||
|
error: headerErrorMsg(typ, errSmallBuffer, b, secureErrorLogMessage),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOnlyCRLF(b []byte) bool {
|
||||||
|
for _, ch := range b {
|
||||||
|
if ch != rChar && ch != nChar {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func headerErrorMsg(typ string, err error, b []byte, secureErrorLogMessage bool) error {
|
||||||
|
return fmt.Errorf("error when reading %s headers: %w. Buffer size=%d", typ, err, len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNothingRead is returned when a keep-alive connection is closed,
|
||||||
|
// either because the remote closed it or because of a read timeout.
|
||||||
|
type ErrNothingRead struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrSmallBuffer is returned when the provided buffer size is too small
|
||||||
|
// for reading request and/or response headers.
|
||||||
|
//
|
||||||
|
// ReadBufferSize value from Server or clients should reduce the number
|
||||||
|
// of such errors.
|
||||||
|
type ErrSmallBuffer struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustPeekBuffered(r *bufio.Reader) []byte {
|
||||||
|
buf, err := r.Peek(r.Buffered())
|
||||||
|
if len(buf) == 0 || err != nil {
|
||||||
|
panic(fmt.Sprintf("bufio.Reader.Peek() returned unexpected data (%q, %v)", buf, err))
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustDiscard(r *bufio.Reader, n int) {
|
||||||
|
if _, err := r.Discard(n); err != nil {
|
||||||
|
panic(fmt.Sprintf("bufio.Reader.Discard(%d) failed: %v", n, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek returns header value for the given key.
|
||||||
|
//
|
||||||
|
// The returned value is valid until the request is released,
|
||||||
|
// either though ReleaseRequest or your request handler returning.
|
||||||
|
// Do not store references to returned value. Make copies instead.
|
||||||
|
func (h *header) Peek(key string) []byte {
|
||||||
|
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
|
||||||
|
return h.peek(b2s(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Host returns Host header value.
|
||||||
|
func (h *header) Host() []byte {
|
||||||
|
if h.disableSpecialHeader {
|
||||||
|
return peekArg(h.h, HeaderHost)
|
||||||
|
}
|
||||||
|
return h.host
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHeaderKeyBytes(kv *argsKV, key string, disableNormalizing bool) []byte {
|
||||||
|
kv.key = append(kv.key[:0], key...)
|
||||||
|
normalizeHeaderKey(kv.key, disableNormalizing)
|
||||||
|
return kv.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func peekArg(h []argsKV, k string) []byte {
|
||||||
|
for i, n := 0, len(h); i < n; i++ {
|
||||||
|
kv := &h[i]
|
||||||
|
if b2s(kv.key) == k {
|
||||||
|
return kv.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) peek(key string) []byte {
|
||||||
|
switch key {
|
||||||
|
case HeaderHost:
|
||||||
|
return h.Host()
|
||||||
|
case HeaderContentType:
|
||||||
|
return h.ContentType()
|
||||||
|
case HeaderUserAgent:
|
||||||
|
return h.UserAgent()
|
||||||
|
case HeaderConnection:
|
||||||
|
if h.ConnectionClose() {
|
||||||
|
return []byte(strClose)
|
||||||
|
}
|
||||||
|
return peekArg(h.h, key)
|
||||||
|
case HeaderContentLength:
|
||||||
|
return h.contentLengthBytes
|
||||||
|
case HeaderCookie:
|
||||||
|
if h.cookiesCollected {
|
||||||
|
return appendRequestCookieBytes(nil, h.cookies)
|
||||||
|
}
|
||||||
|
return peekArg(h.h, key)
|
||||||
|
case HeaderTrailer:
|
||||||
|
return appendArgsKey(nil, h.trailer, strCommaSpace)
|
||||||
|
default:
|
||||||
|
return peekArg(h.h, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectionClose returns true if 'Connection: close' header is set.
|
||||||
|
func (h *header) ConnectionClose() bool {
|
||||||
|
return h.connectionClose
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgent returns User-Agent header value.
|
||||||
|
func (h *header) UserAgent() []byte {
|
||||||
|
if h.disableSpecialHeader {
|
||||||
|
return peekArg(h.h, HeaderUserAgent)
|
||||||
|
}
|
||||||
|
return h.userAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendArgsKey(dst []byte, args []argsKV, sep string) []byte {
|
||||||
|
for i, n := 0, len(args); i < n; i++ {
|
||||||
|
kv := &args[i]
|
||||||
|
dst = append(dst, kv.key...)
|
||||||
|
if i+1 < n {
|
||||||
|
dst = append(dst, sep...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// b2s converts byte slice to a string without memory allocation.
|
||||||
|
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
|
||||||
|
func b2s(b []byte) string {
|
||||||
|
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// s2b converts string to a byte slice without memory allocation.
|
||||||
|
func s2b(s string) []byte {
|
||||||
|
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||||
|
}
|
||||||
@@ -0,0 +1,622 @@
|
|||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/soypat/lneto/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headerBuf struct {
|
||||||
|
buf []byte
|
||||||
|
off int // offset into buf for parsing.
|
||||||
|
// args contains key-value store.
|
||||||
|
args []argsKV
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokint = uint16
|
||||||
|
|
||||||
|
type headerSlice struct {
|
||||||
|
start tokint
|
||||||
|
len tokint
|
||||||
|
}
|
||||||
|
|
||||||
|
type argSlice struct {
|
||||||
|
start tokint
|
||||||
|
len tokint
|
||||||
|
}
|
||||||
|
|
||||||
|
type argsKV struct {
|
||||||
|
key headerSlice
|
||||||
|
value headerSlice // value start >0 means value is present.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb headerBuf) musttoken(slice headerSlice) []byte {
|
||||||
|
return tb.buf[slice.start : slice.start+slice.len]
|
||||||
|
}
|
||||||
|
func (tb headerBuf) mustargs(slice argSlice) []argsKV {
|
||||||
|
return tb.args[slice.start : slice.start+slice.len]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb headerBuf) visitArgs(args argSlice, f func(k, v []byte)) {
|
||||||
|
a := tb.mustargs(args)
|
||||||
|
for _, arg := range a {
|
||||||
|
k := tb.musttoken(arg.key)
|
||||||
|
v := tb.musttoken(arg.value)
|
||||||
|
f(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb headerBuf) visitArgsKey(args argSlice, f func(k []byte)) {
|
||||||
|
a := tb.mustargs(args)
|
||||||
|
for _, arg := range a {
|
||||||
|
f(tb.musttoken(arg.key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb headerBuf) slice(b []byte) headerSlice {
|
||||||
|
base := uintptr(unsafe.Pointer(&tb.buf[0]))
|
||||||
|
off := uintptr(unsafe.Pointer(&b[0]))
|
||||||
|
if off < base || off > base+uintptr(len(tb.buf)) {
|
||||||
|
panic("httpx: argument buffer does not alias header buffer")
|
||||||
|
}
|
||||||
|
return headerSlice{
|
||||||
|
start: tokint(off - base),
|
||||||
|
len: tokint(len(b)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kv argsKV) HasValue() bool { return kv.value.start > 0 }
|
||||||
|
|
||||||
|
type flags uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
disableNormalizing flags = 1 << iota
|
||||||
|
disableSpecialHeader
|
||||||
|
noDefaultContentType
|
||||||
|
connectionClose
|
||||||
|
noHTTP11
|
||||||
|
cookiesCollected
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f flags) hasAll(checkThese flags) bool {
|
||||||
|
return f&checkThese == checkThese
|
||||||
|
}
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
buf headerBuf
|
||||||
|
contentLength int
|
||||||
|
h argSlice
|
||||||
|
cookies argSlice
|
||||||
|
trailer argSlice
|
||||||
|
|
||||||
|
host headerSlice
|
||||||
|
contentLengthBytes headerSlice
|
||||||
|
contentType headerSlice
|
||||||
|
userAgent headerSlice
|
||||||
|
method headerSlice
|
||||||
|
proto headerSlice
|
||||||
|
requestURI headerSlice
|
||||||
|
rawHeaders headerSlice
|
||||||
|
mulHeader headerSlice
|
||||||
|
|
||||||
|
flags flags
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) Set(key, value string) {
|
||||||
|
h.bufKV.key = append(h.bufKV.key[:0], key...)
|
||||||
|
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
|
||||||
|
h.SetCanonical(b2s(h.bufKV.key), value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) Add(key, value string) {
|
||||||
|
if h.setSpecialHeader(key, value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
|
||||||
|
h.h = appendArg(h.h, b2s(k), value, argsHasValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentEncoding returns Content-Encoding header value.
|
||||||
|
func (h *header) ContentEncoding() []byte {
|
||||||
|
return peekArg(h.h, strContentEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContentEncoding sets Content-Encoding header value.
|
||||||
|
func (h *header) SetContentEncoding(contentEncoding string) {
|
||||||
|
h.Set(strContentEncoding, contentEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContentType sets Content-Type header value.
|
||||||
|
func (h *header) SetContentType(contentType string) {
|
||||||
|
h.contentType = append(h.contentType[:0], contentType...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType returns Content-Type header value.
|
||||||
|
func (h *header) ContentType() []byte {
|
||||||
|
contentType := h.contentType
|
||||||
|
if !h.noDefaultContentType && len(h.contentType) == 0 {
|
||||||
|
contentType = append(contentType, defaultContentType...)
|
||||||
|
}
|
||||||
|
return contentType
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCanonical sets the given 'key: value' header assuming that
|
||||||
|
// key is in canonical form.
|
||||||
|
//
|
||||||
|
// If the header is set as a Trailer (forbidden trailers will not be set, see SetTrailer for more details),
|
||||||
|
// it will be sent after the chunked request body.
|
||||||
|
func (h *header) SetCanonical(key, value string) {
|
||||||
|
if h.setSpecialHeader(key, value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.setNonSpecial(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setSpecialHeader handles special headers and return true when a header is processed.
|
||||||
|
func (h *header) setSpecialHeader(key, value string) bool {
|
||||||
|
if len(key) == 0 || h.disableSpecialHeader {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h.trace("setSpecialHeader", slog.String("key", key), slog.String("value", value))
|
||||||
|
switch key[0] | 0x20 {
|
||||||
|
case 'c':
|
||||||
|
switch {
|
||||||
|
case caseInsensitiveCompare(strContentType, key):
|
||||||
|
h.SetContentType(value)
|
||||||
|
return true
|
||||||
|
case caseInsensitiveCompare(strContentLength, key):
|
||||||
|
if contentLength, err := parseContentLength(value); err == nil {
|
||||||
|
h.contentLength = contentLength
|
||||||
|
h.contentLengthBytes = append(h.contentLengthBytes[:0], value...)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case caseInsensitiveCompare(strConnection, key):
|
||||||
|
if strClose == value {
|
||||||
|
h.SetConnectionClose()
|
||||||
|
} else {
|
||||||
|
h.ResetConnectionClose()
|
||||||
|
h.setNonSpecial(key, value)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case caseInsensitiveCompare(strCookie, key):
|
||||||
|
h.collectCookies()
|
||||||
|
h.cookies = parseRequestCookies(h.cookies, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 't': // OK
|
||||||
|
if caseInsensitiveCompare(strTransferEncoding, key) {
|
||||||
|
// Transfer-Encoding is managed automatically.
|
||||||
|
return true
|
||||||
|
} else if caseInsensitiveCompare(strTrailer, key) {
|
||||||
|
_ = h.SetTrailer(value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 'h':
|
||||||
|
if caseInsensitiveCompare(strHost, key) {
|
||||||
|
h.SetHost(value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case 'u':
|
||||||
|
if caseInsensitiveCompare(strUserAgent, key) {
|
||||||
|
h.SetUserAgent(value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHost sets Host header value.
|
||||||
|
func (h *header) SetHost(host string) {
|
||||||
|
h.host = append(h.host[:0], host...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAgent sets User-Agent header value.
|
||||||
|
func (h *header) SetUserAgent(userAgent string) {
|
||||||
|
h.userAgent = append(h.userAgent[:0], userAgent...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConnectionClose sets 'Connection: close' header.
|
||||||
|
func (h *header) SetConnectionClose() {
|
||||||
|
h.connectionClose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetConnectionClose clears 'Connection: close' header if it exists.
|
||||||
|
func (h *header) ResetConnectionClose() {
|
||||||
|
if h.connectionClose {
|
||||||
|
h.connectionClose = false
|
||||||
|
h.h = delAllArgs(h.h, strConnection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) SetContentRange(startPos, endPos, contentLength int) {
|
||||||
|
b := h.bufKV.value[:0]
|
||||||
|
b = append(b, strBytes...)
|
||||||
|
b = append(b, ' ')
|
||||||
|
b = appendUint(b, startPos)
|
||||||
|
b = append(b, '-')
|
||||||
|
b = appendUint(b, endPos)
|
||||||
|
b = append(b, '/')
|
||||||
|
b = appendUint(b, contentLength)
|
||||||
|
h.bufKV.value = b
|
||||||
|
|
||||||
|
h.setNonSpecial(strContentRange, b2s(h.bufKV.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// setNonSpecial directly put into map i.e. not a basic header.
|
||||||
|
func (h *header) setNonSpecial(key string, value string) {
|
||||||
|
h.trace("httpx:setNonSpecial", slog.String("key", key), slog.String("value", value))
|
||||||
|
h.h = setArg(h.h, key, value, argsHasValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint(b []byte, v int) []byte {
|
||||||
|
if v < 0 {
|
||||||
|
panic("negative uint")
|
||||||
|
}
|
||||||
|
return strconv.AppendUint(b, uint64(v), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentLength returns Content-Length header value.
|
||||||
|
//
|
||||||
|
// It may be negative:
|
||||||
|
// -1 means Transfer-Encoding: chunked.
|
||||||
|
// -2 means Transfer-Encoding: identity.
|
||||||
|
func (h *header) ContentLength() int {
|
||||||
|
return h.contentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContentLength sets Content-Length header value.
|
||||||
|
//
|
||||||
|
// Content-Length may be negative:
|
||||||
|
// -1 means Transfer-Encoding: chunked.
|
||||||
|
// -2 means Transfer-Encoding: identity.
|
||||||
|
func (h *header) SetContentLength(contentLength int) {
|
||||||
|
h.contentLength = contentLength
|
||||||
|
if contentLength >= 0 {
|
||||||
|
h.contentLengthBytes = appendUint(h.contentLengthBytes[:0], contentLength)
|
||||||
|
h.h = delAllArgs(h.h, strTransferEncoding)
|
||||||
|
} else {
|
||||||
|
h.contentLengthBytes = h.contentLengthBytes[:0]
|
||||||
|
h.h = setArg(h.h, strTransferEncoding, strChunked, argsHasValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrBadTrailer = errors.New("contain forbidden trailer")
|
||||||
|
|
||||||
|
// SetTrailer sets Trailer header value for chunked request
|
||||||
|
// to indicate which headers will be sent after the body.
|
||||||
|
//
|
||||||
|
// Use Set to set the trailer header later.
|
||||||
|
//
|
||||||
|
// Trailers are only supported with chunked transfer.
|
||||||
|
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||||
|
//
|
||||||
|
// The following trailers are forbidden:
|
||||||
|
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||||
|
// 2. routing (e.g., Host),
|
||||||
|
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||||
|
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||||
|
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||||
|
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||||
|
//
|
||||||
|
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||||
|
func (h *header) SetTrailer(trailer string) error {
|
||||||
|
h.trailer = h.trailer[:0]
|
||||||
|
return h.AddTrailer(trailer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTrailerBytes add Trailer header value for chunked response
|
||||||
|
// to indicate which headers will be sent after the body.
|
||||||
|
//
|
||||||
|
// Use Set to set the trailer header later.
|
||||||
|
//
|
||||||
|
// Trailers are only supported with chunked transfer.
|
||||||
|
// Trailers allow the sender to include additional headers at the end of chunked messages.
|
||||||
|
//
|
||||||
|
// The following trailers are forbidden:
|
||||||
|
// 1. necessary for message framing (e.g., Transfer-Encoding and Content-Length),
|
||||||
|
// 2. routing (e.g., Host),
|
||||||
|
// 3. request modifiers (e.g., controls and conditionals in Section 5 of [RFC7231]),
|
||||||
|
// 4. authentication (e.g., see [RFC7235] and [RFC6265]),
|
||||||
|
// 5. response control data (e.g., see Section 7.1 of [RFC7231]),
|
||||||
|
// 6. determining how to process the payload (e.g., Content-Encoding, Content-Type, Content-Range, and Trailer)
|
||||||
|
//
|
||||||
|
// Return ErrBadTrailer if contain any forbidden trailers.
|
||||||
|
func (h *header) AddTrailer(trailer string) error {
|
||||||
|
h.trace("httpx:AddTrailer", slog.String("trailer", trailer))
|
||||||
|
var err error
|
||||||
|
for i := -1; i+1 < len(trailer); {
|
||||||
|
trailer = trailer[i+1:]
|
||||||
|
i = strings.IndexByte(trailer, ',')
|
||||||
|
if i < 0 {
|
||||||
|
i = len(trailer)
|
||||||
|
}
|
||||||
|
key := stripSpace(trailer[:i])
|
||||||
|
// Forbidden by RFC 7230, section 4.1.2
|
||||||
|
if isBadTrailer(key) {
|
||||||
|
err = ErrBadTrailer
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
h.bufKV.key = append(h.bufKV.key[:0], key...)
|
||||||
|
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
|
||||||
|
h.trailer = appendArg(h.trailer, b2s(h.bufKV.key), "", argsNoValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBadTrailer(key string) bool {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch key[0] | 0x20 {
|
||||||
|
case 'a':
|
||||||
|
return caseInsensitiveCompare(key, strAuthorization)
|
||||||
|
case 'c':
|
||||||
|
if len(key) > len(HeaderContentType) && caseInsensitiveCompare(key[:8], strContentType[:8]) {
|
||||||
|
// skip compare prefix 'Content-'
|
||||||
|
return caseInsensitiveCompare(key[8:], strContentEncoding[8:]) ||
|
||||||
|
caseInsensitiveCompare(key[8:], strContentLength[8:]) ||
|
||||||
|
caseInsensitiveCompare(key[8:], strContentType[8:]) ||
|
||||||
|
caseInsensitiveCompare(key[8:], strContentRange[8:])
|
||||||
|
}
|
||||||
|
return caseInsensitiveCompare(key, strConnection)
|
||||||
|
case 'e':
|
||||||
|
return caseInsensitiveCompare(key, strExpect)
|
||||||
|
case 'h':
|
||||||
|
return caseInsensitiveCompare(key, strHost)
|
||||||
|
case 'k':
|
||||||
|
return caseInsensitiveCompare(key, strKeepAlive)
|
||||||
|
case 'm':
|
||||||
|
return caseInsensitiveCompare(key, strMaxForwards)
|
||||||
|
case 'p':
|
||||||
|
if len(key) > len(HeaderProxyConnection) && caseInsensitiveCompare(key[:6], strProxyConnection[:6]) {
|
||||||
|
// skip compare prefix 'Proxy-'
|
||||||
|
return caseInsensitiveCompare(key[6:], strProxyConnection[6:]) ||
|
||||||
|
caseInsensitiveCompare(key[6:], strProxyAuthenticate[6:]) ||
|
||||||
|
caseInsensitiveCompare(key[6:], strProxyAuthorization[6:])
|
||||||
|
}
|
||||||
|
case 'r':
|
||||||
|
return caseInsensitiveCompare(key, strRange)
|
||||||
|
case 't':
|
||||||
|
return caseInsensitiveCompare(key, strTE) ||
|
||||||
|
caseInsensitiveCompare(key, strTrailer) ||
|
||||||
|
caseInsensitiveCompare(key, strTransferEncoding)
|
||||||
|
case 'w':
|
||||||
|
return caseInsensitiveCompare(key, strWWWAuthenticate)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawHeaders returns raw header key/value bytes.
|
||||||
|
//
|
||||||
|
// Depending on server configuration, header keys may be normalized to
|
||||||
|
// capital-case in place.
|
||||||
|
//
|
||||||
|
// This copy is set aside during parsing, so empty slice is returned for all
|
||||||
|
// cases where parsing did not happen. Similarly, request line is not stored
|
||||||
|
// during parsing and can not be returned.
|
||||||
|
//
|
||||||
|
// The slice is not safe to use after the handler returns.
|
||||||
|
func (h *header) RawHeaders() []byte {
|
||||||
|
return h.rawHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableNormalizing disables header names' normalization.
|
||||||
|
//
|
||||||
|
// By default all the header names are normalized by uppercasing
|
||||||
|
// the first letter and all the first letters following dashes,
|
||||||
|
// while lowercasing all the other letters.
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// - CONNECTION -> Connection
|
||||||
|
// - conteNT-tYPE -> Content-Type
|
||||||
|
// - foo-bar-baz -> Foo-Bar-Baz
|
||||||
|
//
|
||||||
|
// Disable header names' normalization only if know what are you doing.
|
||||||
|
func (h *header) DisableNormalizing() {
|
||||||
|
h.disableNormalizing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableSpecialHeader disables special header processing.
|
||||||
|
// fasthttp will not set any special headers for you, such as Host, Content-Type, User-Agent, etc.
|
||||||
|
// You must set everything yourself.
|
||||||
|
// If RequestHeader.Read() is called, special headers will be ignored.
|
||||||
|
// This can be used to control case and order of special headers.
|
||||||
|
// This is generally not recommended.
|
||||||
|
func (h *header) DisableSpecialHeader() {
|
||||||
|
h.disableSpecialHeader = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method returns HTTP request method.
|
||||||
|
func (h *header) Method() []byte {
|
||||||
|
if len(h.method) == 0 {
|
||||||
|
h.method = append(h.method, http.MethodGet...)
|
||||||
|
}
|
||||||
|
return h.method
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) SetMethod(method string) {
|
||||||
|
h.method = append(h.method[:0], method...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequestURI sets RequestURI for the first HTTP request line.
|
||||||
|
func (h *header) SetRequestURI(requestURI string) {
|
||||||
|
h.requestURI = append(h.requestURI[:0], requestURI...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestURI returns RequestURI from the first HTTP request line.
|
||||||
|
func (h *header) RequestURI() []byte {
|
||||||
|
requestURI := h.requestURI
|
||||||
|
if len(requestURI) == 0 {
|
||||||
|
requestURI = append(requestURI, '/')
|
||||||
|
}
|
||||||
|
return requestURI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol returns HTTP protocol.
|
||||||
|
func (h *header) Protocol() []byte {
|
||||||
|
if len(h.proto) == 0 {
|
||||||
|
h.proto = append(h.proto, strHTTP11...)
|
||||||
|
}
|
||||||
|
return h.proto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) SetProtocol(protocol string) {
|
||||||
|
h.proto = append(h.proto[:0], protocol...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendReqRespCommon appends request/response common header representation to dst and returns the extended buffer.
|
||||||
|
func (h *header) AppendReqRespCommon(dst []byte) []byte {
|
||||||
|
for i, n := 0, len(h.h); i < n; i++ {
|
||||||
|
kv := &h.h[i]
|
||||||
|
// Exclude trailer from header
|
||||||
|
exclude := false
|
||||||
|
for _, t := range h.trailer {
|
||||||
|
if b2s(kv.key) == b2s(t.key) {
|
||||||
|
exclude = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exclude {
|
||||||
|
dst = appendHeaderLine(dst, b2s(kv.key), b2s(kv.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(h.trailer) > 0 {
|
||||||
|
aux := appendArgsKey(nil, h.trailer, strCommaSpace)
|
||||||
|
dst = appendHeaderLine(dst, strTrailer, b2s(aux))
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is no need in h.collectCookies() here, since if cookies aren't collected yet,
|
||||||
|
// they all are located in h.h.
|
||||||
|
n := len(h.cookies)
|
||||||
|
if n > 0 && !h.disableSpecialHeader {
|
||||||
|
dst = append(dst, strCookie...)
|
||||||
|
dst = append(dst, strColonSpace...)
|
||||||
|
dst = appendRequestCookieBytes(dst, h.cookies)
|
||||||
|
dst = append(dst, strCRLF...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ConnectionClose() && !h.disableSpecialHeader {
|
||||||
|
dst = appendHeaderLine(dst, strConnection, strClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(dst, strCRLF...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendHeaderLine(dst []byte, key, value string) []byte {
|
||||||
|
dst = append(dst, key...)
|
||||||
|
dst = append(dst, strColonSpace...)
|
||||||
|
dst = append(dst, value...)
|
||||||
|
return append(dst, strCRLF...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) ignoreBody() bool {
|
||||||
|
return h.IsGet() || h.IsHead()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) collectCookies() {
|
||||||
|
if h.cookiesCollected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, n := 0, len(h.h); i < n; i++ {
|
||||||
|
kv := &h.h[i]
|
||||||
|
if caseInsensitiveCompare(b2s(kv.key), strCookie) {
|
||||||
|
h.cookies = parseRequestCookies(h.cookies, b2s(kv.value))
|
||||||
|
tmp := *kv
|
||||||
|
copy(h.h[i:], h.h[i+1:])
|
||||||
|
n--
|
||||||
|
i--
|
||||||
|
h.h[n] = tmp
|
||||||
|
h.h = h.h[:n]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.cookiesCollected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *header) MethodIs(method string) bool {
|
||||||
|
return b2s(h.method) == method
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGet returns true if request method is GET.
|
||||||
|
func (h *header) IsGet() bool { return len(h.method) == 0 || h.MethodIs(http.MethodGet) }
|
||||||
|
|
||||||
|
// IsHead returns true if request method is HEAD.
|
||||||
|
func (h *header) IsHead() bool { return h.MethodIs(http.MethodHead) }
|
||||||
|
|
||||||
|
// IsPost returns true if request method is POST.
|
||||||
|
func (h *header) IsPost() bool { return h.MethodIs(http.MethodPost) }
|
||||||
|
|
||||||
|
// IsPut returns true if request method is PUT.
|
||||||
|
func (h *header) IsPut() bool { return h.MethodIs(http.MethodPut) }
|
||||||
|
|
||||||
|
// IsDelete returns true if request method is DELETE.
|
||||||
|
func (h *header) IsDelete() bool { return h.MethodIs(http.MethodDelete) }
|
||||||
|
|
||||||
|
// IsConnect returns true if request method is CONNECT.
|
||||||
|
func (h *header) IsConnect() bool { return h.MethodIs(http.MethodConnect) }
|
||||||
|
|
||||||
|
// IsOptions returns true if request method is OPTIONS.
|
||||||
|
func (h *header) IsOptions() bool { return h.MethodIs(http.MethodOptions) }
|
||||||
|
|
||||||
|
// IsTrace returns true if request method is TRACE.
|
||||||
|
func (h *header) IsTrace() bool { return h.MethodIs(http.MethodTrace) }
|
||||||
|
|
||||||
|
// IsPatch returns true if request method is PATCH.
|
||||||
|
func (h *header) IsPatch() bool { return h.MethodIs(http.MethodPatch) }
|
||||||
|
|
||||||
|
// IsHTTP11 returns true if the request is HTTP/1.1.
|
||||||
|
func (h *header) IsHTTP11() bool { return !h.noHTTP11 }
|
||||||
|
|
||||||
|
// Embed this type into a struct, which mustn't be copied,
|
||||||
|
// so `go vet` gives a warning if this struct is copied.
|
||||||
|
//
|
||||||
|
// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.
|
||||||
|
// and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example
|
||||||
|
type noCopy struct{}
|
||||||
|
|
||||||
|
func (*noCopy) Lock() {}
|
||||||
|
func (*noCopy) Unlock() {}
|
||||||
|
|
||||||
|
func (h *header) trace(msg string, attrs ...slog.Attr) {
|
||||||
|
internal.LogAttrs(h.logger, internal.LevelTrace, msg, attrs...)
|
||||||
|
}
|
||||||
|
func (h *header) debug(msg string, attrs ...slog.Attr) {
|
||||||
|
internal.LogAttrs(h.logger, slog.LevelDebug, msg, attrs...)
|
||||||
|
}
|
||||||
|
func (h *header) info(msg string, attrs ...slog.Attr) {
|
||||||
|
internal.LogAttrs(h.logger, slog.LevelInfo, msg, attrs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeHeaderKey(b []byte, disableNormalizing bool) {
|
||||||
|
if disableNormalizing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(b)
|
||||||
|
if n == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b[0] = toUpperTable[b[0]]
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
p := &b[i]
|
||||||
|
if *p == '-' {
|
||||||
|
i++
|
||||||
|
if i < n {
|
||||||
|
b[i] = toUpperTable[b[i]]
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
*p = toLowerTable[*p]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user