http.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package net
  14. import (
  15. "bufio"
  16. "bytes"
  17. "context"
  18. "crypto/tls"
  19. "errors"
  20. "fmt"
  21. "io"
  22. "mime"
  23. "net"
  24. "net/http"
  25. "net/url"
  26. "os"
  27. "path"
  28. "regexp"
  29. "strconv"
  30. "strings"
  31. "time"
  32. "unicode"
  33. "unicode/utf8"
  34. "golang.org/x/net/http2"
  35. "k8s.io/klog/v2"
  36. netutils "k8s.io/utils/net"
  37. )
  38. // JoinPreservingTrailingSlash does a path.Join of the specified elements,
  39. // preserving any trailing slash on the last non-empty segment
  40. func JoinPreservingTrailingSlash(elem ...string) string {
  41. // do the basic path join
  42. result := path.Join(elem...)
  43. // find the last non-empty segment
  44. for i := len(elem) - 1; i >= 0; i-- {
  45. if len(elem[i]) > 0 {
  46. // if the last segment ended in a slash, ensure our result does as well
  47. if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") {
  48. result += "/"
  49. }
  50. break
  51. }
  52. }
  53. return result
  54. }
  55. // IsTimeout returns true if the given error is a network timeout error
  56. func IsTimeout(err error) bool {
  57. var neterr net.Error
  58. if errors.As(err, &neterr) {
  59. return neterr != nil && neterr.Timeout()
  60. }
  61. return false
  62. }
  63. // IsProbableEOF returns true if the given error resembles a connection termination
  64. // scenario that would justify assuming that the watch is empty.
  65. // These errors are what the Go http stack returns back to us which are general
  66. // connection closure errors (strongly correlated) and callers that need to
  67. // differentiate probable errors in connection behavior between normal "this is
  68. // disconnected" should use the method.
  69. func IsProbableEOF(err error) bool {
  70. if err == nil {
  71. return false
  72. }
  73. var uerr *url.Error
  74. if errors.As(err, &uerr) {
  75. err = uerr.Err
  76. }
  77. msg := err.Error()
  78. switch {
  79. case err == io.EOF:
  80. return true
  81. case err == io.ErrUnexpectedEOF:
  82. return true
  83. case msg == "http: can't write HTTP request on broken connection":
  84. return true
  85. case strings.Contains(msg, "http2: server sent GOAWAY and closed the connection"):
  86. return true
  87. case strings.Contains(msg, "connection reset by peer"):
  88. return true
  89. case strings.Contains(strings.ToLower(msg), "use of closed network connection"):
  90. return true
  91. }
  92. return false
  93. }
  94. var defaultTransport = http.DefaultTransport.(*http.Transport)
  95. // SetOldTransportDefaults applies the defaults from http.DefaultTransport
  96. // for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
  97. func SetOldTransportDefaults(t *http.Transport) *http.Transport {
  98. if t.Proxy == nil || isDefault(t.Proxy) {
  99. // http.ProxyFromEnvironment doesn't respect CIDRs and that makes it impossible to exclude things like pod and service IPs from proxy settings
  100. // ProxierWithNoProxyCIDR allows CIDR rules in NO_PROXY
  101. t.Proxy = NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
  102. }
  103. // If no custom dialer is set, use the default context dialer
  104. //lint:file-ignore SA1019 Keep supporting deprecated Dial method of custom transports
  105. if t.DialContext == nil && t.Dial == nil {
  106. t.DialContext = defaultTransport.DialContext
  107. }
  108. if t.TLSHandshakeTimeout == 0 {
  109. t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout
  110. }
  111. if t.IdleConnTimeout == 0 {
  112. t.IdleConnTimeout = defaultTransport.IdleConnTimeout
  113. }
  114. return t
  115. }
  116. // SetTransportDefaults applies the defaults from http.DefaultTransport
  117. // for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
  118. func SetTransportDefaults(t *http.Transport) *http.Transport {
  119. t = SetOldTransportDefaults(t)
  120. // Allow clients to disable http2 if needed.
  121. if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {
  122. klog.Info("HTTP2 has been explicitly disabled")
  123. } else if allowsHTTP2(t) {
  124. if err := configureHTTP2Transport(t); err != nil {
  125. klog.Warningf("Transport failed http2 configuration: %v", err)
  126. }
  127. }
  128. return t
  129. }
  130. func readIdleTimeoutSeconds() int {
  131. ret := 30
  132. // User can set the readIdleTimeout to 0 to disable the HTTP/2
  133. // connection health check.
  134. if s := os.Getenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS"); len(s) > 0 {
  135. i, err := strconv.Atoi(s)
  136. if err != nil {
  137. klog.Warningf("Illegal HTTP2_READ_IDLE_TIMEOUT_SECONDS(%q): %v."+
  138. " Default value %d is used", s, err, ret)
  139. return ret
  140. }
  141. ret = i
  142. }
  143. return ret
  144. }
  145. func pingTimeoutSeconds() int {
  146. ret := 15
  147. if s := os.Getenv("HTTP2_PING_TIMEOUT_SECONDS"); len(s) > 0 {
  148. i, err := strconv.Atoi(s)
  149. if err != nil {
  150. klog.Warningf("Illegal HTTP2_PING_TIMEOUT_SECONDS(%q): %v."+
  151. " Default value %d is used", s, err, ret)
  152. return ret
  153. }
  154. ret = i
  155. }
  156. return ret
  157. }
  158. func configureHTTP2Transport(t *http.Transport) error {
  159. t2, err := http2.ConfigureTransports(t)
  160. if err != nil {
  161. return err
  162. }
  163. // The following enables the HTTP/2 connection health check added in
  164. // https://github.com/golang/net/pull/55. The health check detects and
  165. // closes broken transport layer connections. Without the health check,
  166. // a broken connection can linger too long, e.g., a broken TCP
  167. // connection will be closed by the Linux kernel after 13 to 30 minutes
  168. // by default, which caused
  169. // https://github.com/kubernetes/client-go/issues/374 and
  170. // https://github.com/kubernetes/kubernetes/issues/87615.
  171. t2.ReadIdleTimeout = time.Duration(readIdleTimeoutSeconds()) * time.Second
  172. t2.PingTimeout = time.Duration(pingTimeoutSeconds()) * time.Second
  173. return nil
  174. }
  175. func allowsHTTP2(t *http.Transport) bool {
  176. if t.TLSClientConfig == nil || len(t.TLSClientConfig.NextProtos) == 0 {
  177. // the transport expressed no NextProto preference, allow
  178. return true
  179. }
  180. for _, p := range t.TLSClientConfig.NextProtos {
  181. if p == http2.NextProtoTLS {
  182. // the transport explicitly allowed http/2
  183. return true
  184. }
  185. }
  186. // the transport explicitly set NextProtos and excluded http/2
  187. return false
  188. }
  189. type RoundTripperWrapper interface {
  190. http.RoundTripper
  191. WrappedRoundTripper() http.RoundTripper
  192. }
  193. type DialFunc func(ctx context.Context, net, addr string) (net.Conn, error)
  194. func DialerFor(transport http.RoundTripper) (DialFunc, error) {
  195. if transport == nil {
  196. return nil, nil
  197. }
  198. switch transport := transport.(type) {
  199. case *http.Transport:
  200. // transport.DialContext takes precedence over transport.Dial
  201. if transport.DialContext != nil {
  202. return transport.DialContext, nil
  203. }
  204. // adapt transport.Dial to the DialWithContext signature
  205. if transport.Dial != nil {
  206. return func(ctx context.Context, net, addr string) (net.Conn, error) {
  207. return transport.Dial(net, addr)
  208. }, nil
  209. }
  210. // otherwise return nil
  211. return nil, nil
  212. case RoundTripperWrapper:
  213. return DialerFor(transport.WrappedRoundTripper())
  214. default:
  215. return nil, fmt.Errorf("unknown transport type: %T", transport)
  216. }
  217. }
  218. // CloseIdleConnectionsFor close idles connections for the Transport.
  219. // If the Transport is wrapped it iterates over the wrapped round trippers
  220. // until it finds one that implements the CloseIdleConnections method.
  221. // If the Transport does not have a CloseIdleConnections method
  222. // then this function does nothing.
  223. func CloseIdleConnectionsFor(transport http.RoundTripper) {
  224. if transport == nil {
  225. return
  226. }
  227. type closeIdler interface {
  228. CloseIdleConnections()
  229. }
  230. switch transport := transport.(type) {
  231. case closeIdler:
  232. transport.CloseIdleConnections()
  233. case RoundTripperWrapper:
  234. CloseIdleConnectionsFor(transport.WrappedRoundTripper())
  235. default:
  236. klog.Warningf("unknown transport type: %T", transport)
  237. }
  238. }
  239. type TLSClientConfigHolder interface {
  240. TLSClientConfig() *tls.Config
  241. }
  242. func TLSClientConfig(transport http.RoundTripper) (*tls.Config, error) {
  243. if transport == nil {
  244. return nil, nil
  245. }
  246. switch transport := transport.(type) {
  247. case *http.Transport:
  248. return transport.TLSClientConfig, nil
  249. case TLSClientConfigHolder:
  250. return transport.TLSClientConfig(), nil
  251. case RoundTripperWrapper:
  252. return TLSClientConfig(transport.WrappedRoundTripper())
  253. default:
  254. return nil, fmt.Errorf("unknown transport type: %T", transport)
  255. }
  256. }
  257. func FormatURL(scheme string, host string, port int, path string) *url.URL {
  258. return &url.URL{
  259. Scheme: scheme,
  260. Host: net.JoinHostPort(host, strconv.Itoa(port)),
  261. Path: path,
  262. }
  263. }
  264. func GetHTTPClient(req *http.Request) string {
  265. if ua := req.UserAgent(); len(ua) != 0 {
  266. return ua
  267. }
  268. return "unknown"
  269. }
  270. // SourceIPs splits the comma separated X-Forwarded-For header and joins it with
  271. // the X-Real-Ip header and/or req.RemoteAddr, ignoring invalid IPs.
  272. // The X-Real-Ip is omitted if it's already present in the X-Forwarded-For chain.
  273. // The req.RemoteAddr is always the last IP in the returned list.
  274. // It returns nil if all of these are empty or invalid.
  275. func SourceIPs(req *http.Request) []net.IP {
  276. var srcIPs []net.IP
  277. hdr := req.Header
  278. // First check the X-Forwarded-For header for requests via proxy.
  279. hdrForwardedFor := hdr.Get("X-Forwarded-For")
  280. if hdrForwardedFor != "" {
  281. // X-Forwarded-For can be a csv of IPs in case of multiple proxies.
  282. // Use the first valid one.
  283. parts := strings.Split(hdrForwardedFor, ",")
  284. for _, part := range parts {
  285. ip := netutils.ParseIPSloppy(strings.TrimSpace(part))
  286. if ip != nil {
  287. srcIPs = append(srcIPs, ip)
  288. }
  289. }
  290. }
  291. // Try the X-Real-Ip header.
  292. hdrRealIp := hdr.Get("X-Real-Ip")
  293. if hdrRealIp != "" {
  294. ip := netutils.ParseIPSloppy(hdrRealIp)
  295. // Only append the X-Real-Ip if it's not already contained in the X-Forwarded-For chain.
  296. if ip != nil && !containsIP(srcIPs, ip) {
  297. srcIPs = append(srcIPs, ip)
  298. }
  299. }
  300. // Always include the request Remote Address as it cannot be easily spoofed.
  301. var remoteIP net.IP
  302. // Remote Address in Go's HTTP server is in the form host:port so we need to split that first.
  303. host, _, err := net.SplitHostPort(req.RemoteAddr)
  304. if err == nil {
  305. remoteIP = netutils.ParseIPSloppy(host)
  306. }
  307. // Fallback if Remote Address was just IP.
  308. if remoteIP == nil {
  309. remoteIP = netutils.ParseIPSloppy(req.RemoteAddr)
  310. }
  311. // Don't duplicate remote IP if it's already the last address in the chain.
  312. if remoteIP != nil && (len(srcIPs) == 0 || !remoteIP.Equal(srcIPs[len(srcIPs)-1])) {
  313. srcIPs = append(srcIPs, remoteIP)
  314. }
  315. return srcIPs
  316. }
  317. // Checks whether the given IP address is contained in the list of IPs.
  318. func containsIP(ips []net.IP, ip net.IP) bool {
  319. for _, v := range ips {
  320. if v.Equal(ip) {
  321. return true
  322. }
  323. }
  324. return false
  325. }
  326. // Extracts and returns the clients IP from the given request.
  327. // Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order.
  328. // Returns nil if none of them are set or is set to an invalid value.
  329. func GetClientIP(req *http.Request) net.IP {
  330. ips := SourceIPs(req)
  331. if len(ips) == 0 {
  332. return nil
  333. }
  334. return ips[0]
  335. }
  336. // Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's
  337. // IP address to the X-Forwarded-For chain.
  338. func AppendForwardedForHeader(req *http.Request) {
  339. // Copied from net/http/httputil/reverseproxy.go:
  340. if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
  341. // If we aren't the first proxy retain prior
  342. // X-Forwarded-For information as a comma+space
  343. // separated list and fold multiple headers into one.
  344. if prior, ok := req.Header["X-Forwarded-For"]; ok {
  345. clientIP = strings.Join(prior, ", ") + ", " + clientIP
  346. }
  347. req.Header.Set("X-Forwarded-For", clientIP)
  348. }
  349. }
  350. var defaultProxyFuncPointer = fmt.Sprintf("%p", http.ProxyFromEnvironment)
  351. // isDefault checks to see if the transportProxierFunc is pointing to the default one
  352. func isDefault(transportProxier func(*http.Request) (*url.URL, error)) bool {
  353. transportProxierPointer := fmt.Sprintf("%p", transportProxier)
  354. return transportProxierPointer == defaultProxyFuncPointer
  355. }
  356. // NewProxierWithNoProxyCIDR constructs a Proxier function that respects CIDRs in NO_PROXY and delegates if
  357. // no matching CIDRs are found
  358. func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) {
  359. // we wrap the default method, so we only need to perform our check if the NO_PROXY (or no_proxy) envvar has a CIDR in it
  360. noProxyEnv := os.Getenv("NO_PROXY")
  361. if noProxyEnv == "" {
  362. noProxyEnv = os.Getenv("no_proxy")
  363. }
  364. noProxyRules := strings.Split(noProxyEnv, ",")
  365. cidrs := []*net.IPNet{}
  366. for _, noProxyRule := range noProxyRules {
  367. _, cidr, _ := netutils.ParseCIDRSloppy(noProxyRule)
  368. if cidr != nil {
  369. cidrs = append(cidrs, cidr)
  370. }
  371. }
  372. if len(cidrs) == 0 {
  373. return delegate
  374. }
  375. return func(req *http.Request) (*url.URL, error) {
  376. ip := netutils.ParseIPSloppy(req.URL.Hostname())
  377. if ip == nil {
  378. return delegate(req)
  379. }
  380. for _, cidr := range cidrs {
  381. if cidr.Contains(ip) {
  382. return nil, nil
  383. }
  384. }
  385. return delegate(req)
  386. }
  387. }
  388. // DialerFunc implements Dialer for the provided function.
  389. type DialerFunc func(req *http.Request) (net.Conn, error)
  390. func (fn DialerFunc) Dial(req *http.Request) (net.Conn, error) {
  391. return fn(req)
  392. }
  393. // Dialer dials a host and writes a request to it.
  394. type Dialer interface {
  395. // Dial connects to the host specified by req's URL, writes the request to the connection, and
  396. // returns the opened net.Conn.
  397. Dial(req *http.Request) (net.Conn, error)
  398. }
  399. // ConnectWithRedirects uses dialer to send req, following up to 10 redirects (relative to
  400. // originalLocation). It returns the opened net.Conn and the raw response bytes.
  401. // If requireSameHostRedirects is true, only redirects to the same host are permitted.
  402. func ConnectWithRedirects(originalMethod string, originalLocation *url.URL, header http.Header, originalBody io.Reader, dialer Dialer, requireSameHostRedirects bool) (net.Conn, []byte, error) {
  403. const (
  404. maxRedirects = 9 // Fail on the 10th redirect
  405. maxResponseSize = 16384 // play it safe to allow the potential for lots of / large headers
  406. )
  407. var (
  408. location = originalLocation
  409. method = originalMethod
  410. intermediateConn net.Conn
  411. rawResponse = bytes.NewBuffer(make([]byte, 0, 256))
  412. body = originalBody
  413. )
  414. defer func() {
  415. if intermediateConn != nil {
  416. intermediateConn.Close()
  417. }
  418. }()
  419. redirectLoop:
  420. for redirects := 0; ; redirects++ {
  421. if redirects > maxRedirects {
  422. return nil, nil, fmt.Errorf("too many redirects (%d)", redirects)
  423. }
  424. req, err := http.NewRequest(method, location.String(), body)
  425. if err != nil {
  426. return nil, nil, err
  427. }
  428. req.Header = header
  429. intermediateConn, err = dialer.Dial(req)
  430. if err != nil {
  431. return nil, nil, err
  432. }
  433. // Peek at the backend response.
  434. rawResponse.Reset()
  435. respReader := bufio.NewReader(io.TeeReader(
  436. io.LimitReader(intermediateConn, maxResponseSize), // Don't read more than maxResponseSize bytes.
  437. rawResponse)) // Save the raw response.
  438. resp, err := http.ReadResponse(respReader, nil)
  439. if err != nil {
  440. // Unable to read the backend response; let the client handle it.
  441. klog.Warningf("Error reading backend response: %v", err)
  442. break redirectLoop
  443. }
  444. switch resp.StatusCode {
  445. case http.StatusFound:
  446. // Redirect, continue.
  447. default:
  448. // Don't redirect.
  449. break redirectLoop
  450. }
  451. // Redirected requests switch to "GET" according to the HTTP spec:
  452. // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3
  453. method = "GET"
  454. // don't send a body when following redirects
  455. body = nil
  456. resp.Body.Close() // not used
  457. // Prepare to follow the redirect.
  458. redirectStr := resp.Header.Get("Location")
  459. if redirectStr == "" {
  460. return nil, nil, fmt.Errorf("%d response missing Location header", resp.StatusCode)
  461. }
  462. // We have to parse relative to the current location, NOT originalLocation. For example,
  463. // if we request http://foo.com/a and get back "http://bar.com/b", the result should be
  464. // http://bar.com/b. If we then make that request and get back a redirect to "/c", the result
  465. // should be http://bar.com/c, not http://foo.com/c.
  466. location, err = location.Parse(redirectStr)
  467. if err != nil {
  468. return nil, nil, fmt.Errorf("malformed Location header: %v", err)
  469. }
  470. // Only follow redirects to the same host. Otherwise, propagate the redirect response back.
  471. if requireSameHostRedirects && location.Hostname() != originalLocation.Hostname() {
  472. return nil, nil, fmt.Errorf("hostname mismatch: expected %s, found %s", originalLocation.Hostname(), location.Hostname())
  473. }
  474. // Reset the connection.
  475. intermediateConn.Close()
  476. intermediateConn = nil
  477. }
  478. connToReturn := intermediateConn
  479. intermediateConn = nil // Don't close the connection when we return it.
  480. return connToReturn, rawResponse.Bytes(), nil
  481. }
  482. // CloneRequest creates a shallow copy of the request along with a deep copy of the Headers.
  483. func CloneRequest(req *http.Request) *http.Request {
  484. r := new(http.Request)
  485. // shallow clone
  486. *r = *req
  487. // deep copy headers
  488. r.Header = CloneHeader(req.Header)
  489. return r
  490. }
  491. // CloneHeader creates a deep copy of an http.Header.
  492. func CloneHeader(in http.Header) http.Header {
  493. out := make(http.Header, len(in))
  494. for key, values := range in {
  495. newValues := make([]string, len(values))
  496. copy(newValues, values)
  497. out[key] = newValues
  498. }
  499. return out
  500. }
  501. // WarningHeader contains a single RFC2616 14.46 warnings header
  502. type WarningHeader struct {
  503. // Codeindicates the type of warning. 299 is a miscellaneous persistent warning
  504. Code int
  505. // Agent contains the name or pseudonym of the server adding the Warning header.
  506. // A single "-" is recommended when agent is unknown.
  507. Agent string
  508. // Warning text
  509. Text string
  510. }
  511. // ParseWarningHeaders extract RFC2616 14.46 warnings headers from the specified set of header values.
  512. // Multiple comma-separated warnings per header are supported.
  513. // If errors are encountered on a header, the remainder of that header are skipped and subsequent headers are parsed.
  514. // Returns successfully parsed warnings and any errors encountered.
  515. func ParseWarningHeaders(headers []string) ([]WarningHeader, []error) {
  516. var (
  517. results []WarningHeader
  518. errs []error
  519. )
  520. for _, header := range headers {
  521. for len(header) > 0 {
  522. result, remainder, err := ParseWarningHeader(header)
  523. if err != nil {
  524. errs = append(errs, err)
  525. break
  526. }
  527. results = append(results, result)
  528. header = remainder
  529. }
  530. }
  531. return results, errs
  532. }
  533. var (
  534. codeMatcher = regexp.MustCompile(`^[0-9]{3}$`)
  535. wordDecoder = &mime.WordDecoder{}
  536. )
  537. // ParseWarningHeader extracts one RFC2616 14.46 warning from the specified header,
  538. // returning an error if the header does not contain a correctly formatted warning.
  539. // Any remaining content in the header is returned.
  540. func ParseWarningHeader(header string) (result WarningHeader, remainder string, err error) {
  541. // https://tools.ietf.org/html/rfc2616#section-14.46
  542. // updated by
  543. // https://tools.ietf.org/html/rfc7234#section-5.5
  544. // https://tools.ietf.org/html/rfc7234#appendix-A
  545. // Some requirements regarding production and processing of the Warning
  546. // header fields have been relaxed, as it is not widely implemented.
  547. // Furthermore, the Warning header field no longer uses RFC 2047
  548. // encoding, nor does it allow multiple languages, as these aspects were
  549. // not implemented.
  550. //
  551. // Format is one of:
  552. // warn-code warn-agent "warn-text"
  553. // warn-code warn-agent "warn-text" "warn-date"
  554. //
  555. // warn-code is a three digit number
  556. // warn-agent is unquoted and contains no spaces
  557. // warn-text is quoted with backslash escaping (RFC2047-encoded according to RFC2616, not encoded according to RFC7234)
  558. // warn-date is optional, quoted, and in HTTP-date format (no embedded or escaped quotes)
  559. //
  560. // additional warnings can optionally be included in the same header by comma-separating them:
  561. // warn-code warn-agent "warn-text" "warn-date"[, warn-code warn-agent "warn-text" "warn-date", ...]
  562. // tolerate leading whitespace
  563. header = strings.TrimSpace(header)
  564. parts := strings.SplitN(header, " ", 3)
  565. if len(parts) != 3 {
  566. return WarningHeader{}, "", errors.New("invalid warning header: fewer than 3 segments")
  567. }
  568. code, agent, textDateRemainder := parts[0], parts[1], parts[2]
  569. // verify code format
  570. if !codeMatcher.Match([]byte(code)) {
  571. return WarningHeader{}, "", errors.New("invalid warning header: code segment is not 3 digits between 100-299")
  572. }
  573. codeInt, _ := strconv.ParseInt(code, 10, 64)
  574. // verify agent presence
  575. if len(agent) == 0 {
  576. return WarningHeader{}, "", errors.New("invalid warning header: empty agent segment")
  577. }
  578. if !utf8.ValidString(agent) || hasAnyRunes(agent, unicode.IsControl) {
  579. return WarningHeader{}, "", errors.New("invalid warning header: invalid agent")
  580. }
  581. // verify textDateRemainder presence
  582. if len(textDateRemainder) == 0 {
  583. return WarningHeader{}, "", errors.New("invalid warning header: empty text segment")
  584. }
  585. // extract text
  586. text, dateAndRemainder, err := parseQuotedString(textDateRemainder)
  587. if err != nil {
  588. return WarningHeader{}, "", fmt.Errorf("invalid warning header: %v", err)
  589. }
  590. // tolerate RFC2047-encoded text from warnings produced according to RFC2616
  591. if decodedText, err := wordDecoder.DecodeHeader(text); err == nil {
  592. text = decodedText
  593. }
  594. if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) {
  595. return WarningHeader{}, "", errors.New("invalid warning header: invalid text")
  596. }
  597. result = WarningHeader{Code: int(codeInt), Agent: agent, Text: text}
  598. if len(dateAndRemainder) > 0 {
  599. if dateAndRemainder[0] == '"' {
  600. // consume date
  601. foundEndQuote := false
  602. for i := 1; i < len(dateAndRemainder); i++ {
  603. if dateAndRemainder[i] == '"' {
  604. foundEndQuote = true
  605. remainder = strings.TrimSpace(dateAndRemainder[i+1:])
  606. break
  607. }
  608. }
  609. if !foundEndQuote {
  610. return WarningHeader{}, "", errors.New("invalid warning header: unterminated date segment")
  611. }
  612. } else {
  613. remainder = dateAndRemainder
  614. }
  615. }
  616. if len(remainder) > 0 {
  617. if remainder[0] == ',' {
  618. // consume comma if present
  619. remainder = strings.TrimSpace(remainder[1:])
  620. } else {
  621. return WarningHeader{}, "", errors.New("invalid warning header: unexpected token after warn-date")
  622. }
  623. }
  624. return result, remainder, nil
  625. }
  626. func parseQuotedString(quotedString string) (string, string, error) {
  627. if len(quotedString) == 0 {
  628. return "", "", errors.New("invalid quoted string: 0-length")
  629. }
  630. if quotedString[0] != '"' {
  631. return "", "", errors.New("invalid quoted string: missing initial quote")
  632. }
  633. quotedString = quotedString[1:]
  634. var remainder string
  635. escaping := false
  636. closedQuote := false
  637. result := &strings.Builder{}
  638. loop:
  639. for i := 0; i < len(quotedString); i++ {
  640. b := quotedString[i]
  641. switch b {
  642. case '"':
  643. if escaping {
  644. result.WriteByte(b)
  645. escaping = false
  646. } else {
  647. closedQuote = true
  648. remainder = strings.TrimSpace(quotedString[i+1:])
  649. break loop
  650. }
  651. case '\\':
  652. if escaping {
  653. result.WriteByte(b)
  654. escaping = false
  655. } else {
  656. escaping = true
  657. }
  658. default:
  659. result.WriteByte(b)
  660. escaping = false
  661. }
  662. }
  663. if !closedQuote {
  664. return "", "", errors.New("invalid quoted string: missing closing quote")
  665. }
  666. return result.String(), remainder, nil
  667. }
  668. func NewWarningHeader(code int, agent, text string) (string, error) {
  669. if code < 0 || code > 999 {
  670. return "", errors.New("code must be between 0 and 999")
  671. }
  672. if len(agent) == 0 {
  673. agent = "-"
  674. } else if !utf8.ValidString(agent) || strings.ContainsAny(agent, `\"`) || hasAnyRunes(agent, unicode.IsSpace, unicode.IsControl) {
  675. return "", errors.New("agent must be valid UTF-8 and must not contain spaces, quotes, backslashes, or control characters")
  676. }
  677. if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) {
  678. return "", errors.New("text must be valid UTF-8 and must not contain control characters")
  679. }
  680. return fmt.Sprintf("%03d %s %s", code, agent, makeQuotedString(text)), nil
  681. }
  682. func hasAnyRunes(s string, runeCheckers ...func(rune) bool) bool {
  683. for _, r := range s {
  684. for _, checker := range runeCheckers {
  685. if checker(r) {
  686. return true
  687. }
  688. }
  689. }
  690. return false
  691. }
  692. func makeQuotedString(s string) string {
  693. result := &bytes.Buffer{}
  694. // opening quote
  695. result.WriteRune('"')
  696. for _, c := range s {
  697. switch c {
  698. case '"', '\\':
  699. // escape " and \
  700. result.WriteRune('\\')
  701. result.WriteRune(c)
  702. default:
  703. // write everything else as-is
  704. result.WriteRune(c)
  705. }
  706. }
  707. // closing quote
  708. result.WriteRune('"')
  709. return result.String()
  710. }