Contains the Concourse pipeline definition for building a line-server container
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
5.2 KiB

  1. // Copyright 2009 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // HTTP file system request handler
  5. package httputil
  6. import (
  7. "net/http"
  8. "net/textproto"
  9. "strings"
  10. "time"
  11. )
  12. // scanETag determines if a syntactically valid ETag is present at s. If so,
  13. // the ETag and remaining text after consuming ETag is returned. Otherwise,
  14. // it returns "", "".
  15. func scanETag(s string) (etag string, remain string) {
  16. s = textproto.TrimString(s)
  17. start := 0
  18. if strings.HasPrefix(s, "W/") {
  19. start = 2
  20. }
  21. if len(s[start:]) < 2 || s[start] != '"' {
  22. return "", ""
  23. }
  24. // ETag is either W/"text" or "text".
  25. // See RFC 7232 2.3.
  26. for i := start + 1; i < len(s); i++ {
  27. c := s[i]
  28. switch {
  29. // Character values allowed in ETags.
  30. case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
  31. case c == '"':
  32. return s[:i+1], s[i+1:]
  33. default:
  34. return "", ""
  35. }
  36. }
  37. return "", ""
  38. }
  39. // etagStrongMatch reports whether a and b match using strong ETag comparison.
  40. // Assumes a and b are valid ETags.
  41. func etagStrongMatch(a, b string) bool {
  42. return a == b && a != "" && a[0] == '"'
  43. }
  44. // etagWeakMatch reports whether a and b match using weak ETag comparison.
  45. // Assumes a and b are valid ETags.
  46. func etagWeakMatch(a, b string) bool {
  47. return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
  48. }
  49. // condResult is the result of an HTTP request precondition check.
  50. // See https://tools.ietf.org/html/rfc7232 section 3.
  51. type condResult int
  52. const (
  53. condNone condResult = iota
  54. condTrue
  55. condFalse
  56. )
  57. func checkIfMatch(w http.ResponseWriter, r *http.Request) condResult {
  58. im := r.Header.Get("If-Match")
  59. if im == "" {
  60. return condNone
  61. }
  62. for {
  63. im = textproto.TrimString(im)
  64. if len(im) == 0 {
  65. break
  66. }
  67. if im[0] == ',' {
  68. im = im[1:]
  69. continue
  70. }
  71. if im[0] == '*' {
  72. return condTrue
  73. }
  74. etag, remain := scanETag(im)
  75. if etag == "" {
  76. break
  77. }
  78. if etagStrongMatch(etag, w.Header().Get("Etag")) {
  79. return condTrue
  80. }
  81. im = remain
  82. }
  83. return condFalse
  84. }
  85. func checkIfUnmodifiedSince(r *http.Request, modtime time.Time) condResult {
  86. ius := r.Header.Get("If-Unmodified-Since")
  87. if ius == "" || isZeroTime(modtime) {
  88. return condNone
  89. }
  90. if t, err := http.ParseTime(ius); err == nil {
  91. // The Date-Modified header truncates sub-second precision, so
  92. // use mtime < t+1s instead of mtime <= t to check for unmodified.
  93. if modtime.Before(t.Add(1 * time.Second)) {
  94. return condTrue
  95. }
  96. return condFalse
  97. }
  98. return condNone
  99. }
  100. func checkIfNoneMatch(w http.ResponseWriter, r *http.Request) condResult {
  101. inm := r.Header.Get("If-None-Match")
  102. if inm == "" {
  103. return condNone
  104. }
  105. buf := inm
  106. for {
  107. buf = textproto.TrimString(buf)
  108. if len(buf) == 0 {
  109. break
  110. }
  111. if buf[0] == ',' {
  112. buf = buf[1:]
  113. }
  114. if buf[0] == '*' {
  115. return condFalse
  116. }
  117. etag, remain := scanETag(buf)
  118. if etag == "" {
  119. break
  120. }
  121. if etagWeakMatch(etag, w.Header().Get("Etag")) {
  122. return condFalse
  123. }
  124. buf = remain
  125. }
  126. return condTrue
  127. }
  128. func checkIfModifiedSince(r *http.Request, modtime time.Time) condResult {
  129. if r.Method != "GET" && r.Method != "HEAD" {
  130. return condNone
  131. }
  132. ims := r.Header.Get("If-Modified-Since")
  133. if ims == "" || isZeroTime(modtime) {
  134. return condNone
  135. }
  136. t, err := http.ParseTime(ims)
  137. if err != nil {
  138. return condNone
  139. }
  140. // The Date-Modified header truncates sub-second precision, so
  141. // use mtime < t+1s instead of mtime <= t to check for unmodified.
  142. if modtime.Before(t.Add(1 * time.Second)) {
  143. return condFalse
  144. }
  145. return condTrue
  146. }
  147. var unixEpochTime = time.Unix(0, 0)
  148. // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0).
  149. func isZeroTime(t time.Time) bool {
  150. return t.IsZero() || t.Equal(unixEpochTime)
  151. }
  152. func setLastModified(w http.ResponseWriter, modtime time.Time) {
  153. if !isZeroTime(modtime) {
  154. w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
  155. }
  156. }
  157. func writeNotModified(w http.ResponseWriter) {
  158. // RFC 7232 section 4.1:
  159. // a sender SHOULD NOT generate representation metadata other than the
  160. // above listed fields unless said metadata exists for the purpose of
  161. // guiding cache updates (e.g., Last-Modified might be useful if the
  162. // response does not have an ETag field).
  163. h := w.Header()
  164. delete(h, "Content-Type")
  165. delete(h, "Content-Length")
  166. if h.Get("Etag") != "" {
  167. delete(h, "Last-Modified")
  168. }
  169. w.WriteHeader(http.StatusNotModified)
  170. }
  171. // CheckPreconditions evaluates request preconditions and reports whether a precondition
  172. // resulted in sending StatusNotModified or StatusPreconditionFailed.
  173. func CheckPreconditions(w http.ResponseWriter, r *http.Request, modtime time.Time) (done bool) {
  174. // This function carefully follows RFC 7232 section 6.
  175. ch := checkIfMatch(w, r)
  176. if ch == condNone {
  177. ch = checkIfUnmodifiedSince(r, modtime)
  178. }
  179. if ch == condFalse {
  180. w.WriteHeader(http.StatusPreconditionFailed)
  181. return true
  182. }
  183. switch checkIfNoneMatch(w, r) {
  184. case condFalse:
  185. if r.Method == "GET" || r.Method == "HEAD" {
  186. writeNotModified(w)
  187. return true
  188. } else {
  189. w.WriteHeader(http.StatusPreconditionFailed)
  190. return true
  191. }
  192. case condNone:
  193. if checkIfModifiedSince(r, modtime) == condFalse {
  194. writeNotModified(w)
  195. return true
  196. }
  197. }
  198. return false
  199. }