13 private $varnishETagHack =
true;
17 private $lastModified;
19 private $hasRepresentation;
23 private ?array $eTagParts =
null;
48 $hasRepresentation =
null
51 $this->lastModified = $lastModified;
52 $this->hasRepresentation = $hasRepresentation;
63 $this->varnishETagHack = $hack;
66 private function getETag(): ?string {
67 if ( is_callable( $this->eTag ) ) {
69 $this->eTag = ( $this->eTag )();
75 private function getETagParts(): ?array {
76 if ( $this->eTagParts !== null ) {
77 return $this->eTagParts;
80 $eTag = $this->getETag();
82 if ( $eTag ===
null ) {
86 $this->eTagParts = $this->eTagParser->parseETag( $eTag );
87 if ( !$this->eTagParts ) {
88 throw new RuntimeException(
'Invalid ETag returned by handler: `' .
89 $this->eTagParser->getLastError() .
'`' );
92 return $this->eTagParts;
95 private function getLastModified(): ?int {
96 if ( is_callable( $this->lastModified ) ) {
98 $this->lastModified = ( $this->lastModified )();
101 if ( is_string( $this->lastModified ) ) {
103 $this->lastModified = (int)ConvertibleTimestamp::convert(
110 return $this->lastModified;
113 private function hasRepresentation(): bool {
114 if ( is_callable( $this->hasRepresentation ) ) {
116 $this->hasRepresentation = ( $this->hasRepresentation )();
119 if ( $this->hasRepresentation ===
null ) {
121 $this->hasRepresentation = $this->getETag() !==
null
122 || $this->getLastModified() !==
null;
125 return $this->hasRepresentation;
136 $getOrHead = in_array( $request->
getMethod(), [
'GET',
'HEAD' ] );
137 if ( $request->
hasHeader(
'If-Match' ) ) {
140 foreach ( $this->eTagParser->parseHeaderList( $im ) as $tag ) {
141 if ( ( $tag[
'whole'] ===
'*' && $this->hasRepresentation() ) ||
142 $this->strongCompare( $this->getETagParts(), $tag )
151 } elseif ( $request->
hasHeader(
'If-Unmodified-Since' ) ) {
152 $requestDate = HttpDate::parse( $request->
getHeader(
'If-Unmodified-Since' )[0] );
153 $lastModified = $this->getLastModified();
154 if ( $requestDate !==
null
155 && ( $lastModified ===
null || $lastModified > $requestDate )
160 if ( $request->
hasHeader(
'If-None-Match' ) ) {
161 $inm = $request->
getHeader(
'If-None-Match' );
162 foreach ( $this->eTagParser->parseHeaderList( $inm ) as $tag ) {
163 if ( ( $tag[
'whole'] ===
'*' && $this->hasRepresentation() ) ||
164 $this->weakCompare( $this->getETagParts(), $tag )
166 return $getOrHead ? 304 : 412;
169 } elseif ( $getOrHead && $request->
hasHeader(
'If-Modified-Since' ) ) {
170 $requestDate = HttpDate::parse( $request->
getHeader(
'If-Modified-Since' )[0] );
171 $lastModified = $this->getLastModified();
172 if ( $requestDate !==
null && $lastModified !==
null
173 && $lastModified <= $requestDate
205 $lastModified = $this->getLastModified();
206 if ( $lastModified !==
null && !$response->
hasHeader(
'Last-Modified' ) ) {
207 $response->
setHeader(
'Last-Modified', HttpDate::format( $lastModified ) );
210 $eTag = $this->getETag();
211 if ( $eTag !==
null && !$response->
hasHeader(
'ETag' ) ) {
223 private function weakCompare( $resourceETag, $headerETag ) {
224 if ( $resourceETag ===
null || $headerETag ===
null ) {
227 return $resourceETag[
'contents'] === $headerETag[
'contents'];
243 private function strongCompare( $resourceETag, $headerETag ) {
244 if ( $resourceETag ===
null || $headerETag ===
null ) {
248 return !$resourceETag[
'weak']
249 && ( $this->varnishETagHack || !$headerETag[
'weak'] )
250 && $resourceETag[
'contents'] === $headerETag[
'contents'];