Completed
Push — authpdo ( 7f89f0...388201 )
by Andreas
18:44 queued 12:59
created

httputils.php ➔ http_rangeRequest()   F

Complexity

Conditions 19
Paths 686

Size

Total Lines 83
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 56
nc 686
nop 3
dl 0
loc 83
rs 2.4096
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Utilities for handling HTTP related tasks
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Andreas Gohr <[email protected]>
7
 */
8
9
define('HTTP_MULTIPART_BOUNDARY','D0KuW1K1B0uNDARY');
10
define('HTTP_HEADER_LF',"\r\n");
11
define('HTTP_CHUNK_SIZE',16*1024);
12
13
/**
14
 * Checks and sets HTTP headers for conditional HTTP requests
15
 *
16
 * @author   Simon Willison <[email protected]>
17
 * @link     http://simonwillison.net/2003/Apr/23/conditionalGet/
18
 *
19
 * @param    int $timestamp lastmodified time of the cache file
20
 * @returns  void or exits with previously header() commands executed
21
 */
22
function http_conditionalRequest($timestamp){
23
    // A PHP implementation of conditional get, see
24
    //   http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers/
25
    $last_modified = substr(gmdate('r', $timestamp), 0, -5).'GMT';
26
    $etag = '"'.md5($last_modified).'"';
27
    // Send the headers
28
    header("Last-Modified: $last_modified");
29
    header("ETag: $etag");
30
    // See if the client has provided the required headers
31
    if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])){
32
        $if_modified_since = stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']);
33
    }else{
34
        $if_modified_since = false;
35
    }
36
37
    if (isset($_SERVER['HTTP_IF_NONE_MATCH'])){
38
        $if_none_match = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);
39
    }else{
40
        $if_none_match = false;
41
    }
42
43
    if (!$if_modified_since && !$if_none_match){
0 ignored issues
show
Bug Best Practice introduced by
The expression $if_modified_since of type string|false 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...
Bug Best Practice introduced by
The expression $if_none_match of type string|false 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...
44
        return;
45
    }
46
47
    // At least one of the headers is there - check them
48
    if ($if_none_match && $if_none_match != $etag) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $if_none_match of type string|false is loosely compared to true; 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...
49
        return; // etag is there but doesn't match
50
    }
51
52
    if ($if_modified_since && $if_modified_since != $last_modified) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $if_modified_since of type string|false is loosely compared to true; 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...
53
        return; // if-modified-since is there but doesn't match
54
    }
55
56
    // Nothing has changed since their last request - serve a 304 and exit
57
    header('HTTP/1.0 304 Not Modified');
58
59
    // don't produce output, even if compression is on
60
    @ob_end_clean();
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
61
    exit;
62
}
63
64
/**
65
 * Let the webserver send the given file via x-sendfile method
66
 *
67
 * @author Chris Smith <[email protected]>
68
 *
69
 * @param string $file absolute path of file to send
70
 * @returns  void or exits with previous header() commands executed
71
 */
72
function http_sendfile($file) {
73
    global $conf;
74
75
    //use x-sendfile header to pass the delivery to compatible web servers
76
    if($conf['xsendfile'] == 1){
77
        header("X-LIGHTTPD-send-file: $file");
78
        ob_end_clean();
79
        exit;
80
    }elseif($conf['xsendfile'] == 2){
81
        header("X-Sendfile: $file");
82
        ob_end_clean();
83
        exit;
84
    }elseif($conf['xsendfile'] == 3){
85
        // FS#2388 nginx just needs the relative path.
86
        $file = DOKU_REL.substr($file, strlen(fullpath(DOKU_INC)) + 1);
87
        header("X-Accel-Redirect: $file");
88
        ob_end_clean();
89
        exit;
90
    }
91
}
92
93
/**
94
 * Send file contents supporting rangeRequests
95
 *
96
 * This function exits the running script
97
 *
98
 * @param resource $fh - file handle for an already open file
99
 * @param int $size     - size of the whole file
100
 * @param int $mime     - MIME type of the file
101
 *
102
 * @author Andreas Gohr <[email protected]>
103
 */
104
function http_rangeRequest($fh,$size,$mime){
105
    $ranges  = array();
106
    $isrange = false;
107
108
    header('Accept-Ranges: bytes');
109
110
    if(!isset($_SERVER['HTTP_RANGE'])){
111
        // no range requested - send the whole file
112
        $ranges[] = array(0,$size,$size);
113
    }else{
114
        $t = explode('=', $_SERVER['HTTP_RANGE']);
115
        if (!$t[0]=='bytes') {
116
            // we only understand byte ranges - send the whole file
117
            $ranges[] = array(0,$size,$size);
118
        }else{
119
            $isrange = true;
120
            // handle multiple ranges
121
            $r = explode(',',$t[1]);
122
            foreach($r as $x){
123
                $p = explode('-', $x);
124
                $start = (int)$p[0];
125
                $end   = (int)$p[1];
126
                if (!$end) $end = $size - 1;
127
                if ($start > $end || $start > $size || $end > $size){
128
                    header('HTTP/1.1 416 Requested Range Not Satisfiable');
129
                    print 'Bad Range Request!';
130
                    exit;
131
                }
132
                $len = $end - $start + 1;
133
                $ranges[] = array($start,$end,$len);
134
            }
135
        }
136
    }
137
    $parts = count($ranges);
138
139
    // now send the type and length headers
140
    if(!$isrange){
141
        header("Content-Type: $mime",true);
142
    }else{
143
        header('HTTP/1.1 206 Partial Content');
144
        if($parts == 1){
145
            header("Content-Type: $mime",true);
146
        }else{
147
            header('Content-Type: multipart/byteranges; boundary='.HTTP_MULTIPART_BOUNDARY,true);
148
        }
149
    }
150
151
    // send all ranges
152
    for($i=0; $i<$parts; $i++){
153
        list($start,$end,$len) = $ranges[$i];
154
155
        // multipart or normal headers
156
        if($parts > 1){
157
            echo HTTP_HEADER_LF.'--'.HTTP_MULTIPART_BOUNDARY.HTTP_HEADER_LF;
158
            echo "Content-Type: $mime".HTTP_HEADER_LF;
159
            echo "Content-Range: bytes $start-$end/$size".HTTP_HEADER_LF;
160
            echo HTTP_HEADER_LF;
161
        }else{
162
            header("Content-Length: $len");
163
            if($isrange){
164
                header("Content-Range: bytes $start-$end/$size");
165
            }
166
        }
167
168
        // send file content
169
        fseek($fh,$start); //seek to start of range
170
        $chunk = ($len > HTTP_CHUNK_SIZE) ? HTTP_CHUNK_SIZE : $len;
171
        while (!feof($fh) && $chunk > 0) {
172
            @set_time_limit(30); // large files can take a lot of time
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
173
            print fread($fh, $chunk);
174
            flush();
175
            $len -= $chunk;
176
            $chunk = ($len > HTTP_CHUNK_SIZE) ? HTTP_CHUNK_SIZE : $len;
177
        }
178
    }
179
    if($parts > 1){
180
        echo HTTP_HEADER_LF.'--'.HTTP_MULTIPART_BOUNDARY.'--'.HTTP_HEADER_LF;
181
    }
182
183
    // everything should be done here, exit (or return if testing)
184
    if (defined('SIMPLE_TEST')) return;
185
    exit;
186
}
187
188
/**
189
 * Check for a gzipped version and create if necessary
190
 *
191
 * return true if there exists a gzip version of the uncompressed file
192
 * (samepath/samefilename.sameext.gz) created after the uncompressed file
193
 *
194
 * @author Chris Smith <[email protected]>
195
 *
196
 * @param string $uncompressed_file
197
 * @return bool
198
 */
199
function http_gzip_valid($uncompressed_file) {
200
    if(!DOKU_HAS_GZIP) return false;
201
202
    $gzip = $uncompressed_file.'.gz';
203
    if (filemtime($gzip) < filemtime($uncompressed_file)) {    // filemtime returns false (0) if file doesn't exist
204
        return copy($uncompressed_file, 'compress.zlib://'.$gzip);
205
    }
206
207
    return true;
208
}
209
210
/**
211
 * Set HTTP headers and echo cachefile, if useable
212
 *
213
 * This function handles output of cacheable resource files. It ses the needed
214
 * HTTP headers. If a useable cache is present, it is passed to the web server
215
 * and the script is terminated.
216
 *
217
 * @param string $cache cache file name
218
 * @param bool   $cache_ok    if cache can be used
219
 */
220
function http_cached($cache, $cache_ok) {
221
    global $conf;
222
223
    // check cache age & handle conditional request
224
    // since the resource files are timestamped, we can use a long max age: 1 year
225
    header('Cache-Control: public, max-age=31536000');
226
    header('Pragma: public');
227
    if($cache_ok){
228
        http_conditionalRequest(filemtime($cache));
229
        if($conf['allowdebug']) header("X-CacheUsed: $cache");
230
231
        // finally send output
232
        if ($conf['gzip_output'] && http_gzip_valid($cache)) {
233
            header('Vary: Accept-Encoding');
234
            header('Content-Encoding: gzip');
235
            readfile($cache.".gz");
236
        } else {
237
            http_sendfile($cache);
238
            readfile($cache);
239
        }
240
        exit;
241
    }
242
243
    http_conditionalRequest(time());
244
}
245
246
/**
247
 * Cache content and print it
248
 *
249
 * @param string $file file name
250
 * @param string $content
251
 */
252
function http_cached_finish($file, $content) {
253
    global $conf;
254
255
    // save cache file
256
    io_saveFile($file, $content);
257
    if(DOKU_HAS_GZIP) io_saveFile("$file.gz",$content);
258
259
    // finally send output
260
    if ($conf['gzip_output'] && DOKU_HAS_GZIP) {
261
        header('Vary: Accept-Encoding');
262
        header('Content-Encoding: gzip');
263
        print gzencode($content,9,FORCE_GZIP);
264
    } else {
265
        print $content;
266
    }
267
}
268
269
/**
270
 * Fetches raw, unparsed POST data
271
 *
272
 * @return string
273
 */
274
function http_get_raw_post_data() {
275
    static $postData = null;
276
    if ($postData === null) {
277
        $postData = file_get_contents('php://input');
278
    }
279
    return $postData;
280
}
281
282
/**
283
 * Set the HTTP response status and takes care of the used PHP SAPI
284
 *
285
 * Inspired by CodeIgniter's set_status_header function
286
 *
287
 * @param int    $code
288
 * @param string $text
289
 */
290
function http_status($code = 200, $text = '') {
291
    static $stati = array(
292
        200 => 'OK',
293
        201 => 'Created',
294
        202 => 'Accepted',
295
        203 => 'Non-Authoritative Information',
296
        204 => 'No Content',
297
        205 => 'Reset Content',
298
        206 => 'Partial Content',
299
300
        300 => 'Multiple Choices',
301
        301 => 'Moved Permanently',
302
        302 => 'Found',
303
        304 => 'Not Modified',
304
        305 => 'Use Proxy',
305
        307 => 'Temporary Redirect',
306
307
        400 => 'Bad Request',
308
        401 => 'Unauthorized',
309
        403 => 'Forbidden',
310
        404 => 'Not Found',
311
        405 => 'Method Not Allowed',
312
        406 => 'Not Acceptable',
313
        407 => 'Proxy Authentication Required',
314
        408 => 'Request Timeout',
315
        409 => 'Conflict',
316
        410 => 'Gone',
317
        411 => 'Length Required',
318
        412 => 'Precondition Failed',
319
        413 => 'Request Entity Too Large',
320
        414 => 'Request-URI Too Long',
321
        415 => 'Unsupported Media Type',
322
        416 => 'Requested Range Not Satisfiable',
323
        417 => 'Expectation Failed',
324
325
        500 => 'Internal Server Error',
326
        501 => 'Not Implemented',
327
        502 => 'Bad Gateway',
328
        503 => 'Service Unavailable',
329
        504 => 'Gateway Timeout',
330
        505 => 'HTTP Version Not Supported'
331
    );
332
333
    if($text == '' && isset($stati[$code])) {
334
        $text = $stati[$code];
335
    }
336
337
    $server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : false;
338
339
    if(substr(php_sapi_name(), 0, 3) == 'cgi' || defined('SIMPLE_TEST')) {
340
        header("Status: {$code} {$text}", true);
341
    } elseif($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0') {
342
        header($server_protocol." {$code} {$text}", true, $code);
343
    } else {
344
        header("HTTP/1.1 {$code} {$text}", true, $code);
345
    }
346
}
347