12 private $varnishETagHack =
true;
16 private $lastModified;
18 private $hasRepresentation;
22 private ?array $eTagParts =
null;
47 $hasRepresentation =
null
50 $this->lastModified = $lastModified;
51 $this->hasRepresentation = $hasRepresentation;
62 $this->varnishETagHack = $hack;
65 private function getETag(): ?string {
66 if ( is_callable( $this->eTag ) ) {
68 $this->eTag = ( $this->eTag )();
74 private function getETagParts(): ?array {
75 if ( $this->eTagParts !== null ) {
76 return $this->eTagParts;
79 $eTag = $this->getETag();
81 if ( $eTag ===
null ) {
85 $this->eTagParts = $this->eTagParser->parseETag( $eTag );
86 if ( !$this->eTagParts ) {
87 throw new RuntimeException(
'Invalid ETag returned by handler: `' .
88 $this->eTagParser->getLastError() .
'`' );
91 return $this->eTagParts;
94 private function getLastModified(): ?int {
95 if ( is_callable( $this->lastModified ) ) {
97 $this->lastModified = ( $this->lastModified )();
100 if ( is_string( $this->lastModified ) ) {
102 $this->lastModified = (int)ConvertibleTimestamp::convert(
109 return $this->lastModified;
112 private function hasRepresentation(): bool {
113 if ( is_callable( $this->hasRepresentation ) ) {
115 $this->hasRepresentation = ( $this->hasRepresentation )();
118 if ( $this->hasRepresentation ===
null ) {
120 $this->hasRepresentation = $this->getETag() !==
null
121 || $this->getLastModified() !==
null;
124 return $this->hasRepresentation;
135 $getOrHead = in_array( $request->
getMethod(), [
'GET',
'HEAD' ] );
136 if ( $request->
hasHeader(
'If-Match' ) ) {
139 foreach ( $this->eTagParser->parseHeaderList( $im ) as $tag ) {
140 if ( ( $tag[
'whole'] ===
'*' && $this->hasRepresentation() ) ||
141 $this->strongCompare( $this->getETagParts(), $tag )
150 } elseif ( $request->
hasHeader(
'If-Unmodified-Since' ) ) {
151 $requestDate = HttpDate::parse( $request->
getHeader(
'If-Unmodified-Since' )[0] );
152 $lastModified = $this->getLastModified();
153 if ( $requestDate !==
null
154 && ( $lastModified ===
null || $lastModified > $requestDate )
159 if ( $request->
hasHeader(
'If-None-Match' ) ) {
160 $inm = $request->
getHeader(
'If-None-Match' );
161 foreach ( $this->eTagParser->parseHeaderList( $inm ) as $tag ) {
162 if ( ( $tag[
'whole'] ===
'*' && $this->hasRepresentation() ) ||
163 $this->weakCompare( $this->getETagParts(), $tag )
165 return $getOrHead ? 304 : 412;
168 } elseif ( $getOrHead && $request->
hasHeader(
'If-Modified-Since' ) ) {
169 $requestDate = HttpDate::parse( $request->
getHeader(
'If-Modified-Since' )[0] );
170 $lastModified = $this->getLastModified();
171 if ( $requestDate !==
null && $lastModified !==
null
172 && $lastModified <= $requestDate
204 $lastModified = $this->getLastModified();
205 if ( $lastModified !==
null && !$response->
hasHeader(
'Last-Modified' ) ) {
206 $response->
setHeader(
'Last-Modified', HttpDate::format( $lastModified ) );
209 $eTag = $this->getETag();
210 if ( $eTag !==
null && !$response->
hasHeader(
'ETag' ) ) {
222 private function weakCompare( $resourceETag, $headerETag ) {
223 if ( $resourceETag ===
null || $headerETag ===
null ) {
226 return $resourceETag[
'contents'] === $headerETag[
'contents'];
242 private function strongCompare( $resourceETag, $headerETag ) {
243 if ( $resourceETag ===
null || $headerETag ===
null ) {
247 return !$resourceETag[
'weak']
248 && ( $this->varnishETagHack || !$headerETag[
'weak'] )
249 && $resourceETag[
'contents'] === $headerETag[
'contents'];