12 private bool $varnishETagHack =
true;
16 private $lastModified;
18 private $hasRepresentation;
22 private ?array $eTagParts =
null;
47 $hasRepresentation =
null
50 $this->lastModified = $lastModified;
51 $this->hasRepresentation = $hasRepresentation;
60 $this->varnishETagHack = $hack;
63 private function getETag(): ?string {
64 if ( is_callable( $this->eTag ) ) {
66 $this->eTag = ( $this->eTag )();
72 private function getETagParts(): ?array {
73 if ( $this->eTagParts !== null ) {
74 return $this->eTagParts;
77 $eTag = $this->getETag();
79 if ( $eTag ===
null ) {
83 $this->eTagParts = $this->eTagParser->parseETag( $eTag );
84 if ( !$this->eTagParts ) {
85 throw new RuntimeException(
'Invalid ETag returned by handler: `' .
86 $this->eTagParser->getLastError() .
'`' );
89 return $this->eTagParts;
92 private function getLastModified(): ?int {
93 if ( is_callable( $this->lastModified ) ) {
95 $this->lastModified = ( $this->lastModified )();
98 if ( is_string( $this->lastModified ) ) {
100 $this->lastModified = (int)ConvertibleTimestamp::convert(
107 return $this->lastModified;
110 private function hasRepresentation(): bool {
111 if ( is_callable( $this->hasRepresentation ) ) {
113 $this->hasRepresentation = ( $this->hasRepresentation )();
116 if ( $this->hasRepresentation ===
null ) {
118 $this->hasRepresentation = $this->getETag() !==
null
119 || $this->getLastModified() !==
null;
122 return $this->hasRepresentation;
133 $getOrHead = in_array(
$request->getMethod(), [
'GET',
'HEAD' ] );
134 if (
$request->hasHeader(
'If-Match' ) ) {
135 $im =
$request->getHeader(
'If-Match' );
137 foreach ( $this->eTagParser->parseHeaderList( $im ) as $tag ) {
138 if ( ( $tag[
'whole'] ===
'*' && $this->hasRepresentation() ) ||
139 $this->strongCompare( $this->getETagParts(), $tag )
148 } elseif (
$request->hasHeader(
'If-Unmodified-Since' ) ) {
149 $requestDate = HttpDate::parse(
$request->getHeader(
'If-Unmodified-Since' )[0] );
150 $lastModified = $this->getLastModified();
151 if ( $requestDate !==
null
152 && ( $lastModified ===
null || $lastModified > $requestDate )
157 if (
$request->hasHeader(
'If-None-Match' ) ) {
158 $inm =
$request->getHeader(
'If-None-Match' );
159 foreach ( $this->eTagParser->parseHeaderList( $inm ) as $tag ) {
160 if ( ( $tag[
'whole'] ===
'*' && $this->hasRepresentation() ) ||
161 $this->weakCompare( $this->getETagParts(), $tag )
163 return $getOrHead ? 304 : 412;
166 } elseif ( $getOrHead &&
$request->hasHeader(
'If-Modified-Since' ) ) {
167 $requestDate = HttpDate::parse(
$request->getHeader(
'If-Modified-Since' )[0] );
168 $lastModified = $this->getLastModified();
169 if ( $requestDate !==
null && $lastModified !==
null
170 && $lastModified <= $requestDate
202 $lastModified = $this->getLastModified();
203 if ( $lastModified !==
null && !$response->
hasHeader(
'Last-Modified' ) ) {
204 $response->
setHeader(
'Last-Modified', HttpDate::format( $lastModified ) );
207 $eTag = $this->getETag();
208 if ( $eTag !==
null && !$response->
hasHeader(
'ETag' ) ) {
220 private function weakCompare( $resourceETag, $headerETag ) {
221 if ( $resourceETag ===
null || $headerETag ===
null ) {
224 return $resourceETag[
'contents'] === $headerETag[
'contents'];
240 private function strongCompare( $resourceETag, $headerETag ) {
241 if ( $resourceETag ===
null || $headerETag ===
null ) {
245 return !$resourceETag[
'weak']
246 && ( $this->varnishETagHack || !$headerETag[
'weak'] )
247 && $resourceETag[
'contents'] === $headerETag[
'contents'];