Test Setup Failed
Push — master ( 210134...c17796 )
by Damian
03:18
created

src/Control/HTTP.php (14 issues)

1
<?php
2
3
namespace SilverStripe\Control;
4
5
use SilverStripe\Assets\File;
6
use SilverStripe\Core\Config\Configurable;
7
use SilverStripe\Core\Convert;
8
use InvalidArgumentException;
9
use finfo;
10
11
/**
12
 * A class with HTTP-related helpers. Like Debug, this is more a bundle of methods than a class.
13
 */
14
class HTTP
15
{
16
    use Configurable;
17
18
    /**
19
     * @var int
20
     */
21
    protected static $cache_age = 0;
22
23
    /**
24
     * @var int
25
     */
26
    protected static $modification_date = null;
27
28
    /**
29
     * @var string
30
     */
31
    protected static $etag = null;
32
33
    /**
34
     * @config
35
     *
36
     * @var bool
37
     */
38
    private static $cache_ajax_requests = true;
0 ignored issues
show
The private property $cache_ajax_requests is not used, and could be removed.
Loading history...
39
40
    /**
41
     * Turns a local system filename into a URL by comparing it to the script filename.
42
     *
43
     * @param string $filename
44
     * @return string
45
     */
46
    public static function filename2url($filename)
47
    {
48
        $filename = realpath($filename);
49
        if (!$filename) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
50
            return null;
51
        }
52
53
        // Filter files outside of the webroot
54
        $base = realpath(BASE_PATH);
55
        $baseLength = strlen($base);
0 ignored issues
show
It seems like $base can also be of type false; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

55
        $baseLength = strlen(/** @scrutinizer ignore-type */ $base);
Loading history...
56
        if (substr($filename, 0, $baseLength) !== $base) {
57
            return null;
58
        }
59
60
        $relativePath = ltrim(substr($filename, $baseLength), '/\\');
0 ignored issues
show
It seems like substr($filename, $baseLength) can also be of type false; however, parameter $str of ltrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

60
        $relativePath = ltrim(/** @scrutinizer ignore-type */ substr($filename, $baseLength), '/\\');
Loading history...
61
        return Director::absoluteURL($relativePath);
62
    }
63
64
    /**
65
     * Turn all relative URLs in the content to absolute URLs.
66
     *
67
     * @param string $html
68
     *
69
     * @return string
70
     */
71
    public static function absoluteURLs($html)
72
    {
73
        $html = str_replace('$CurrentPageURL', Controller::curr()->getRequest()->getURL(), $html);
74
        return HTTP::urlRewriter($html, function ($url) {
75
            //no need to rewrite, if uri has a protocol (determined here by existence of reserved URI character ":")
76
            if (preg_match('/^\w+:/', $url)) {
77
                return $url;
78
            }
79
            return Director::absoluteURL($url, true);
0 ignored issues
show
true of type true is incompatible with the type string expected by parameter $relativeParent of SilverStripe\Control\Director::absoluteURL(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

79
            return Director::absoluteURL($url, /** @scrutinizer ignore-type */ true);
Loading history...
80
        });
81
    }
82
83
    /**
84
     * Rewrite all the URLs in the given content, evaluating the given string as PHP code.
85
     *
86
     * Put $URL where you want the URL to appear, however, you can't embed $URL in strings, for example:
87
     * <ul>
88
     * <li><code>'"../../" . $URL'</code></li>
89
     * <li><code>'myRewriter($URL)'</code></li>
90
     * <li><code>'(substr($URL, 0, 1)=="/") ? "../" . substr($URL, 1) : $URL'</code></li>
91
     * </ul>
92
     *
93
     * As of 3.2 $code should be a callable which takes a single parameter and returns the rewritten,
94
     * for example:
95
     * <code>
96
     * function($url) {
97
     *      return Director::absoluteURL($url, true);
98
     * }
99
     * </code>
100
     *
101
     * @param string $content The HTML to search for links to rewrite.
102
     * @param callable $code Either a string that can evaluate to an expression to rewrite links
103
     * (depreciated), or a callable that takes a single parameter and returns the rewritten URL.
104
     *
105
     * @return string The content with all links rewritten as per the logic specified in $code.
106
     */
107
    public static function urlRewriter($content, $code)
108
    {
109
        if (!is_callable($code)) {
110
            throw new InvalidArgumentException(
111
                'HTTP::urlRewriter expects a callable as the second parameter'
112
            );
113
        }
114
115
        // Replace attributes
116
        $attribs = array("src", "background", "a" => "href", "link" => "href", "base" => "href");
117
        $regExps = [];
118
        foreach ($attribs as $tag => $attrib) {
119
            if (!is_numeric($tag)) {
120
                $tagPrefix = "$tag ";
121
            } else {
122
                $tagPrefix = "";
123
            }
124
125
            $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *\")([^\"]*)(\")/i";
126
            $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *')([^']*)(')/i";
127
            $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *)([^\"' ]*)( )/i";
128
        }
129
        // Replace css styles
130
        // @todo - http://www.css3.info/preview/multiple-backgrounds/
131
        $styles = array('background-image', 'background', 'list-style-image', 'list-style', 'content');
132
        foreach ($styles as $style) {
133
            $regExps[] = "/($style:[^;]*url *\\(\")([^\"]+)(\"\\))/i";
134
            $regExps[] = "/($style:[^;]*url *\\(')([^']+)('\\))/i";
135
            $regExps[] = "/($style:[^;]*url *\\()([^\"\\)')]+)(\\))/i";
136
        }
137
138
        // Callback for regexp replacement
139
        $callback = function ($matches) use ($code) {
140
            // Decode HTML attribute
141
            $URL = Convert::xml2raw($matches[2]);
142
            $rewritten = $code($URL);
143
            return $matches[1] . Convert::raw2xml($rewritten) . $matches[3];
0 ignored issues
show
Are you sure SilverStripe\Core\Convert::raw2xml($rewritten) of type string|array can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

143
            return $matches[1] . /** @scrutinizer ignore-type */ Convert::raw2xml($rewritten) . $matches[3];
Loading history...
144
        };
145
146
        // Execute each expression
147
        foreach ($regExps as $regExp) {
148
            $content = preg_replace_callback($regExp, $callback, $content);
149
        }
150
151
        return $content;
152
    }
153
154
    /**
155
     * Will try to include a GET parameter for an existing URL, preserving existing parameters and
156
     * fragments. If no URL is given, falls back to $_SERVER['REQUEST_URI']. Uses parse_url() to
157
     * dissect the URL, and http_build_query() to reconstruct it with the additional parameter.
158
     * Converts any '&' (ampersand) URL parameter separators to the more XHTML compliant '&amp;'.
159
     *
160
     * CAUTION: If the URL is determined to be relative, it is prepended with Director::absoluteBaseURL().
161
     * This method will always return an absolute URL because Director::makeRelative() can lead to
162
     * inconsistent results.
163
     *
164
     * @param string $varname
165
     * @param string $varvalue
166
     * @param string|null $currentURL Relative or absolute URL, or HTTPRequest to get url from
167
     * @param string $separator Separator for http_build_query().
168
     * @return string
169
     */
170
    public static function setGetVar($varname, $varvalue, $currentURL = null, $separator = '&')
171
    {
172
        if (!isset($currentURL)) {
173
            $request = Controller::curr()->getRequest();
174
            $currentURL = $request->getURL(true);
175
        }
176
        $uri = $currentURL;
177
178
        $isRelative = false;
179
        // We need absolute URLs for parse_url()
180
        if (Director::is_relative_url($uri)) {
181
            $uri = Director::absoluteBaseURL() . $uri;
182
            $isRelative = true;
183
        }
184
185
        // try to parse uri
186
        $parts = parse_url($uri);
187
        if (!$parts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
188
            throw new InvalidArgumentException("Can't parse URL: " . $uri);
189
        }
190
191
        // Parse params and add new variable
192
        $params = array();
193
        if (isset($parts['query'])) {
194
            parse_str($parts['query'], $params);
195
        }
196
        $params[$varname] = $varvalue;
197
198
        // Generate URI segments and formatting
199
        $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
200
        $user = (isset($parts['user']) && $parts['user'] != '')  ? $parts['user'] : '';
201
202
        if ($user != '') {
203
            // format in either user:[email protected] or [email protected]
204
            $user .= (isset($parts['pass']) && $parts['pass'] != '') ? ':' . $parts['pass'] . '@' : '@';
205
        }
206
207
        $host = (isset($parts['host'])) ? $parts['host'] : '';
208
        $port = (isset($parts['port']) && $parts['port'] != '') ? ':'.$parts['port'] : '';
209
        $path = (isset($parts['path']) && $parts['path'] != '') ? $parts['path'] : '';
210
211
        // handle URL params which are existing / new
212
        $params = ($params) ?  '?' . http_build_query($params, null, $separator) : '';
213
214
        // keep fragments (anchors) intact.
215
        $fragment = (isset($parts['fragment']) && $parts['fragment'] != '') ?  '#'.$parts['fragment'] : '';
216
217
        // Recompile URI segments
218
        $newUri =  $scheme . '://' . $user . $host . $port . $path . $params . $fragment;
219
220
        if ($isRelative) {
221
            return Director::makeRelative($newUri);
222
        }
223
224
        return $newUri;
225
    }
226
227
    /**
228
     * @param string $varname
229
     * @param string $varvalue
230
     * @param null|string $currentURL
231
     *
232
     * @return string
233
     */
234
    public static function RAW_setGetVar($varname, $varvalue, $currentURL = null)
235
    {
236
        $url = self::setGetVar($varname, $varvalue, $currentURL);
237
        return Convert::xml2raw($url);
238
    }
239
240
    /**
241
     * Search for all tags with a specific attribute, then return the value of that attribute in a
242
     * flat array.
243
     *
244
     * @param string $content
245
     * @param array $attributes An array of tags to attributes, for example "[a] => 'href', [div] => 'id'"
246
     *
247
     * @return array
248
     */
249
    public static function findByTagAndAttribute($content, $attributes)
250
    {
251
        $regexes = array();
252
253
        foreach ($attributes as $tag => $attribute) {
254
            $regexes[] = "/<{$tag} [^>]*$attribute *= *([\"'])(.*?)\\1[^>]*>/i";
255
            $regexes[] = "/<{$tag} [^>]*$attribute *= *([^ \"'>]+)/i";
256
        }
257
258
        $result = array();
259
260
        if ($regexes) {
261
            foreach ($regexes as $regex) {
262
                if (preg_match_all($regex, $content, $matches)) {
263
                    $result = array_merge_recursive($result, (isset($matches[2]) ? $matches[2] : $matches[1]));
264
                }
265
            }
266
        }
267
268
        return count($result) ? $result : null;
269
    }
270
271
    /**
272
     * @param string $content
273
     *
274
     * @return array
275
     */
276
    public static function getLinksIn($content)
277
    {
278
        return self::findByTagAndAttribute($content, array("a" => "href"));
279
    }
280
281
    /**
282
     * @param string $content
283
     *
284
     * @return array
285
     */
286
    public static function getImagesIn($content)
287
    {
288
        return self::findByTagAndAttribute($content, array("img" => "src"));
289
    }
290
291
    /**
292
     * Get the MIME type based on a file's extension. If the finfo class exists in PHP, and the file
293
     * exists relative to the project root, then use that extension, otherwise fallback to a list of
294
     * commonly known MIME types.
295
     *
296
     * @param string $filename
297
     *
298
     * @return string
299
     */
300
    public static function get_mime_type($filename)
301
    {
302
        // If the finfo module is compiled into PHP, use it.
303
        $path = BASE_PATH . DIRECTORY_SEPARATOR . $filename;
304
        if (class_exists('finfo') && file_exists($path)) {
305
            $finfo = new finfo(FILEINFO_MIME_TYPE);
0 ignored issues
show
The call to finfo::finfo() has too few arguments starting with arg. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

305
            $finfo = /** @scrutinizer ignore-call */ new finfo(FILEINFO_MIME_TYPE);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
306
            return $finfo->file($path);
307
        }
308
309
        // Fallback to use the list from the HTTP.yml configuration and rely on the file extension
310
        // to get the file mime-type
311
        $ext = strtolower(File::get_file_extension($filename));
312
        // Get the mime-types
313
        $mimeTypes = HTTP::config()->uninherited('MimeTypes');
314
315
        // The mime type doesn't exist
316
        if (!isset($mimeTypes[$ext])) {
317
            return 'application/unknown';
318
        }
319
320
        return $mimeTypes[$ext];
321
    }
322
323
    /**
324
     * Set the maximum age of this page in web caches, in seconds.
325
     *
326
     * @param int $age
327
     */
328
    public static function set_cache_age($age)
329
    {
330
        self::$cache_age = $age;
331
    }
332
333
    /**
334
     * @param string $dateString
335
     */
336
    public static function register_modification_date($dateString)
337
    {
338
        $timestamp = strtotime($dateString);
339
        if ($timestamp > self::$modification_date) {
340
            self::$modification_date = $timestamp;
0 ignored issues
show
Documentation Bug introduced by
It seems like $timestamp can also be of type false. However, the property $modification_date is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
341
        }
342
    }
343
344
    /**
345
     * @param int $timestamp
346
     */
347
    public static function register_modification_timestamp($timestamp)
348
    {
349
        if ($timestamp > self::$modification_date) {
350
            self::$modification_date = $timestamp;
351
        }
352
    }
353
354
    /**
355
     * @param string $etag
356
     */
357
    public static function register_etag($etag)
358
    {
359
        if (0 !== strpos($etag, '"')) {
360
            $etag = sprintf('"%s"', $etag);
361
        }
362
        self::$etag = $etag;
363
    }
364
365
    /**
366
     * Add the appropriate caching headers to the response, including If-Modified-Since / 304 handling.
367
     * Note that setting HTTP::$cache_age will overrule any cache headers set by PHP's
368
     * session_cache_limiter functionality. It is your responsibility to ensure only cacheable data
369
     * is in fact cached, and HTTP::$cache_age isn't set when the HTTP body contains session-specific
370
     * content.
371
     *
372
     * Omitting the $body argument or passing a string is deprecated; in these cases, the headers are
373
     * output directly.
374
     *
375
     * @param HTTPResponse $body
376
     */
377
    public static function add_cache_headers($body = null)
378
    {
379
        $cacheAge = self::$cache_age;
380
381
        // Validate argument
382
        if ($body && !($body instanceof HTTPResponse)) {
383
            user_error("HTTP::add_cache_headers() must be passed an HTTPResponse object", E_USER_WARNING);
384
            $body = null;
385
        }
386
387
        // Development sites have frequently changing templates; this can get stuffed up by the code
388
        // below.
389
        if (Director::isDev()) {
390
            $cacheAge = 0;
391
        }
392
393
        // The headers have been sent and we don't have an HTTPResponse object to attach things to; no point in
394
        // us trying.
395
        if (headers_sent() && !$body) {
396
            return;
397
        }
398
399
        // Populate $responseHeaders with all the headers that we want to build
400
        $responseHeaders = array();
401
402
        $cacheControlHeaders = HTTP::config()->uninherited('cache_control');
403
404
405
        // currently using a config setting to cancel this, seems to be so that the CMS caches ajax requests
406
        if (function_exists('apache_request_headers') && static::config()->uninherited('cache_ajax_requests')) {
407
            $requestHeaders = array_change_key_case(apache_request_headers(), CASE_LOWER);
0 ignored issues
show
It seems like apache_request_headers() can also be of type false; however, parameter $input of array_change_key_case() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

407
            $requestHeaders = array_change_key_case(/** @scrutinizer ignore-type */ apache_request_headers(), CASE_LOWER);
Loading history...
408
            if (isset($requestHeaders['x-requested-with'])
409
                && $requestHeaders['x-requested-with']=='XMLHttpRequest'
410
            ) {
411
                $cacheAge = 0;
412
            }
413
        }
414
415
        if ($cacheAge > 0) {
416
            $cacheControlHeaders['max-age'] = self::$cache_age;
417
418
            // Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information,
419
            // defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter).
420
            // Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should
421
            // prefer the caching information indicated through the "Cache-Control" header.
422
            $responseHeaders["Pragma"] = "";
423
424
            // To do: User-Agent should only be added in situations where you *are* actually
425
            // varying according to user-agent.
426
            $vary = HTTP::config()->uninherited('vary');
427
            if ($vary && strlen($vary)) {
428
                $responseHeaders['Vary'] = $vary;
429
            }
430
        } else {
431
            $contentDisposition = null;
432
            if ($body) {
433
                // Grab header for checking. Unfortunately HTTPRequest uses a mistyped variant.
434
                $contentDisposition = $body->getHeader('Content-disposition');
435
                if (!$contentDisposition) {
436
                    $contentDisposition = $body->getHeader('Content-Disposition');
437
                }
438
            }
439
440
            if ($body &&
441
                Director::is_https() &&
442
                isset($_SERVER['HTTP_USER_AGENT']) &&
443
                strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE')==true &&
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') of type string to the boolean true. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
444
                strstr($contentDisposition, 'attachment;')==true
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strstr($contentDisposition, 'attachment;') of type string to the boolean true. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
445
            ) {
446
                // IE6-IE8 have problems saving files when https and no-cache are used
447
                // (http://support.microsoft.com/kb/323308)
448
                // Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options.
449
                $cacheControlHeaders['max-age'] = 3;
450
451
                // Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information,
452
                // defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter).
453
                // Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should
454
                // prefer the caching information indicated through the "Cache-Control" header.
455
                $responseHeaders["Pragma"] = "";
456
            } else {
457
                $cacheControlHeaders['no-cache'] = "true";
458
                $cacheControlHeaders['no-store'] = "true";
459
            }
460
        }
461
462
        foreach ($cacheControlHeaders as $header => $value) {
463
            if (is_null($value)) {
464
                unset($cacheControlHeaders[$header]);
465
            } elseif ((is_bool($value) && $value) || $value === "true") {
466
                $cacheControlHeaders[$header] = $header;
467
            } else {
468
                $cacheControlHeaders[$header] = $header."=".$value;
469
            }
470
        }
471
472
        $responseHeaders['Cache-Control'] = implode(', ', $cacheControlHeaders);
473
        unset($cacheControlHeaders, $header, $value);
474
475
        if (self::$modification_date && $cacheAge > 0) {
476
            $responseHeaders["Last-Modified"] = self::gmt_date(self::$modification_date);
477
478
            // Chrome ignores Varies when redirecting back (http://code.google.com/p/chromium/issues/detail?id=79758)
479
            // which means that if you log out, you get redirected back to a page which Chrome then checks against
480
            // last-modified (which passes, getting a 304)
481
            // when it shouldn't be trying to use that page at all because it's the "logged in" version.
482
            // By also using and etag that includes both the modification date and all the varies
483
            // values which we also check against we can catch this and not return a 304
484
            $etagParts = array(self::$modification_date, serialize($_COOKIE));
485
            $etagParts[] = Director::is_https() ? 'https' : 'http';
486
            if (isset($_SERVER['HTTP_USER_AGENT'])) {
487
                $etagParts[] = $_SERVER['HTTP_USER_AGENT'];
488
            }
489
            if (isset($_SERVER['HTTP_ACCEPT'])) {
490
                $etagParts[] = $_SERVER['HTTP_ACCEPT'];
491
            }
492
493
            $etag = sha1(implode(':', $etagParts));
494
            $responseHeaders["ETag"] = $etag;
495
496
            // 304 response detection
497
            if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
498
                $ifModifiedSince = strtotime(stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']));
499
500
                // As above, only 304 if the last request had all the same varies values
501
                // (or the etag isn't passed as part of the request - but with chrome it always is)
502
                $matchesEtag = !isset($_SERVER['HTTP_IF_NONE_MATCH']) || $_SERVER['HTTP_IF_NONE_MATCH'] == $etag;
503
504
                if ($ifModifiedSince >= self::$modification_date && $matchesEtag) {
505
                    if ($body) {
506
                        $body->setStatusCode(304);
507
                        $body->setBody('');
508
                    } else {
509
                        header('HTTP/1.0 304 Not Modified');
510
                        die();
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
511
                    }
512
                }
513
            }
514
515
            $expires = time() + $cacheAge;
516
            $responseHeaders["Expires"] = self::gmt_date($expires);
517
        }
518
519
        if (self::$etag) {
520
            $responseHeaders['ETag'] = self::$etag;
521
        }
522
523
        // etag needs to be a quoted string according to HTTP spec
524
        if (!empty($responseHeaders['ETag']) && 0 !== strpos($responseHeaders['ETag'], '"')) {
525
            $responseHeaders['ETag'] = sprintf('"%s"', $responseHeaders['ETag']);
526
        }
527
528
        // Now that we've generated them, either output them or attach them to the HTTPResponse as appropriate
529
        foreach ($responseHeaders as $k => $v) {
530
            if ($body) {
531
                // Set the header now if it's not already set.
532
                if ($body->getHeader($k) === null) {
533
                    $body->addHeader($k, $v);
534
                }
535
            } elseif (!headers_sent()) {
536
                header("$k: $v");
537
            }
538
        }
539
    }
540
541
542
    /**
543
     * Return an {@link http://www.faqs.org/rfcs/rfc2822 RFC 2822} date in the GMT timezone (a timestamp
544
     * is always in GMT: the number of seconds since January 1 1970 00:00:00 GMT)
545
     *
546
     * @param int $timestamp
547
     *
548
     * @return string
549
     */
550
    public static function gmt_date($timestamp)
551
    {
552
        return gmdate('D, d M Y H:i:s', $timestamp) . ' GMT';
0 ignored issues
show
Are you sure gmdate('D, d M Y H:i:s', $timestamp) of type false|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

552
        return /** @scrutinizer ignore-type */ gmdate('D, d M Y H:i:s', $timestamp) . ' GMT';
Loading history...
553
    }
554
555
    /**
556
     * Return static variable cache_age in second
557
     *
558
     * @return int
559
     */
560
    public static function get_cache_age()
561
    {
562
        return self::$cache_age;
563
    }
564
}
565