directive.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. /**
  2. * Copyright 2015 Paul Querna
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package cacheobject
  18. import (
  19. "errors"
  20. "math"
  21. "net/http"
  22. "net/textproto"
  23. "strconv"
  24. "strings"
  25. )
  26. // TODO(pquerna): add extensions from here: http://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml
  27. var (
  28. ErrQuoteMismatch = errors.New("Missing closing quote")
  29. ErrMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `max-age`")
  30. ErrSMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `s-maxage`")
  31. ErrMaxStaleDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
  32. ErrMinFreshDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
  33. ErrNoCacheNoArgs = errors.New("Unexpected argument to `no-cache`")
  34. ErrNoStoreNoArgs = errors.New("Unexpected argument to `no-store`")
  35. ErrNoTransformNoArgs = errors.New("Unexpected argument to `no-transform`")
  36. ErrOnlyIfCachedNoArgs = errors.New("Unexpected argument to `only-if-cached`")
  37. ErrMustRevalidateNoArgs = errors.New("Unexpected argument to `must-revalidate`")
  38. ErrPublicNoArgs = errors.New("Unexpected argument to `public`")
  39. ErrProxyRevalidateNoArgs = errors.New("Unexpected argument to `proxy-revalidate`")
  40. )
  41. func whitespace(b byte) bool {
  42. if b == '\t' || b == ' ' {
  43. return true
  44. }
  45. return false
  46. }
  47. func parse(value string, cd cacheDirective) error {
  48. var err error = nil
  49. i := 0
  50. for i < len(value) && err == nil {
  51. // eat leading whitespace or commas
  52. if whitespace(value[i]) || value[i] == ',' {
  53. i++
  54. continue
  55. }
  56. j := i + 1
  57. for j < len(value) {
  58. if !isToken(value[j]) {
  59. break
  60. }
  61. j++
  62. }
  63. token := strings.ToLower(value[i:j])
  64. tokenHasFields := hasFieldNames(token)
  65. /*
  66. println("GOT TOKEN:")
  67. println(" i -> ", i)
  68. println(" j -> ", j)
  69. println(" token -> ", token)
  70. */
  71. if j+1 < len(value) && value[j] == '=' {
  72. k := j + 1
  73. // minimum size two bytes of "", but we let httpUnquote handle it.
  74. if k < len(value) && value[k] == '"' {
  75. eaten, result := httpUnquote(value[k:])
  76. if eaten == -1 {
  77. return ErrQuoteMismatch
  78. }
  79. i = k + eaten
  80. err = cd.addPair(token, result)
  81. } else {
  82. z := k
  83. for z < len(value) {
  84. if tokenHasFields {
  85. if whitespace(value[z]) {
  86. break
  87. }
  88. } else {
  89. if whitespace(value[z]) || value[z] == ',' {
  90. break
  91. }
  92. }
  93. z++
  94. }
  95. i = z
  96. result := value[k:z]
  97. if result != "" && result[len(result)-1] == ',' {
  98. result = result[:len(result)-1]
  99. }
  100. err = cd.addPair(token, result)
  101. }
  102. } else {
  103. if token != "," {
  104. err = cd.addToken(token)
  105. }
  106. i = j
  107. }
  108. }
  109. return err
  110. }
  111. // DeltaSeconds specifies a non-negative integer, representing
  112. // time in seconds: http://tools.ietf.org/html/rfc7234#section-1.2.1
  113. //
  114. // When set to -1, this means unset.
  115. //
  116. type DeltaSeconds int32
  117. // Parser for delta-seconds, a uint31, more or less:
  118. // http://tools.ietf.org/html/rfc7234#section-1.2.1
  119. func parseDeltaSeconds(v string) (DeltaSeconds, error) {
  120. n, err := strconv.ParseUint(v, 10, 32)
  121. if err != nil {
  122. if numError, ok := err.(*strconv.NumError); ok {
  123. if numError.Err == strconv.ErrRange {
  124. return DeltaSeconds(math.MaxInt32), nil
  125. }
  126. }
  127. return DeltaSeconds(-1), err
  128. } else {
  129. if n > math.MaxInt32 {
  130. return DeltaSeconds(math.MaxInt32), nil
  131. } else {
  132. return DeltaSeconds(n), nil
  133. }
  134. }
  135. }
  136. // Fields present in a header.
  137. type FieldNames map[string]bool
  138. // internal interface for shared methods of RequestCacheDirectives and ResponseCacheDirectives
  139. type cacheDirective interface {
  140. addToken(s string) error
  141. addPair(s string, v string) error
  142. }
  143. // LOW LEVEL API: Repersentation of possible request directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.1
  144. //
  145. // Note: Many fields will be `nil` in practice.
  146. //
  147. type RequestCacheDirectives struct {
  148. // max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.1
  149. //
  150. // The "max-age" request directive indicates that the client is
  151. // unwilling to accept a response whose age is greater than the
  152. // specified number of seconds. Unless the max-stale request directive
  153. // is also present, the client is not willing to accept a stale
  154. // response.
  155. MaxAge DeltaSeconds
  156. // max-stale(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.2
  157. //
  158. // The "max-stale" request directive indicates that the client is
  159. // willing to accept a response that has exceeded its freshness
  160. // lifetime. If max-stale is assigned a value, then the client is
  161. // willing to accept a response that has exceeded its freshness lifetime
  162. // by no more than the specified number of seconds. If no value is
  163. // assigned to max-stale, then the client is willing to accept a stale
  164. // response of any age.
  165. MaxStale DeltaSeconds
  166. // min-fresh(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.3
  167. //
  168. // The "min-fresh" request directive indicates that the client is
  169. // willing to accept a response whose freshness lifetime is no less than
  170. // its current age plus the specified time in seconds. That is, the
  171. // client wants a response that will still be fresh for at least the
  172. // specified number of seconds.
  173. MinFresh DeltaSeconds
  174. // no-cache(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.4
  175. //
  176. // The "no-cache" request directive indicates that a cache MUST NOT use
  177. // a stored response to satisfy the request without successful
  178. // validation on the origin server.
  179. NoCache bool
  180. // no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.5
  181. //
  182. // The "no-store" request directive indicates that a cache MUST NOT
  183. // store any part of either this request or any response to it. This
  184. // directive applies to both private and shared caches.
  185. NoStore bool
  186. // no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.6
  187. //
  188. // The "no-transform" request directive indicates that an intermediary
  189. // (whether or not it implements a cache) MUST NOT transform the
  190. // payload, as defined in Section 5.7.2 of RFC7230.
  191. NoTransform bool
  192. // only-if-cached(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.7
  193. //
  194. // The "only-if-cached" request directive indicates that the client only
  195. // wishes to obtain a stored response.
  196. OnlyIfCached bool
  197. // Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
  198. //
  199. // The Cache-Control header field can be extended through the use of one
  200. // or more cache-extension tokens, each with an optional value. A cache
  201. // MUST ignore unrecognized cache directives.
  202. Extensions []string
  203. }
  204. func (cd *RequestCacheDirectives) addToken(token string) error {
  205. var err error = nil
  206. switch token {
  207. case "max-age":
  208. err = ErrMaxAgeDeltaSeconds
  209. case "max-stale":
  210. err = ErrMaxStaleDeltaSeconds
  211. case "min-fresh":
  212. err = ErrMinFreshDeltaSeconds
  213. case "no-cache":
  214. cd.NoCache = true
  215. case "no-store":
  216. cd.NoStore = true
  217. case "no-transform":
  218. cd.NoTransform = true
  219. case "only-if-cached":
  220. cd.OnlyIfCached = true
  221. default:
  222. cd.Extensions = append(cd.Extensions, token)
  223. }
  224. return err
  225. }
  226. func (cd *RequestCacheDirectives) addPair(token string, v string) error {
  227. var err error = nil
  228. switch token {
  229. case "max-age":
  230. cd.MaxAge, err = parseDeltaSeconds(v)
  231. if err != nil {
  232. err = ErrMaxAgeDeltaSeconds
  233. }
  234. case "max-stale":
  235. cd.MaxStale, err = parseDeltaSeconds(v)
  236. if err != nil {
  237. err = ErrMaxStaleDeltaSeconds
  238. }
  239. case "min-fresh":
  240. cd.MinFresh, err = parseDeltaSeconds(v)
  241. if err != nil {
  242. err = ErrMinFreshDeltaSeconds
  243. }
  244. case "no-cache":
  245. err = ErrNoCacheNoArgs
  246. case "no-store":
  247. err = ErrNoStoreNoArgs
  248. case "no-transform":
  249. err = ErrNoTransformNoArgs
  250. case "only-if-cached":
  251. err = ErrOnlyIfCachedNoArgs
  252. default:
  253. // TODO(pquerna): this sucks, making user re-parse
  254. cd.Extensions = append(cd.Extensions, token+"="+v)
  255. }
  256. return err
  257. }
  258. // LOW LEVEL API: Parses a Cache Control Header from a Request into a set of directives.
  259. func ParseRequestCacheControl(value string) (*RequestCacheDirectives, error) {
  260. cd := &RequestCacheDirectives{
  261. MaxAge: -1,
  262. MaxStale: -1,
  263. MinFresh: -1,
  264. }
  265. err := parse(value, cd)
  266. if err != nil {
  267. return nil, err
  268. }
  269. return cd, nil
  270. }
  271. // LOW LEVEL API: Repersentation of possible response directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.2
  272. //
  273. // Note: Many fields will be `nil` in practice.
  274. //
  275. type ResponseCacheDirectives struct {
  276. // must-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.1
  277. //
  278. // The "must-revalidate" response directive indicates that once it has
  279. // become stale, a cache MUST NOT use the response to satisfy subsequent
  280. // requests without successful validation on the origin server.
  281. MustRevalidate bool
  282. // no-cache(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
  283. //
  284. // The "no-cache" response directive indicates that the response MUST
  285. // NOT be used to satisfy a subsequent request without successful
  286. // validation on the origin server.
  287. //
  288. // If the no-cache response directive specifies one or more field-names,
  289. // then a cache MAY use the response to satisfy a subsequent request,
  290. // subject to any other restrictions on caching. However, any header
  291. // fields in the response that have the field-name(s) listed MUST NOT be
  292. // sent in the response to a subsequent request without successful
  293. // revalidation with the origin server.
  294. NoCache FieldNames
  295. // no-cache(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
  296. //
  297. // While the RFC defines optional field-names on a no-cache directive,
  298. // many applications only want to know if any no-cache directives were
  299. // present at all.
  300. NoCachePresent bool
  301. // no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.3
  302. //
  303. // The "no-store" request directive indicates that a cache MUST NOT
  304. // store any part of either this request or any response to it. This
  305. // directive applies to both private and shared caches.
  306. NoStore bool
  307. // no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.4
  308. //
  309. // The "no-transform" response directive indicates that an intermediary
  310. // (regardless of whether it implements a cache) MUST NOT transform the
  311. // payload, as defined in Section 5.7.2 of RFC7230.
  312. NoTransform bool
  313. // public(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.5
  314. //
  315. // The "public" response directive indicates that any cache MAY store
  316. // the response, even if the response would normally be non-cacheable or
  317. // cacheable only within a private cache.
  318. Public bool
  319. // private(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
  320. //
  321. // The "private" response directive indicates that the response message
  322. // is intended for a single user and MUST NOT be stored by a shared
  323. // cache. A private cache MAY store the response and reuse it for later
  324. // requests, even if the response would normally be non-cacheable.
  325. //
  326. // If the private response directive specifies one or more field-names,
  327. // this requirement is limited to the field-values associated with the
  328. // listed response header fields. That is, a shared cache MUST NOT
  329. // store the specified field-names(s), whereas it MAY store the
  330. // remainder of the response message.
  331. Private FieldNames
  332. // private(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
  333. //
  334. // While the RFC defines optional field-names on a private directive,
  335. // many applications only want to know if any private directives were
  336. // present at all.
  337. PrivatePresent bool
  338. // proxy-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.7
  339. //
  340. // The "proxy-revalidate" response directive has the same meaning as the
  341. // must-revalidate response directive, except that it does not apply to
  342. // private caches.
  343. ProxyRevalidate bool
  344. // max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.8
  345. //
  346. // The "max-age" response directive indicates that the response is to be
  347. // considered stale after its age is greater than the specified number
  348. // of seconds.
  349. MaxAge DeltaSeconds
  350. // s-maxage(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.9
  351. //
  352. // The "s-maxage" response directive indicates that, in shared caches,
  353. // the maximum age specified by this directive overrides the maximum age
  354. // specified by either the max-age directive or the Expires header
  355. // field. The s-maxage directive also implies the semantics of the
  356. // proxy-revalidate response directive.
  357. SMaxAge DeltaSeconds
  358. // Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
  359. //
  360. // The Cache-Control header field can be extended through the use of one
  361. // or more cache-extension tokens, each with an optional value. A cache
  362. // MUST ignore unrecognized cache directives.
  363. Extensions []string
  364. }
  365. // LOW LEVEL API: Parses a Cache Control Header from a Response into a set of directives.
  366. func ParseResponseCacheControl(value string) (*ResponseCacheDirectives, error) {
  367. cd := &ResponseCacheDirectives{
  368. MaxAge: -1,
  369. SMaxAge: -1,
  370. }
  371. err := parse(value, cd)
  372. if err != nil {
  373. return nil, err
  374. }
  375. return cd, nil
  376. }
  377. func (cd *ResponseCacheDirectives) addToken(token string) error {
  378. var err error = nil
  379. switch token {
  380. case "must-revalidate":
  381. cd.MustRevalidate = true
  382. case "no-cache":
  383. cd.NoCachePresent = true
  384. case "no-store":
  385. cd.NoStore = true
  386. case "no-transform":
  387. cd.NoTransform = true
  388. case "public":
  389. cd.Public = true
  390. case "private":
  391. cd.PrivatePresent = true
  392. case "proxy-revalidate":
  393. cd.ProxyRevalidate = true
  394. case "max-age":
  395. err = ErrMaxAgeDeltaSeconds
  396. case "s-maxage":
  397. err = ErrSMaxAgeDeltaSeconds
  398. default:
  399. cd.Extensions = append(cd.Extensions, token)
  400. }
  401. return err
  402. }
  403. func hasFieldNames(token string) bool {
  404. switch token {
  405. case "no-cache":
  406. return true
  407. case "private":
  408. return true
  409. }
  410. return false
  411. }
  412. func (cd *ResponseCacheDirectives) addPair(token string, v string) error {
  413. var err error = nil
  414. switch token {
  415. case "must-revalidate":
  416. err = ErrMustRevalidateNoArgs
  417. case "no-cache":
  418. cd.NoCachePresent = true
  419. tokens := strings.Split(v, ",")
  420. if cd.NoCache == nil {
  421. cd.NoCache = make(FieldNames)
  422. }
  423. for _, t := range tokens {
  424. k := http.CanonicalHeaderKey(textproto.TrimString(t))
  425. cd.NoCache[k] = true
  426. }
  427. case "no-store":
  428. err = ErrNoStoreNoArgs
  429. case "no-transform":
  430. err = ErrNoTransformNoArgs
  431. case "public":
  432. err = ErrPublicNoArgs
  433. case "private":
  434. cd.PrivatePresent = true
  435. tokens := strings.Split(v, ",")
  436. if cd.Private == nil {
  437. cd.Private = make(FieldNames)
  438. }
  439. for _, t := range tokens {
  440. k := http.CanonicalHeaderKey(textproto.TrimString(t))
  441. cd.Private[k] = true
  442. }
  443. case "proxy-revalidate":
  444. err = ErrProxyRevalidateNoArgs
  445. case "max-age":
  446. cd.MaxAge, err = parseDeltaSeconds(v)
  447. case "s-maxage":
  448. cd.SMaxAge, err = parseDeltaSeconds(v)
  449. default:
  450. // TODO(pquerna): this sucks, making user re-parse, and its technically not 'quoted' like the original,
  451. // but this is still easier, just a SplitN on "="
  452. cd.Extensions = append(cd.Extensions, token+"="+v)
  453. }
  454. return err
  455. }