GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( fa660a...0fc3b2 )
by François
12:36 queued 08:27
created

RemoteStorageService::addNoCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 5
rs 9.4286
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
/**
4
 *  This program is free software: you can redistribute it and/or modify
5
 *  it under the terms of the GNU Lesser General Public License as published by
6
 *  the Free Software Foundation, either version 3 of the License, or
7
 *  (at your option) any later version.
8
 *
9
 *  This program is distributed in the hope that it will be useful,
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 *  GNU Lesser General Public License for more details.
13
 *
14
 *  You should have received a copy of the GNU Lesser General Public License
15
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
namespace fkooman\RemoteStorage;
18
19
use fkooman\Http\Exception\BadRequestException;
20
use fkooman\Http\Exception\ForbiddenException;
21
use fkooman\Http\Exception\NotFoundException;
22
use fkooman\Http\Exception\PreconditionFailedException;
23
use fkooman\Http\Exception\UnauthorizedException;
24
use fkooman\Http\Request;
25
use fkooman\Http\Response;
26
use fkooman\IO\IO;
27
use fkooman\OAuth\AccessTokenStorageInterface;
28
use fkooman\OAuth\ApprovalStorageInterface;
29
use fkooman\OAuth\AuthorizationCodeStorageInterface;
30
use fkooman\OAuth\ClientStorageInterface;
31
use fkooman\OAuth\OAuthService;
32
use fkooman\OAuth\ResourceServerStorageInterface;
33
use fkooman\RemoteStorage\Exception\PathException;
34
use fkooman\Rest\Plugin\Authentication\Bearer\Scope;
35
use fkooman\Rest\Plugin\Authentication\Bearer\TokenInfo;
36
use fkooman\Tpl\TemplateManagerInterface;
37
use InvalidArgumentException;
38
use fkooman\Rest\Plugin\Authentication\UserInfoInterface;
39
use fkooman\OAuth\Approval;
40
use fkooman\Http\RedirectResponse;
41
use fkooman\Json\Json;
42
43
class RemoteStorageService extends OAuthService
44
{
45
    /** @var RemoteStorage */
46
    private $remoteStorage;
47
48
    /** @var ApprovalManagementStorage */
49
    private $approvalManagementStorage;
50
51
    public function __construct(RemoteStorage $remoteStorage, ApprovalManagementStorage $approvalManagementStorage, TemplateManagerInterface $templateManager, ClientStorageInterface $clientStorage, ResourceServerStorageInterface $resourceServerStorage, ApprovalStorageInterface $approvalStorage, AuthorizationCodeStorageInterface $authorizationCodeStorage, AccessTokenStorageInterface $accessTokenStorage, array $options = array(), IO $io = null)
52
    {
53
        $this->remoteStorage = $remoteStorage;
54
        $this->approvalManagementStorage = $approvalManagementStorage;
55
56
        parent::__construct(
57
            $templateManager,
58
            $clientStorage,
59
            $resourceServerStorage,
60
            $approvalStorage,
61
            $authorizationCodeStorage,
62
            $accessTokenStorage,
63
            $options,
64
            $io
65
        );
66
67
        $this->get(
68
            '/_account',
69
            function (Request $request, UserInfoInterface $userInfo) {
70
                $approvalList = $this->approvalManagementStorage->getApprovalList($userInfo->getUserId());
71
72
                return $this->templateManager->render(
73
                    'getAccountPage',
74
                    array(
75
                        'approval_list' => $approvalList,
76
                        'host' => $request->getHeader('Host'),
77
                        'user_id' => $userInfo->getUserId(),
78
                        'disk_usage' => $this->remoteStorage->getFolderSize(new Path('/'.$userInfo->getUserId().'/')),
79
                        'request_url' => $request->getUrl()->toString(),
80
                        'show_account_icon' => true,
81
                    )
82
                );
83
            },
84
            array(
85
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array(
86
                    'activate' => array('user'),
87
                ),
88
            )
89
        );
90
91
        $this->delete(
92
            '/_approvals',
93
            function (Request $request, UserInfoInterface $userInfo) {
94
                $deleteApprovalRequest = RequestValidation::validateDeleteApprovalRequest($request);
95
96
                $approval = new Approval(
97
                    $userInfo->getUserId(),
98
                    $deleteApprovalRequest['client_id'],
99
                    $deleteApprovalRequest['response_type'],
100
                    $deleteApprovalRequest['scope']
101
                );
102
                $this->approvalManagementStorage->deleteApproval($approval);
103
104
                return new RedirectResponse($request->getUrl()->getRootUrl().'_account', 302);
105
            },
106
            array(
107
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array(
108
                    'activate' => array('user'),
109
                ),
110
            )
111
        );
112
113
        $this->get(
114
            '/.well-known/webfinger',
115
            function (Request $request) {
116
                $resource = $request->getUrl()->getQueryParameter('resource');
117
                if (null === $resource) {
118
                    throw new BadRequestException('resource parameter missing');
119
                }
120
                if (0 !== strpos($resource, 'acct:')) {
121
                    throw new BadRequestException('unsupported resource type');
122
                }
123
                $userAddress = substr($resource, 5);
124
                $atPos = strpos($userAddress, '@');
125
                if (false === $atPos) {
126
                    throw new BadRequestException('invalid user address');
127
                }
128
                $user = substr($userAddress, 0, $atPos);
129
130
                $webFingerData = array(
131
                    'links' => array(
132
                        array(
133
                            'href' => sprintf('%s%s', $request->getUrl()->getRootUrl(), $user),
134
                            'properties' => array(
135
                                'http://remotestorage.io/spec/version' => 'draft-dejong-remotestorage-05',
136
                                'http://remotestorage.io/spec/web-authoring' => null,
137
                                'http://tools.ietf.org/html/rfc6749#section-4.2' => sprintf('%s_oauth/authorize?login_hint=%s', $request->getUrl()->getRootUrl(), $user),
138
                                'http://tools.ietf.org/html/rfc6750#section-2.3' => null,
139
                                'http://tools.ietf.org/html/rfc7233' => 'development' !== $this->options['server_mode'] ? 'GET' : null,
140
                            ),
141
                            'rel' => 'http://tools.ietf.org/id/draft-dejong-remotestorage',
142
                        ),
143
                        // legacy -03 WebFinger response
144
                        array(
145
                            'href' => sprintf('%s%s', $request->getUrl()->getRootUrl(), $user),
146
                            'properties' => array(
147
                                'http://remotestorage.io/spec/version' => 'draft-dejong-remotestorage-03',
148
                                'http://tools.ietf.org/html/rfc2616#section-14.16' => 'development' !== $this->options['server_mode'] ? 'GET' : false,
149
                                'http://tools.ietf.org/html/rfc6749#section-4.2' => sprintf('%s_oauth/authorize?login_hint=%s', $request->getUrl()->getRootUrl(), $user),
150
                                'http://tools.ietf.org/html/rfc6750#section-2.3' => false,
151
                            ),
152
                            'rel' => 'remotestorage',
153
                        ),
154
                    ),
155
                );
156
157
                $response = new Response(200, 'application/jrd+json');
158
                $response->setHeader('Access-Control-Allow-Origin', '*');
159
                $response->setBody(
160
                    Json::encode($webFingerData)
161
                );
162
163
                return $response;
164
            },
165
            array(
166
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array(
167
                    'enabled' => false,
168
                ),
169
            )
170
        );
171
172
        $this->get(
173
            '/',
174
            function (Request $request, UserInfoInterface $userInfo = null) {
175
                return $this->templateManager->render(
176
                    'indexPage',
177
                    array(
178
                        'user_id' => null !== $userInfo ? $userInfo->getUserId() : null,
179
                        'show_account_icon' => true,
180
                    )
181
                );
182
            },
183
            array(
184
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array(
185
                    'activate' => array('user'),
186
                    'require' => false,
187
                ),
188
            )
189
        );
190
191
        $this->addRoute(
192
            ['GET', 'HEAD'],
193
            '*',
194
            function (Request $request, TokenInfo $tokenInfo = null) {
195
                $response = $this->getObject($request, $tokenInfo);
196
                $this->addNoCache($response);
197
                $this->addCors($response);
198
199
                return $response;
200
            },
201
            array(
202
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array(
203
                    'activate' => array('api'),
204
                    'require' => false,
205
                ),
206
            )
207
        );
208
209
        // put a document
210
        $this->put(
211
            '*',
212
            function (Request $request, TokenInfo $tokenInfo) {
213
                $response = $this->putDocument($request, $tokenInfo);
214
                $this->addCors($response);
215
216
                return $response;
217
            },
218
            array(
219
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array(
220
                    'activate' => array('api'),
221
                ),
222
                'fkooman\Rest\Plugin\ReferrerCheck\ReferrerCheckPlugin' => array(
223
                    'enabled' => false,
224
                ),
225
            )
226
        );
227
228
        // delete a document
229
        $this->delete(
230
            '*',
231
            function (Request $request, TokenInfo $tokenInfo) {
232
                $response = $this->deleteDocument($request, $tokenInfo);
233
                $this->addCors($response);
234
235
                return $response;
236
            },
237
            array(
238
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array(
239
                    'activate' => array('api'),
240
                ),
241
                'fkooman\Rest\Plugin\ReferrerCheck\ReferrerCheckPlugin' => array(
242
                    'enabled' => false,
243
                ),
244
            )
245
        );
246
247
        // options request
248
        $this->options(
249
            '*',
250
            function (Request $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
251
                $response = new Response();
252
                $response->setHeader(
253
                    'Access-Control-Allow-Methods',
254
                    'GET, PUT, DELETE, HEAD, OPTIONS'
255
                );
256
                $response->setHeader(
257
                    'Access-Control-Allow-Headers',
258
                    'Authorization, Content-Length, Content-Type, Origin, X-Requested-With, If-Match, If-None-Match'
259
                );
260
261
                return $response;
262
            },
263
            array(
264
                'fkooman\Rest\Plugin\Authentication\AuthenticationPlugin' => array('enabled' => false),
265
            )
266
        );
267
    }
268
269
    public function getObject(Request $request, $tokenInfo)
270
    {
271
        $path = new Path($request->getUrl()->getPathInfo());
272
273
        // allow requests to public files (GET|HEAD) without authentication
274
        if ($path->getIsPublic() && $path->getIsDocument()) {
275
            return $this->getDocument($path, $request, $tokenInfo);
276
        }
277
278
        // past this point we MUST be authenticated
279
        if (null === $tokenInfo) {
280
            $e = new UnauthorizedException('unauthorized', 'must authenticate to view folder listing');
281
            $e->addScheme('Bearer', array('realm' => 'remoteStorage API'));
282
            throw $e;
283
        }
284
285
        if ($path->getIsFolder()) {
286
            return $this->getFolder($path, $request, $tokenInfo);
287
        }
288
289
        return $this->getDocument($path, $request, $tokenInfo);
290
    }
291
292
    public function getFolder(Path $path, Request $request, TokenInfo $tokenInfo)
293
    {
294
        if ($path->getUserId() !== $tokenInfo->getUserId()) {
295
            throw new ForbiddenException('path does not match authorized subject');
296
        }
297
        if (!$this->hasReadScope($tokenInfo->getScope(), $path->getModuleName())) {
298
            throw new ForbiddenException('path does not match authorized scope');
299
        }
300
301
        $folderVersion = $this->remoteStorage->getVersion($path);
302
        if (null === $folderVersion) {
303
            // folder does not exist, so we just invent this
304
            // ETag that will be the same for all empty folders
305
            $folderVersion = 'e:404';
306
        }
307
308
        $requestedVersion = $this->stripQuotes(
309
            $request->getHeader('If-None-Match')
310
        );
311
312 View Code Duplication
        if (null !== $requestedVersion) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313
            if (in_array($folderVersion, $requestedVersion)) {
314
                //return new RemoteStorageResponse($request, 304, $folderVersion);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
315
                $response = new Response(304, 'application/ld+json');
316
                $response->setHeader('ETag', '"'.$folderVersion.'"');
317
318
                return $response;
319
            }
320
        }
321
322
        $rsr = new Response(200, 'application/ld+json');
323
        $rsr->setHeader('ETag', '"'.$folderVersion.'"');
324
325
        if ('GET' === $request->getMethod()) {
326
            $rsr->setBody(
327
                $this->remoteStorage->getFolder(
328
                    $path,
329
                    $this->stripQuotes(
330
                        $request->getHeader('If-None-Match')
331
                    )
332
                )
333
            );
334
        }
335
336
        return $rsr;
337
    }
338
339
    public function getDocument(Path $path, Request $request, TokenInfo $tokenInfo = null)
340
    {
341
        if (null !== $tokenInfo) {
342
            if ($path->getUserId() !== $tokenInfo->getUserId()) {
343
                throw new ForbiddenException('path does not match authorized subject');
344
            }
345
            if (!$this->hasReadScope($tokenInfo->getScope(), $path->getModuleName())) {
346
                throw new ForbiddenException('path does not match authorized scope');
347
            }
348
        }
349
        $documentVersion = $this->remoteStorage->getVersion($path);
350
        if (null === $documentVersion) {
351
            throw new NotFoundException(
352
                sprintf('document "%s" not found', $path->getPath())
353
            );
354
        }
355
356
        $requestedVersion = $this->stripQuotes(
357
            $request->getHeader('If-None-Match')
358
        );
359
        $documentContentType = $this->remoteStorage->getContentType($path);
360
361 View Code Duplication
        if (null !== $requestedVersion) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
362
            if (in_array($documentVersion, $requestedVersion)) {
363
                $response = new Response(304, $documentContentType);
364
                $response->setHeader('ETag', '"'.$documentVersion.'"');
365
366
                return $response;
367
            }
368
        }
369
370
        $rsr = new Response(200, $documentContentType);
371
        $rsr->setHeader('ETag', '"'.$documentVersion.'"');
372
373
        if ('development' !== $this->options['server_mode']) {
374
            $rsr->setHeader('Accept-Ranges', 'bytes');
375
        }
376
377
        if ('GET' === $request->getMethod()) {
378
            if ('development' === $this->options['server_mode']) {
379
                // use body
380
                $rsr->setBody(
381
                    file_get_contents(
382
                        $this->remoteStorage->getDocument(
383
                            $path,
384
                            $requestedVersion
385
                        )
386
                    )
387
                );
388
            } else {
389
                // use X-SendFile
390
                $rsr->setFile(
391
                    $this->remoteStorage->getDocument(
392
                        $path,
393
                        $requestedVersion
394
                    )
395
                );
396
            }
397
        }
398
399
        return $rsr;
400
    }
401
402
    public function putDocument(Request $request, TokenInfo $tokenInfo)
403
    {
404
        $path = new Path($request->getUrl()->getPathInfo());
405
406
        if ($path->getUserId() !== $tokenInfo->getUserId()) {
407
            throw new ForbiddenException('path does not match authorized subject');
408
        }
409
        if (!$this->hasWriteScope($tokenInfo->getScope(), $path->getModuleName())) {
410
            throw new ForbiddenException('path does not match authorized scope');
411
        }
412
413
        $ifMatch = $this->stripQuotes(
414
            $request->getHeader('If-Match')
415
        );
416
        $ifNoneMatch = $this->stripQuotes(
417
            $request->getHeader('If-None-Match')
418
        );
419
420
        $documentVersion = $this->remoteStorage->getVersion($path);
421
        if (null !== $ifMatch && !in_array($documentVersion, $ifMatch)) {
422
            throw new PreconditionFailedException('version mismatch');
423
        }
424
425
        if (null !== $ifNoneMatch && in_array('*', $ifNoneMatch) && null !== $documentVersion) {
426
            throw new PreconditionFailedException('document already exists');
427
        }
428
429
        $x = $this->remoteStorage->putDocument(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $x is correct as $this->remoteStorage->pu...$ifMatch, $ifNoneMatch) (which targets fkooman\RemoteStorage\RemoteStorage::putDocument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
430
            $path,
431
            $request->getHeader('Content-Type'),
432
            $request->getBody(),
433
            $ifMatch,
434
            $ifNoneMatch
435
        );
436
        // we have to get the version again after the PUT
437
        $documentVersion = $this->remoteStorage->getVersion($path);
438
439
        $rsr = new Response();
440
        $rsr->setHeader('ETag', '"'.$documentVersion.'"');
441
        $rsr->setBody($x);
442
443
        return $rsr;
444
    }
445
446
    public function deleteDocument(Request $request, TokenInfo $tokenInfo)
447
    {
448
        $path = new Path($request->getUrl()->getPathInfo());
449
450
        if ($path->getUserId() !== $tokenInfo->getUserId()) {
451
            throw new ForbiddenException('path does not match authorized subject');
452
        }
453
        if (!$this->hasWriteScope($tokenInfo->getScope(), $path->getModuleName())) {
454
            throw new ForbiddenException('path does not match authorized scope');
455
        }
456
457
        // need to get the version before the delete
458
        $documentVersion = $this->remoteStorage->getVersion($path);
459
460
        $ifMatch = $this->stripQuotes(
461
            $request->getHeader('If-Match')
462
        );
463
464
        // if document does not exist, and we have If-Match header set we should
465
        // return a 412 instead of a 404
466
        if (null !== $ifMatch && !in_array($documentVersion, $ifMatch)) {
467
            throw new PreconditionFailedException('version mismatch');
468
        }
469
470
        if (null === $documentVersion) {
471
            throw new NotFoundException(
472
                sprintf('document "%s" not found', $path->getPath())
473
            );
474
        }
475
476
        $ifMatch = $this->stripQuotes(
477
            $request->getHeader('If-Match')
478
        );
479
        if (null !== $ifMatch && !in_array($documentVersion, $ifMatch)) {
480
            throw new PreconditionFailedException('version mismatch');
481
        }
482
483
        $x = $this->remoteStorage->deleteDocument(
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $x is correct as $this->remoteStorage->de...cument($path, $ifMatch) (which targets fkooman\RemoteStorage\Re...orage::deleteDocument()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
484
            $path,
485
            $ifMatch
486
        );
487
        $rsr = new Response();
488
        $rsr->setHeader('ETag', '"'.$documentVersion.'"');
489
        $rsr->setBody($x);
490
491
        return $rsr;
492
    }
493
494
#    public function optionsRequest(Request $request)
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
495
#    {
496
#        return new Response();
497
#    }
498
499
    private function hasReadScope(Scope $i, $moduleName)
500
    {
501
        $validReadScopes = array(
502
            '*:r',
503
            '*:rw',
504
            sprintf('%s:%s', $moduleName, 'r'),
505
            sprintf('%s:%s', $moduleName, 'rw'),
506
        );
507
508
        foreach ($validReadScopes as $scope) {
509
            if ($i->hasScope($scope)) {
510
                return true;
511
            }
512
        }
513
514
        return false;
515
    }
516
517
    private function hasWriteScope(Scope $i, $moduleName)
518
    {
519
        $validWriteScopes = array(
520
            '*:rw',
521
            sprintf('%s:%s', $moduleName, 'rw'),
522
        );
523
524
        foreach ($validWriteScopes as $scope) {
525
            if ($i->hasScope($scope)) {
526
                return true;
527
            }
528
        }
529
530
        return false;
531
    }
532
533
    /**
534
     * ETag/If-Match/If-None-Match are always quoted, this method removes
535
     * the quotes.
536
     */
537
    public function stripQuotes($versionHeader)
538
    {
539
        if (null === $versionHeader) {
540
            return;
541
        }
542
543
        $versions = array();
544
545
        if ('*' === $versionHeader) {
546
            return array('*');
547
        }
548
549
        foreach (explode(',', $versionHeader) as $v) {
550
            $v = trim($v);
551
            $startQuote = strpos($v, '"');
552
            $endQuote = strrpos($v, '"');
553
            $length = strlen($v);
554
555
            if (0 !== $startQuote || $length - 1 !== $endQuote) {
556
                throw new BadRequestException('version header must start and end with a double quote');
557
            }
558
            $versions[] = substr($v, 1, $length - 2);
559
        }
560
561
        return $versions;
562
    }
563
564
    public function run(Request $request = null)
565
    {
566
        if (null === $request) {
567
            throw new InvalidArgumentException('must provide Request object');
568
        }
569
570
        $response = null;
571
        try {
572
            $response = parent::run($request);
573
        } catch (PathException $e) {
574
            $e = new BadRequestException($e->getMessage());
575
            $response = $e->getJsonResponse();
576
        }
577
578
        // if error, add CORS
579
        // XXX: most ugly code below ;)
580
        $statusCode = explode(' ', $response->toArray()[0])[1];
581
        if (400 <= $statusCode && 500 > $statusCode) {
582
            $this->addCors($response);
583
            $this->addNoCache($response);
584
        }
585
586
        return $response;
587
    }
588
589
    private function addCors(Response &$response)
590
    {
591
        $response->setHeader('Access-Control-Allow-Origin', '*');
592
        $response->setHeader(
593
            'Access-Control-Expose-Headers',
594
            'ETag, Content-Length'
595
        );
596
    }
597
598
    private function addNoCache(Response &$response)
599
    {
600
        $response->setHeader('Expires', 0);
601
        $response->setHeader('Cache-Control', 'no-cache');
602
    }
603
}
604