http.go 23 KB

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