• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Utils / 29770052588

20 Jul 2026 06:55PM UTC coverage: 92.619% (-0.1%) from 92.722%
29770052588

push

github

web-flow
Merge pull request #57 from JBZoo/release/8.0

8.0.0 — PHP 8.3+ floor & lock-step major

15 of 16 new or added lines in 7 files covered. (93.75%)

106 existing lines in 13 files now uncovered.

1669 of 1802 relevant lines covered (92.62%)

41.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.67
/src/Http.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Utils.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Utils
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Utils;
18

19
/**
20
 * @psalm-suppress UnusedClass
21
 */
22
final class Http
23
{
24
    /**
25
     * Transmit headers that force a browser to display the download file dialog.
26
     * Cross browser compatible. Only fires if headers have not already been sent.
27
     * @param string $filename The name of the filename to display to browsers
28
     * @codeCoverageIgnore
29
     */
30
    public static function download(string $filename): bool
31
    {
32
        if (\headers_sent()) {
33
            return false;
34
        }
35

36
        // required for IE, otherwise Content-disposition is ignored
37
        if (isStrEmpty(Sys::iniGet('zlib.output_compression'))) {
38
            Sys::iniSet('zlib.output_compression', 'Off');
39
        }
40

41
        Sys::setTime();
42

43
        // Set headers
44
        \header('Pragma: public');
45
        \header('Expires: 0');
46
        \header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
47
        \header('Cache-Control: private', false);
48
        \header('Content-Disposition: attachment; filename="' . \basename(\str_replace('"', '', $filename)) . '";');
49
        \header('Content-Type: application/force-download');
50
        \header('Content-Transfer-Encoding: binary');
51
        \header('Content-Length: ' . \filesize($filename));
52

53
        // output file
54
        if (Sys::isFunc('fpassthru')) {
55
            $handle = \fopen($filename, 'r');
56
            if ($handle === false) {
57
                throw new Exception("Can't open file '{$filename}'");
58
            }
59
            \fpassthru($handle);
60
            \fclose($handle);
61
        } else {
62
            /** @phan-suppress-next-line PhanPluginRemoveDebugEcho */
63
            echo \file_get_contents($filename);
64
        }
65

66
        return true;
67
    }
68

69
    /**
70
     * Sets the headers to prevent caching for the different browsers.
71
     * Different browsers support different nocache headers, so several
72
     * headers must be sent so that all of them get the point that no caching should occur.
73
     * @codeCoverageIgnore
74
     */
75
    public static function nocache(): bool
76
    {
77
        if (!\headers_sent()) {
78
            \header('Expires: Wed, 11 Jan 1984 05:00:00 GMT');
79
            \header('Last-Modified: ' . \gmdate('D, d M Y H:i:s') . ' GMT');
80
            \header('Cache-Control: no-cache, must-revalidate, max-age=0');
81
            \header('Pragma: no-cache');
82

83
            return true;
84
        }
85

86
        return false;
87
    }
88

89
    /**
90
     * Transmit UTF-8 content headers if the headers haven't already been sent.
91
     *
92
     * @param string $contentType The content type to send out
93
     * @codeCoverageIgnore
94
     */
95
    public static function utf8(string $contentType = 'text/html'): bool
96
    {
97
        if (!\headers_sent()) {
98
            \header('Content-type: ' . $contentType . '; charset=utf-8');
99

100
            return true;
101
        }
102

103
        return false;
104
    }
105

106
    /**
107
     * Get all HTTP headers.
108
     * @see https://github.com/symfony/http-foundation/blob/master/ServerBag.php
109
     * @SuppressWarnings(PHPMD.Superglobals)
110
     * @SuppressWarnings(PHPMD.NPathComplexity)
111
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
112
     */
113
    public static function getHeaders(): array
114
    {
115
        $headers = [];
72✔
116

117
        $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true];
72✔
118

119
        foreach ($_SERVER as $key => $value) {
72✔
120
            if (\str_starts_with($key, 'HTTP_')) {
72✔
121
                $headers[\substr($key, 5)] = $value;
48✔
122
            } elseif (isset($contentHeaders[$key])) { // CONTENT_* are not prefixed with HTTP_
72✔
UNCOV
123
                $headers[$key] = $value;
×
124
            }
125
        }
126

127
        if (isset($_SERVER['PHP_AUTH_USER'])) {
72✔
128
            $headers['PHP_AUTH_USER'] = $_SERVER['PHP_AUTH_USER'];
18✔
129
            $headers['PHP_AUTH_PW']   = $_SERVER['PHP_AUTH_PW'] ?? '';
18✔
130
        } else {
131
            /*
132
             * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default
133
             * For this workaround to work, add these lines to your .htaccess file:
134
             * RewriteCond %{HTTP:Authorization} ^(.+)$
135
             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
136
             *
137
             * A sample .htaccess file:
138
             * RewriteEngine On
139
             * RewriteCond %{HTTP:Authorization} ^(.+)$
140
             * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
141
             * RewriteCond %{REQUEST_FILENAME} !-f
142
             * RewriteRule ^(.*)$ app.php [QSA,L]
143
             */
144
            $authorizationHeader = null;
54✔
145
            if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
54✔
146
                $authorizationHeader = $_SERVER['HTTP_AUTHORIZATION'];
36✔
147
            } elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
18✔
148
                $authorizationHeader = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
18✔
149
            }
150

151
            if (bool($authorizationHeader)) {
54✔
152
                $authorizationHeader = (string)$authorizationHeader;
54✔
153
                if (\stripos($authorizationHeader, 'basic ') === 0) {
54✔
154
                    // Decode AUTHORIZATION header into PHP_AUTH_USER
155
                    // and PHP_AUTH_PW when authorization header is basic
156
                    $exploded = \explode(
18✔
157
                        ':',
18✔
158
                        (string)\base64_decode(\substr($authorizationHeader, 6), true),
18✔
159
                        2,
18✔
160
                    );
18✔
161

162
                    $expectedNumOfParts = 2;
18✔
163
                    if (\count($exploded) === $expectedNumOfParts) {
18✔
164
                        $headers['PHP_AUTH_USER'] = $exploded[0];
18✔
165
                        $headers['PHP_AUTH_PW']   = $exploded[1] ?? ''; // @phpstan-ignore-line
18✔
166
                    }
167
                } elseif (
168
                    isStrEmpty($_SERVER['PHP_AUTH_DIGEST'] ?? '')
36✔
169
                    && (\stripos($authorizationHeader, 'digest ') === 0)
36✔
170
                ) {
171
                    // In some circumstances PHP_AUTH_DIGEST needs to be set
172
                    $headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
12✔
173
                    $_SERVER['PHP_AUTH_DIGEST'] = $authorizationHeader;
12✔
174
                } elseif (\stripos($authorizationHeader, 'bearer ') === 0) {
24✔
175
                    /*
176
                     * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables,
177
                     *      I'll just set $headers['AUTHORIZATION'] here.
178
                     *      http://php.net/manual/en/reserved.variables.server.php
179
                     */
180
                    $headers['AUTHORIZATION'] = $authorizationHeader;
12✔
181
                }
182
            }
183
        }
184

185
        if (isset($headers['AUTHORIZATION'])) {
72✔
186
            return $headers;
48✔
187
        }
188

189
        // PHP_AUTH_USER/PHP_AUTH_PW
190
        if (isset($headers['PHP_AUTH_USER'])) {
24✔
191
            $user          = $headers['PHP_AUTH_USER'];
18✔
192
            $password      = $headers['PHP_AUTH_PW'] ?? '';
18✔
193
            $authorization = 'Basic ' . \base64_encode($user . ':' . $password);
18✔
194

195
            $headers['AUTHORIZATION'] = $authorization;
18✔
196
        } elseif (isset($headers['PHP_AUTH_DIGEST'])) {
6✔
197
            $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
6✔
198
        }
199

200
        return $headers;
24✔
201
    }
202
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc