Completed
Push — master ( f28230...054587 )
by Michael
03:22
created

Timthumb::realpath()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 2
b 0
f 0
nc 4
nop 1
dl 0
loc 11
rs 9.4285
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 26 and the first side effect is on line 24.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * TimThumb by Ben Gillbanks and Mark Maunder
4
 * Based on work done by Tim McDaniels and Darren Hoyt
5
 * http://code.google.com/p/timthumb/
6
 *
7
 * GNU General Public License, version 2
8
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9
 *
10
 * Examples and documentation available on the project homepage
11
 * http://www.binarymoon.co.uk/projects/timthumb/
12
 *
13
 * $Rev$
14
 */
15
16
/*
17
 * --- TimThumb CONFIGURATION ---
18
 * To edit the configs it is best to create a file called timthumb-config.php
19
 * and define variables you want to customize in there. It will automatically be
20
 * loaded by timthumb. This will save you having to re-edit these variables
21
 * everytime you download a new version
22
*/
23
24
include_once __DIR__ . '/header.php';
25
26
define('VERSION', '2.8.14');                                                                        // Version of this script
27
//Load a config file if it exists. Otherwise, use the values below
28
if (file_exists(__DIR__ . '/timthumb-config.php')) {
29
    require_once('timthumb-config.php');
30
}
31
if (!defined('DEBUG_ON')) {
32
    define('DEBUG_ON', false);
33
}                                // Enable debug logging to web server error log (STDERR)
34
if (!defined('DEBUG_LEVEL')) {
35
    define('DEBUG_LEVEL', 1);
36
}                                // Debug level 1 is less noisy and 3 is the most noisy
37
if (!defined('MEMORY_LIMIT')) {
38
    define('MEMORY_LIMIT', '30M');
39
}                            // Set PHP memory limit
40
if (!defined('BLOCK_EXTERNAL_LEECHERS')) {
41
    define('BLOCK_EXTERNAL_LEECHERS', false);
42
}                // If the image or webshot is being loaded on an external site, display a red "No Hotlinking" gif.
43
if (!defined('DISPLAY_ERROR_MESSAGES')) {
44
    define('DISPLAY_ERROR_MESSAGES', true);
45
}                // Display error messages. Set to false to turn off errors (good for production websites)
46
//Image fetching and caching
47
if (!defined('ALLOW_EXTERNAL')) {
48
    define('ALLOW_EXTERNAL', true);
49
}                        // Allow image fetching from external websites. Will check against ALLOWED_SITES if ALLOW_ALL_EXTERNAL_SITES is false
50
if (!defined('ALLOW_ALL_EXTERNAL_SITES')) {
51
    define('ALLOW_ALL_EXTERNAL_SITES', false);
52
}                // Less secure.
53
if (!defined('FILE_CACHE_ENABLED')) {
54
    define('FILE_CACHE_ENABLED', true);
55
}                    // Should we store resized/modified images on disk to speed things up?
56
if (!defined('FILE_CACHE_TIME_BETWEEN_CLEANS')) {
57
    define('FILE_CACHE_TIME_BETWEEN_CLEANS', 86400);
58
}    // How often the cache is cleaned
59
60
if (!defined('FILE_CACHE_MAX_FILE_AGE')) {
61
    define('FILE_CACHE_MAX_FILE_AGE', 86400);
62
}                // How old does a file have to be to be deleted from the cache
63
if (!defined('FILE_CACHE_SUFFIX')) {
64
    define('FILE_CACHE_SUFFIX', '.timthumb.txt');
65
}            // What to put at the end of all files in the cache directory so we can identify them
66
if (!defined('FILE_CACHE_PREFIX')) {
67
    define('FILE_CACHE_PREFIX', 'timthumb');
68
}                // What to put at the beg of all files in the cache directory so we can identify them
69
if (!defined('FILE_CACHE_DIRECTORY')) {
70
    define('FILE_CACHE_DIRECTORY', '../../cache');
71
} // Directory where images are cached. Left blank it will use the system temporary directory (which is better for security)
72
if (!defined('MAX_FILE_SIZE')) {
73
    define('MAX_FILE_SIZE', 10485760);
74
}                        // 10 Megs is 10485760. This is the max internal or external file size that we'll process.
75
if (!defined('CURL_TIMEOUT')) {
76
    define('CURL_TIMEOUT', 20);
77
}                            // Timeout duration for Curl. This only applies if you have Curl installed and aren't using PHP's default URL fetching mechanism.
78
if (!defined('WAIT_BETWEEN_FETCH_ERRORS')) {
79
    define('WAIT_BETWEEN_FETCH_ERRORS', 3600);
80
}                // Time to wait between errors fetching remote file
81
82
//Browser caching
83
if (!defined('BROWSER_CACHE_MAX_AGE')) {
84
    define('BROWSER_CACHE_MAX_AGE', 864000);
85
}                // Time to cache in the browser
86
if (!defined('BROWSER_CACHE_DISABLE')) {
87
    define('BROWSER_CACHE_DISABLE', false);
88
}                // Use for testing if you want to disable all browser caching
89
90
//Image size and defaults
91
if (!defined('MAX_WIDTH')) {
92
    define('MAX_WIDTH', 1500);
93
}                                // Maximum image width
94
if (!defined('MAX_HEIGHT')) {
95
    define('MAX_HEIGHT', 1500);
96
}                            // Maximum image height
97
if (!defined('NOT_FOUND_IMAGE')) {
98
    define('NOT_FOUND_IMAGE', '');
99
}                            // Image to serve if any 404 occurs
100
if (!defined('ERROR_IMAGE')) {
101
    define('ERROR_IMAGE', '');
102
}                                // Image to serve if an error occurs instead of showing error message
103
if (!defined('PNG_IS_TRANSPARENT')) {
104
    define('PNG_IS_TRANSPARENT', false);
105
}                    // Define if a png image should have a transparent background color. Use False value if you want to display a custom coloured canvas_colour
106
if (!defined('DEFAULT_Q')) {
107
    define('DEFAULT_Q', 90);
108
}                                // Default image quality. Allows overrid in timthumb-config.php
109
if (!defined('DEFAULT_ZC')) {
110
    define('DEFAULT_ZC', 1);
111
}                                // Default zoom/crop setting. Allows overrid in timthumb-config.php
112
if (!defined('DEFAULT_F')) {
113
    define('DEFAULT_F', '');
114
}                                // Default image filters. Allows overrid in timthumb-config.php
115
if (!defined('DEFAULT_S')) {
116
    define('DEFAULT_S', 0);
117
}                                // Default sharpen value. Allows overrid in timthumb-config.php
118
if (!defined('DEFAULT_CC')) {
119
    define('DEFAULT_CC', 'ffffff');
120
}                        // Default canvas colour. Allows overrid in timthumb-config.php
121
if (!defined('DEFAULT_WIDTH')) {
122
    define('DEFAULT_WIDTH', 100);
123
}                            // Default thumbnail width. Allows overrid in timthumb-config.php
124
if (!defined('DEFAULT_HEIGHT')) {
125
    define('DEFAULT_HEIGHT', 100);
126
}                            // Default thumbnail height. Allows overrid in timthumb-config.php
127
128
/**
129
 * Additional Parameters:
130
 * LOCAL_FILE_BASE_DIRECTORY = Override the DOCUMENT_ROOT. This is best used in timthumb-config.php
131
 */
132
133
//Image compression is enabled if either of these point to valid paths
134
135
//These are now disabled by default because the file sizes of PNGs (and GIFs) are much smaller than we used to generate.
136
//They only work for PNGs. GIFs and JPEGs are not affected.
137
if (!defined('OPTIPNG_ENABLED')) {
138
    define('OPTIPNG_ENABLED', false);
139
}
140
if (!defined('OPTIPNG_PATH')) {
141
    define('OPTIPNG_PATH', '/usr/bin/optipng');
142
} //This will run first because it gives better compression than pngcrush.
143
if (!defined('PNGCRUSH_ENABLED')) {
144
    define('PNGCRUSH_ENABLED', false);
145
}
146
if (!defined('PNGCRUSH_PATH')) {
147
    define('PNGCRUSH_PATH', '/usr/bin/pngcrush');
148
} //This will only run if OPTIPNG_PATH is not set or is not valid
149
150
/*
151
    -------====Website Screenshots configuration - BETA====-------
152
153
    If you just want image thumbnails and don't want website screenshots, you can safely leave this as is.
154
155
    If you would like to get website screenshots set up, you will need root access to your own server.
156
157
    Enable ALLOW_ALL_EXTERNAL_SITES so you can fetch any external web page. This is more secure now that we're using a non-web folder for cache.
158
    Enable BLOCK_EXTERNAL_LEECHERS so that your site doesn't generate thumbnails for the whole Internet.
159
160
    Instructions to get website screenshots enabled on Ubuntu Linux:
161
162
    1. Install Xvfb with the following command: sudo apt-get install subversion libqt4-webkit libqt4-dev g++ xvfb
163
    2. Go to a directory where you can download some code
164
    3. Check-out the latest version of CutyCapt with the following command: svn co https://cutycapt.svn.sourceforge.net/svnroot/cutycapt
165
    4. Compile CutyCapt by doing: cd cutycapt/CutyCapt
166
    5. qmake
167
    6. make
168
    7. cp CutyCapt /usr/local/bin/
169
    8. Test it by running: xvfb-run --server-args="-screen 0, 1024x768x24" CutyCapt --url="http://markmaunder.com/" --out=test.png
170
    9. If you get a file called test.png with something in it, it probably worked. Now test the script by accessing it as follows:
171
    10. http://yoursite.com/path/to/timthumb.php?src=http://markmaunder.com/&webshot=1
172
173
    Notes on performance:
174
    The first time a webshot loads, it will take a few seconds.
175
    From then on it uses the regular timthumb caching mechanism with the configurable options above
176
    and loading will be very fast.
177
178
    --ADVANCED USERS ONLY--
179
    If you'd like a slight speedup (about 25%) and you know Linux, you can run the following command which will keep Xvfb running in the background.
180
    nohup Xvfb :100 -ac -nolisten tcp -screen 0, 1024x768x24 > /dev/null 2>&1 &
181
    Then set WEBSHOT_XVFB_RUNNING = true below. This will save your server having to fire off a new Xvfb server and shut it down every time a new shot is generated.
182
    You will need to take responsibility for keeping Xvfb running in case it crashes. (It seems pretty stable)
183
    You will also need to take responsibility for server security if you're running Xvfb as root.
184
185
186
*/
187
if (!defined('WEBSHOT_ENABLED')) {
188
    define('WEBSHOT_ENABLED', false);
189
} //Beta feature. Adding webshot=1 to your query string will cause the script to return a browser screenshot rather than try to fetch an image.
190
if (!defined('WEBSHOT_CUTYCAPT')) {
191
    define('WEBSHOT_CUTYCAPT', '/usr/local/bin/CutyCapt');
192
} //The path to CutyCapt.
193
if (!defined('WEBSHOT_XVFB')) {
194
    define('WEBSHOT_XVFB', '/usr/bin/xvfb-run');
195
}        //The path to the Xvfb server
196
if (!defined('WEBSHOT_SCREEN_X')) {
197
    define('WEBSHOT_SCREEN_X', '1024');
198
}            //1024 works ok
199
if (!defined('WEBSHOT_SCREEN_Y')) {
200
    define('WEBSHOT_SCREEN_Y', '768');
201
}            //768 works ok
202
if (!defined('WEBSHOT_COLOR_DEPTH')) {
203
    define('WEBSHOT_COLOR_DEPTH', '24');
204
}            //I haven't tested anything besides 24
205
if (!defined('WEBSHOT_IMAGE_FORMAT')) {
206
    define('WEBSHOT_IMAGE_FORMAT', 'png');
207
}            //png is about 2.5 times the size of jpg but is a LOT better quality
208
if (!defined('WEBSHOT_TIMEOUT')) {
209
    define('WEBSHOT_TIMEOUT', '20');
210
}            //Seconds to wait for a webshot
211
if (!defined('WEBSHOT_USER_AGENT')) {
212
    define('WEBSHOT_USER_AGENT', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18');
213
} //I hate to do this, but a non-browser robot user agent might not show what humans see. So we pretend to be Firefox
214
if (!defined('WEBSHOT_JAVASCRIPT_ON')) {
215
    define('WEBSHOT_JAVASCRIPT_ON', true);
216
}            //Setting to false might give you a slight speedup and block ads. But it could cause other issues.
217
if (!defined('WEBSHOT_JAVA_ON')) {
218
    define('WEBSHOT_JAVA_ON', false);
219
}            //Have only tested this as fase
220
if (!defined('WEBSHOT_PLUGINS_ON')) {
221
    define('WEBSHOT_PLUGINS_ON', true);
222
}            //Enable flash and other plugins
223
if (!defined('WEBSHOT_PROXY')) {
224
    define('WEBSHOT_PROXY', '');
225
}                //In case you're behind a proxy server.
226
if (!defined('WEBSHOT_XVFB_RUNNING')) {
227
    define('WEBSHOT_XVFB_RUNNING', false);
228
}            //ADVANCED: Enable this if you've got Xvfb running in the background.
229
230
// If ALLOW_EXTERNAL is true and ALLOW_ALL_EXTERNAL_SITES is false, then external images will only be fetched from these domains and their subdomains.
231
if (!isset($ALLOWED_SITES)) {
232
    $ALLOWED_SITES = array(
233
        'flickr.com',
234
        'staticflickr.com',
235
        'picasa.com',
236
        'img.youtube.com',
237
        'upload.wikimedia.org',
238
        'photobucket.com',
239
        'imgur.com',
240
        'imageshack.us',
241
        'tinypic.com');
242
}
243
// -------------------------------------------------------------
244
// -------------- STOP EDITING CONFIGURATION HERE --------------
245
// -------------------------------------------------------------
246
247
Timthumb::start();
248
249
/**
250
 * Class timthumb
251
 */
252
class Timthumb
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
253
{
254
    protected        $src                      = '';
255
    protected        $is404                    = false;
256
    protected        $docRoot                  = '';
257
    protected        $lastURLError             = false;
258
    protected        $localImage               = '';
259
    protected        $localImageMTime          = 0.0;
260
    protected        $url                      = false;
261
    protected        $myHost                   = '';
262
    protected        $isURL                    = false;
263
    protected        $cachefile                = '';
264
    protected        $errors                   = array();
265
    protected        $toDeletes                = array();
266
    protected        $cacheDirectory           = '';
267
    protected        $startTime                = 0.0;
268
    protected        $lastBenchTime            = 0.0;
269
    protected        $cropTop                  = false;
270
    protected        $salt                     = '';
271
    protected        $fileCacheVersion         = 1; //Generally if timthumb.php is modifed (upgraded) then the salt changes and all cache files are recreated. This is a backup mechanism to force regen.
272
    protected        $filePrependSecurityBlock = "<?php exit('Execution denied!'); //"; //Designed to have three letter mime type, space, question mark and greater than symbol appended. 6 bytes total.
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $filePrependSecurityBlock exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
273
    protected static $curlDataWritten          = 0;
274
    protected static $curlFH                   = false;
275
276
    public static function start()
277
    {
278
        $tim = new timthumb();
279
        $tim->handleErrors();
280
        $tim->securityChecks();
281
        if ($tim->tryBrowserCache()) {
282
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method start() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
283
        }
284
        $tim->handleErrors();
285
        if (FILE_CACHE_ENABLED && $tim->tryServerCache()) {
286
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method start() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
287
        }
288
        $tim->handleErrors();
289
        $tim->run();
290
        $tim->handleErrors();
291
        exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method start() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
292
    }
293
294
    /**
295
     *
296
     */
297
    public function __construct()
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
298
    {
299
        global $ALLOWED_SITES;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
300
        $this->startTime = microtime(true);
301
        date_default_timezone_set('UTC');
302
        $this->debug(1, 'Starting new request from ' . $this->getIP() . ' to ' . XoopsRequest::getString('REQUEST_URI', '', 'SERVER'));
303
        $this->calcDocRoot();
304
        //On windows systems I'm assuming fileinode returns an empty string or a number that doesn't change. Check this.
305
        $this->salt = @filemtime(__FILE__) . '-' . @fileinode(__FILE__);
306
        $this->debug(3, 'Salt is: ' . $this->salt);
307
        if (FILE_CACHE_DIRECTORY) {
308
            if (!is_dir(FILE_CACHE_DIRECTORY)) {
309
                @mkdir(FILE_CACHE_DIRECTORY);
0 ignored issues
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...
310
                if (!is_dir(FILE_CACHE_DIRECTORY)) {
311
                    $this->error('Could not create the file cache directory.');
312
313
                    return false;
314
                }
315
            }
316
            $this->cacheDirectory = FILE_CACHE_DIRECTORY;
317
            if (!touch($this->cacheDirectory . '/index.html')) {
318
                $this->error('Could not create the index.html file - to fix this create an empty file named index.html file in the cache directory.');
319
            }
320
        } else {
321
            $this->cacheDirectory = sys_get_temp_dir();
322
        }
323
        //Clean the cache before we do anything because we don't want the first visitor after FILE_CACHE_TIME_BETWEEN_CLEANS expires to get a stale image.
324
        $this->cleanCache();
325
326
        $this->myHost = preg_replace('/^www\./i', '', $_SERVER['HTTP_HOST']);
327
        $this->src    = $this->param('src');
328
        $this->url    = parse_url($this->src);
0 ignored issues
show
Documentation Bug introduced by
It seems like parse_url($this->src) can also be of type array<string,string>. However, the property $url is declared as type boolean. 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...
329
        $this->src    = preg_replace('/https?:\/\/(?:www\.)?' . $this->myHost . '/i', '', $this->src);
0 ignored issues
show
Security Code Execution introduced by
'/https?:\\/\\/(?:www\\.... . $this->myHost . '/i' can contain request data and is used in code execution context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
330
331
        if (strlen($this->src) <= 3) {
332
            $this->error('No image specified');
333
334
            return false;
335
        }
336
        if (BLOCK_EXTERNAL_LEECHERS && array_key_exists('HTTP_REFERER', $_SERVER) && (!preg_match('/^https?:\/\/(?:www\.)?' . $this->myHost . '(?:$|\/)/i', $_SERVER['HTTP_REFERER']))) {
337
            // base64 encoded red image that says 'no hotlinkers'
338
            // nothing to worry about! :)
339
            $imgData = base64_decode("R0lGODlhUAAMAIAAAP8AAP///yH5BAAHAP8ALAAAAABQAAwAAAJpjI+py+0Po5y0OgAMjjv01YUZ\nOGplhWXfNa6JCLnWkXplrcBmW+spbwvaVr/cDyg7IoFC2KbYVC2NQ5MQ4ZNao9Ynzjl9ScNYpneb\nDULB3RP6JuPuaGfuuV4fumf8PuvqFyhYtjdoeFgAADs=");
340
            header('Content-Type: image/gif');
341
            header('Content-Length: ' . strlen($imgData));
342
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
343
            header('Pragma: no-cache');
344
            header('Expires: ' . gmdate('D, d M Y H:i:s', time()));
345
            echo $imgData;
346
347
            return false;
348
            exit(0);
0 ignored issues
show
Unused Code introduced by
die(0); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
Coding Style Compatibility introduced by
The method __construct() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
349
        }
350
        if (preg_match('/^https?:\/\/[^\/]+/i', $this->src)) {
351
            $this->debug(2, 'Is a request for an external URL: ' . $this->src);
352
            $this->isURL = true;
353
        } else {
354
            $this->debug(2, 'Is a request for an internal file: ' . $this->src);
355
        }
356
        if ($this->isURL && (!ALLOW_EXTERNAL)) {
357
            $this->error('You are not allowed to fetch images from an external website.');
358
359
            return false;
360
        }
361
        if ($this->isURL) {
362
            if (ALLOW_ALL_EXTERNAL_SITES) {
363
                $this->debug(2, 'Fetching from all external sites is enabled.');
364
            } else {
365
                $this->debug(2, 'Fetching only from selected external sites is enabled.');
366
                $allowed = false;
367
                foreach ($ALLOWED_SITES as $site) {
368
                    if ((strtolower($this->url['host']) === strtolower($site)) || (strtolower(substr($this->url['host'], -strlen($site) - 1)) === strtolower(".$site"))) {
369
                        $this->debug(3, "URL hostname {$this->url['host']} matches $site so allowing.");
370
                        $allowed = true;
371
                    }
372
                }
373
                if (!$allowed) {
374
                    return $this->error("You may not fetch images from that site. To enable this site in timthumb, you can either add it to \$ALLOWED_SITES and set ALLOW_EXTERNAL=true. Or you can set ALLOW_ALL_EXTERNAL_SITES=true, depending on your security needs.");
375
                }
376
            }
377
        }
378
379
        $cachePrefix = ($this->isURL ? '_ext_' : '_int_');
380
        if ($this->isURL) {
381
            $arr = explode('&', $_SERVER ['QUERY_STRING']);
382
            asort($arr);
383
            $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . implode('', $arr) . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
384
        } else {
385
            $this->localImage = $this->getLocalImagePath($this->src);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getLocalImagePath($this->src) can also be of type boolean. However, the property $localImage is declared as type string. 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...
386
            if (!$this->localImage) {
387
                $this->debug(1, "Could not find the local image: {$this->localImage}");
388
                $this->error('Could not find the internal image you specified.');
389
                $this->set404();
390
391
                return false;
392
            }
393
            $this->debug(1, "Local image path is {$this->localImage}");
394
            $this->localImageMTime = @filemtime($this->localImage);
395
            //We include the mtime of the local file in case in changes on disk.
396
            $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . $this->localImageMTime . $_SERVER ['QUERY_STRING'] . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
397
        }
398
        $this->debug(2, 'Cache file is: ' . $this->cachefile);
399
400
        return true;
401
    }
402
403
    public function __destruct()
404
    {
405
        foreach ($this->toDeletes as $del) {
406
            $this->debug(2, "Deleting temp file $del");
407
            @unlink($del);
0 ignored issues
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...
408
        }
409
    }
410
411
    /**
412
     * @return bool
413
     */
414
    public function run()
415
    {
416
        if ($this->isURL) {
417
            if (!ALLOW_EXTERNAL) {
418
                $this->debug(1, 'Got a request for an external image but ALLOW_EXTERNAL is disabled so returning error msg.');
419
                $this->error('You are not allowed to fetch images from an external website.');
420
421
                return false;
422
            }
423
            $this->debug(3, 'Got request for external image. Starting serveExternalImage.');
424
            if ($this->param('webshot')) {
425
                if (WEBSHOT_ENABLED) {
426
                    $this->debug(3, 'webshot param is set, so we\'re going to take a webshot.');
427
                    $this->serveWebshot();
428
                } else {
429
                    $this->error('You added the webshot parameter but webshots are disabled on this server. You need to set WEBSHOT_ENABLED == true to enable webshots.');
430
                }
431
            } else {
432
                $this->debug(3, 'webshot is NOT set so we\'re going to try to fetch a regular image.');
433
                $this->serveExternalImage();
434
            }
435
        } else {
436
            $this->debug(3, 'Got request for internal image. Starting serveInternalImage()');
437
            $this->serveInternalImage();
438
        }
439
440
        return true;
441
    }
442
443
    /**
444
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be null|false?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
445
     */
446
    protected function handleErrors()
447
    {
448
        if ($this->haveErrors()) {
449
            if (NOT_FOUND_IMAGE && $this->is404()) {
450
                if ($this->serveImg(NOT_FOUND_IMAGE)) {
451
                    exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method handleErrors() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
452
                } else {
453
                    $this->error('Additionally, the 404 image that is configured could not be found or there was an error serving it.');
454
                }
455
            }
456
            if (ERROR_IMAGE) {
457
                if ($this->serveImg(ERROR_IMAGE)) {
458
                    exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method handleErrors() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
459
                } else {
460
                    $this->error('Additionally, the error image that is configured could not be found or there was an error serving it.');
461
                }
462
            }
463
            $this->serveErrors();
464
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method handleErrors() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
465
        }
466
467
        return false;
468
    }
469
470
    /**
471
     * @return bool
472
     */
473
    protected function tryBrowserCache()
0 ignored issues
show
Coding Style introduced by
tryBrowserCache uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
474
    {
475
        if (BROWSER_CACHE_DISABLE) {
476
            $this->debug(3, 'Browser caching is disabled');
477
478
            return false;
479
        }
480
        if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
481
            $this->debug(3, 'Got a conditional get');
482
            $mtime = false;
483
            //We've already checked if the real file exists in the constructor
484
            if (!is_file($this->cachefile)) {
485
                //If we don't have something cached, regenerate the cached image.
486
                return false;
487
            }
488
            if ($this->localImageMTime) {
489
                $mtime = $this->localImageMTime;
490
                $this->debug(3, "Local real file's modification time is $mtime");
491
            } elseif (is_file($this->cachefile)) { //If it's not a local request then use the mtime of the cached file to determine the 304
492
                $mtime = @filemtime($this->cachefile);
493
                $this->debug(3, "Cached file's modification time is $mtime");
494
            }
495
            if (!$mtime) {
496
                return false;
497
            }
498
499
            $iftime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
500
            $this->debug(3, "The conditional get's if-modified-since unixtime is $iftime");
501
            if ($iftime < 1) {
502
                $this->debug(3, 'Got an invalid conditional get modified since time. Returning false.');
503
504
                return false;
505
            }
506
            if ($iftime < $mtime) { //Real file or cache file has been modified since last request, so force refetch.
507
                $this->debug(3, 'File has been modified since last fetch.');
508
509
                return false;
510
            } else { //Otherwise serve a 304
511
                $this->debug(3, 'File has not been modified since last get, so serving a 304.');
512
                header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
513
                $this->debug(1, 'Returning 304 not modified');
514
515
                return true;
516
            }
517
        }
518
519
        return false;
520
    }
521
522
    /**
523
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
524
     */
525
    protected function tryServerCache()
526
    {
527
        $this->debug(3, 'Trying server cache');
528
        if (file_exists($this->cachefile)) {
529
            $this->debug(3, "Cachefile {$this->cachefile} exists");
530
            if ($this->isURL) {
531
                $this->debug(3, 'This is an external request, so checking if the cachefile is empty which means the request failed previously.');
532
                if (filesize($this->cachefile) < 1) {
533
                    $this->debug(3, 'Found an empty cachefile indicating a failed earlier request. Checking how old it is.');
534
                    //Fetching error occured previously
535
                    if (time() - @filemtime($this->cachefile) > WAIT_BETWEEN_FETCH_ERRORS) {
536
                        $this->debug(3, 'File is older than ' . WAIT_BETWEEN_FETCH_ERRORS . ' seconds. Deleting and returning false so app can try and load file.');
537
                        @unlink($this->cachefile);
0 ignored issues
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...
538
539
                        return false; //to indicate we didn't serve from cache and app should try and load
540
                    } else {
541
                        $this->debug(3, 'Empty cachefile is still fresh so returning message saying we had an error fetching this image from remote host.');
542
                        $this->set404();
543
                        $this->error('An error occured fetching image.');
544
545
                        return false;
546
                    }
547
                }
548
            } else {
549
                $this->debug(3, "Trying to serve cachefile {$this->cachefile}");
550
            }
551
            if ($this->serveCacheFile()) {
552
                $this->debug(3, "Succesfully served cachefile {$this->cachefile}");
553
554
                return true;
555
            } else {
556
                $this->debug(3, "Failed to serve cachefile {$this->cachefile} - Deleting it from cache.");
557
                //Image serving failed. We can't retry at this point, but lets remove it from cache so the next request recreates it
558
                @unlink($this->cachefile);
0 ignored issues
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...
559
560
                return true;
561
            }
562
        }
563
564
        return null;
565
    }
566
567
    /**
568
     * @param $err
569
     *
570
     * @return bool
571
     */
572
    protected function error($err)
573
    {
574
        $this->debug(3, "Adding error message: $err");
575
        $this->errors[] = $err;
576
577
        return false;
578
    }
579
580
    /**
581
     * @return bool
582
     */
583
    protected function haveErrors()
584
    {
585
        return count($this->errors) > 0;
586
    }
587
588
    protected function serveErrors()
0 ignored issues
show
Coding Style introduced by
serveErrors uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
589
    {
590
        header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
591
        if (!DISPLAY_ERROR_MESSAGES) {
592
            return;
593
        }
594
        $html = '<ul>';
595
        foreach ($this->errors as $err) {
596
            $html .= '<li>' . htmlentities($err) . '</li>';
597
        }
598
        $html .= '</ul>';
599
        echo '<h1>A TimThumb error has occured</h1>The following error(s) occured:<br>' . $html . '<br>';
600
        echo '<br>Query String : ' . htmlentities($_SERVER['QUERY_STRING'], ENT_QUOTES);
601
        echo '<br>TimThumb version : ' . VERSION . '</pre>';
602
    }
603
604
    /**
605
     * @return bool
606
     */
607
    protected function serveInternalImage()
608
    {
609
        $this->debug(3, "Local image path is $this->localImage");
610
        if (!$this->localImage) {
611
            $this->sanityFail('localImage not set after verifying it earlier in the code.');
612
613
            return false;
614
        }
615
        $fileSize = filesize($this->localImage);
616
        if ($fileSize > MAX_FILE_SIZE) {
617
            $this->error('The file you specified is greater than the maximum allowed file size.');
618
619
            return false;
620
        }
621
        if ($fileSize <= 0) {
622
            $this->error('The file you specified is <= 0 bytes.');
623
624
            return false;
625
        }
626
        $this->debug(3, 'Calling processImageAndWriteToCache() for local image.');
627
        if ($this->processImageAndWriteToCache($this->localImage)) {
628
            $this->serveCacheFile();
629
630
            return true;
631
        } else {
632
            return false;
633
        }
634
    }
635
636
    /**
637
     * @return bool|void
638
     */
639
    protected function cleanCache()
640
    {
641
        if (FILE_CACHE_TIME_BETWEEN_CLEANS < 0) {
642
            return null;
643
        }
644
        $this->debug(3, 'cleanCache() called');
645
        $lastCleanFile = $this->cacheDirectory . '/timthumb_cacheLastCleanTime.touch';
646
647
        //If this is a new timthumb installation we need to create the file
648
        if (!is_file($lastCleanFile)) {
649
            $this->debug(1, "File tracking last clean doesn't exist. Creating $lastCleanFile");
650
            if (!touch($lastCleanFile)) {
651
                $this->error('Could not create cache clean timestamp file.');
652
            }
653
654
            return null;
655
        }
656
        if (@filemtime($lastCleanFile) < (time() - FILE_CACHE_TIME_BETWEEN_CLEANS)) { //Cache was last cleaned more than 1 day ago
657
            $this->debug(1, 'Cache was last cleaned more than ' . FILE_CACHE_TIME_BETWEEN_CLEANS . ' seconds ago. Cleaning now.');
658
            // Very slight race condition here, but worst case we'll have 2 or 3 servers cleaning the cache simultaneously once a day.
659
            if (!touch($lastCleanFile)) {
660
                $this->error('Could not create cache clean timestamp file.');
661
            }
662
            $files = glob($this->cacheDirectory . '/*' . FILE_CACHE_SUFFIX);
663
            if ($files) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files 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...
664
                $timeAgo = time() - FILE_CACHE_MAX_FILE_AGE;
665
                foreach ($files as $file) {
666
                    if (@filemtime($file) < $timeAgo) {
667
                        $this->debug(3, "Deleting cache file $file older than max age: " . FILE_CACHE_MAX_FILE_AGE . ' seconds');
668
                        @unlink($file);
0 ignored issues
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...
669
                    }
670
                }
671
            }
672
673
            return true;
674
        } else {
675
            $this->debug(3, 'Cache was cleaned less than ' . FILE_CACHE_TIME_BETWEEN_CLEANS . ' seconds ago so no cleaning needed.');
676
        }
677
678
        return false;
679
    }
680
681
    /**
682
     * @param $localImage
683
     *
684
     * @return bool
685
     */
686
    protected function processImageAndWriteToCache($localImage)
687
    {
688
        $sData    = getimagesize($localImage);
689
        $origType = $sData[2];
690
        $mimeType = $sData['mime'];
691
692
        $this->debug(3, "Mime type of image is $mimeType");
693
        if (!preg_match('/^image\/(?:gif|jpg|jpeg|png)$/i', $mimeType)) {
694
            return $this->error('The image being resized is not a valid gif, jpg or png.');
695
        }
696
697
        if (!function_exists('imagecreatetruecolor')) {
698
            return $this->error('GD Library Error: imagecreatetruecolor does not exist - please contact your webhost and ask them to install the GD library');
699
        }
700
701
        if (defined('IMG_FILTER_NEGATE') && function_exists('imagefilter')) {
702
            $imageFilters = array(
703
                1  => array(IMG_FILTER_NEGATE, 0),
704
                2  => array(IMG_FILTER_GRAYSCALE, 0),
705
                3  => array(IMG_FILTER_BRIGHTNESS, 1),
706
                4  => array(IMG_FILTER_CONTRAST, 1),
707
                5  => array(IMG_FILTER_COLORIZE, 4),
708
                6  => array(IMG_FILTER_EDGEDETECT, 0),
709
                7  => array(IMG_FILTER_EMBOSS, 0),
710
                8  => array(IMG_FILTER_GAUSSIAN_BLUR, 0),
711
                9  => array(IMG_FILTER_SELECTIVE_BLUR, 0),
712
                10 => array(IMG_FILTER_MEAN_REMOVAL, 0),
713
                11 => array(IMG_FILTER_SMOOTH, 0));
714
        }
715
716
        // get standard input properties
717
        $newWidth     = (int)abs($this->param('w', 0));
718
        $newHeight    = (int)abs($this->param('h', 0));
719
        $zoom_crop    = (int)$this->param('zc', DEFAULT_ZC);
720
        $quality      = (int)abs($this->param('q', DEFAULT_Q));
721
        $align        = $this->cropTop ? 't' : $this->param('a', 'c');
722
        $filters      = $this->param('f', DEFAULT_F);
723
        $sharpen      = (bool)$this->param('s', DEFAULT_S);
724
        $canvas_color = $this->param('cc', DEFAULT_CC);
725
        $canvas_trans = (bool)$this->param('ct', '1');
726
727
        // set default width and height if neither are set already
728
        if ($newWidth == 0 && $newHeight == 0) {
729
            $newWidth  = (int)DEFAULT_WIDTH;
730
            $newHeight = (int)DEFAULT_HEIGHT;
731
        }
732
733
        // ensure size limits can not be abused
734
        $newWidth  = min($newWidth, MAX_WIDTH);
735
        $newHeight = min($newHeight, MAX_HEIGHT);
736
737
        // set memory limit to be able to have enough space to resize larger images
738
        $this->setMemoryLimit();
739
740
        // open the existing image
741
        $image = $this->openImage($mimeType, $localImage);
742
        if ($image === false) {
743
            return $this->error('Unable to open image.');
744
        }
745
746
        // Get original width and height
747
        $width    = imagesx($image);
748
        $height   = imagesy($image);
749
        $origin_x = 0;
750
        $origin_y = 0;
751
752
        // generate new w/h if not provided
753
        if ($newWidth && !$newHeight) {
754
            $newHeight = floor($height * ($newWidth / $width));
755
        } elseif ($newHeight && !$newWidth) {
756
            $newWidth = floor($width * ($newHeight / $height));
757
        }
758
759
        // scale down and add borders
760
        if ($zoom_crop == 3) {
761
            $final_height = $height * ($newWidth / $width);
762
763
            if ($final_height > $newHeight) {
764
                $newWidth = $width * ($newHeight / $height);
765
            } else {
766
                $newHeight = $final_height;
767
            }
768
        }
769
770
        // create a new true color image
771
        $canvas = imagecreatetruecolor($newWidth, $newHeight);
772
        imagealphablending($canvas, false);
773
774
        if (strlen($canvas_color) == 3) { //if is 3-char notation, edit string into 6-char notation
775
            $canvas_color = str_repeat(substr($canvas_color, 0, 1), 2) . str_repeat(substr($canvas_color, 1, 1), 2) . str_repeat(substr($canvas_color, 2, 1), 2);
776
        } elseif (strlen($canvas_color) != 6) {
777
            $canvas_color = DEFAULT_CC; // on error return default canvas color
778
        }
779
780
        $canvas_color_R = hexdec(substr($canvas_color, 0, 2));
781
        $canvas_color_G = hexdec(substr($canvas_color, 2, 2));
782
        $canvas_color_B = hexdec(substr($canvas_color, 4, 2));
783
784
        // Create a new transparent color for image
785
        // If is a png and PNG_IS_TRANSPARENT is false then remove the alpha transparency
786
        // (and if is set a canvas color show it in the background)
787
        if (!PNG_IS_TRANSPARENT && $canvas_trans && preg_match('/^image\/png$/i', $mimeType)) {
788
            $color = imagecolorallocatealpha($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 127);
789
        } else {
790
            $color = imagecolorallocatealpha($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 0);
791
        }
792
793
        // Completely fill the background of the new image with allocated color.
794
        imagefill($canvas, 0, 0, $color);
795
        // scale down and add borders
796
        if ($zoom_crop == 2) {
797
            $final_height = $height * ($newWidth / $width);
798
            if ($final_height > $newHeight) {
799
                $origin_x = $newWidth / 2;
800
                $newWidth = $width * ($newHeight / $height);
801
                $origin_x = round($origin_x - ($newWidth / 2));
802
            } else {
803
                $origin_y  = $newHeight / 2;
804
                $newHeight = $final_height;
805
                $origin_y  = round($origin_y - ($newHeight / 2));
806
            }
807
        }
808
809
        // Restore transparency blending
810
        imagesavealpha($canvas, true);
811
812
        if ($zoom_crop > 0) {
813
            $src_x = $src_y = 0;
814
            $src_w = $width;
815
            $src_h = $height;
816
817
            $cmp_x = $width / $newWidth;
818
            $cmp_y = $height / $newHeight;
819
820
            // calculate x or y coordinate and width or height of source
821
            if ($cmp_x > $cmp_y) {
822
                $src_w = round($width / $cmp_x * $cmp_y);
823
                $src_x = round(($width - ($width / $cmp_x * $cmp_y)) / 2);
824
            } elseif ($cmp_y > $cmp_x) {
825
                $src_h = round($height / $cmp_y * $cmp_x);
826
                $src_y = round(($height - ($height / $cmp_y * $cmp_x)) / 2);
827
            }
828
829
            // positional cropping!
830
            if ($align) {
831
                if (strpos($align, 't') !== false) {
832
                    $src_y = 0;
833
                }
834
                if (strpos($align, 'b') !== false) {
835
                    $src_y = $height - $src_h;
836
                }
837
                if (strpos($align, 'l') !== false) {
838
                    $src_x = 0;
839
                }
840
                if (strpos($align, 'r') !== false) {
841
                    $src_x = $width - $src_w;
842
                }
843
            }
844
845
            imagecopyresampled($canvas, $image, $origin_x, $origin_y, $src_x, $src_y, $newWidth, $newHeight, $src_w, $src_h);
846
        } else {
847
            // copy and resize part of an image with resampling
848
            imagecopyresampled($canvas, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
849
        }
850
851
        if (defined('IMG_FILTER_NEGATE') && $filters != '' && function_exists('imagefilter')) {
852
            // apply filters to image
853
            $filterList = explode('|', $filters);
854
            foreach ($filterList as $fl) {
855
                $filterSettings = explode(',', $fl);
856
                if (isset($imageFilters[$filterSettings[0]])) {
857
                    for ($i = 0; $i < 4; ++$i) {
858
                        if (!isset($filterSettings[$i])) {
859
                            $filterSettings[$i] = null;
860
                        } else {
861
                            $filterSettings[$i] = (int)$filterSettings[$i];
862
                        }
863
                    }
864
865
                    switch ($imageFilters[$filterSettings[0]][1]) {
866
                        case 1:
867
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1]);
868
                            break;
869
870 View Code Duplication
                        case 2:
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...
871
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2]);
872
                            break;
873
874 View Code Duplication
                        case 3:
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...
875
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3]);
876
                            break;
877
878 View Code Duplication
                        case 4:
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...
879
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3], $filterSettings[4]);
880
                            break;
881
882
                        default:
883
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0]);
884
                            break;
885
                    }
886
                }
887
            }
888
        }
889
890
        // sharpen image
891
        if ($sharpen && function_exists('imageconvolution')) {
892
            $sharpenMatrix = array(
893
                array(-1, -1, -1),
894
                array(-1, 16, -1),
895
                array(-1, -1, -1));
896
897
            $divisor = 8;
898
            $offset  = 0;
899
900
            imageconvolution($canvas, $sharpenMatrix, $divisor, $offset);
901
        }
902
        //Straight from Wordpress core code. Reduces filesize by up to 70% for PNG's
903
        if ((IMAGETYPE_PNG == $origType || IMAGETYPE_GIF == $origType) && function_exists('imageistruecolor') && !imageistruecolor($image) && imagecolortransparent($image) > 0) {
904
            imagetruecolortopalette($canvas, false, imagecolorstotal($image));
905
        }
906
907
        $imgType  = '';
0 ignored issues
show
Unused Code introduced by
$imgType is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
908
        $tempfile = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
909
        if (preg_match('/^image\/(?:jpg|jpeg)$/i', $mimeType)) {
910
            $imgType = 'jpg';
911
            imagejpeg($canvas, $tempfile, $quality);
912
        } elseif (preg_match('/^image\/png$/i', $mimeType)) {
913
            $imgType = 'png';
914
            imagepng($canvas, $tempfile, floor($quality * 0.09));
915
        } elseif (preg_match('/^image\/gif$/i', $mimeType)) {
916
            $imgType = 'gif';
917
            imagegif($canvas, $tempfile);
918
        } else {
919
            return $this->sanityFail('Could not match mime type after verifying it previously.');
920
        }
921
922
        if ($imgType === 'png' && OPTIPNG_ENABLED && OPTIPNG_PATH && @is_file(OPTIPNG_PATH)) {
923
            $exec = OPTIPNG_PATH;
924
            $this->debug(3, "optipng'ing $tempfile");
925
            $presize = filesize($tempfile);
926
            $out     = `$exec -o1 $tempfile`; //you can use up to -o7 but it really slows things down
0 ignored issues
show
Unused Code introduced by
$out is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
927
            clearstatcache();
928
            $aftersize = filesize($tempfile);
929
            $sizeDrop  = $presize - $aftersize;
930
            if ($sizeDrop > 0) {
931
                $this->debug(1, "optipng reduced size by $sizeDrop");
932
            } elseif ($sizeDrop < 0) {
933
                $this->debug(1, "optipng increased size! Difference was: $sizeDrop");
934
            } else {
935
                $this->debug(1, 'optipng did not change image size.');
936
            }
937
        } elseif ($imgType === 'png' && PNGCRUSH_ENABLED && PNGCRUSH_PATH && @is_file(PNGCRUSH_PATH)) {
938
            $exec      = PNGCRUSH_PATH;
939
            $tempfile2 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
940
            $this->debug(3, "pngcrush'ing $tempfile to $tempfile2");
941
            $out   = `$exec $tempfile $tempfile2`;
942
            $todel = '';
0 ignored issues
show
Unused Code introduced by
$todel is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
943
            if (is_file($tempfile2)) {
944
                $sizeDrop = filesize($tempfile) - filesize($tempfile2);
945
                if ($sizeDrop > 0) {
946
                    $this->debug(1, "pngcrush was succesful and gave a $sizeDrop byte size reduction");
947
                    $todel    = $tempfile;
948
                    $tempfile = $tempfile2;
949
                } else {
950
                    $this->debug(1, "pngcrush did not reduce file size. Difference was $sizeDrop bytes.");
951
                    $todel = $tempfile2;
952
                }
953
            } else {
954
                $this->debug(3, "pngcrush failed with output: $out");
955
                $todel = $tempfile2;
956
            }
957
            @unlink($todel);
0 ignored issues
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...
958
        }
959
960
        $this->debug(3, 'Rewriting image with security header.');
961
        $tempfile4 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
962
        $context   = stream_context_create();
963
        $fp        = fopen($tempfile, 'r', 0, $context);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
964
        file_put_contents($tempfile4, $this->filePrependSecurityBlock . $imgType . ' ?' . '>'); //6 extra bytes, first 3 being image type
965
        file_put_contents($tempfile4, $fp, FILE_APPEND);
966
        fclose($fp);
967
        @unlink($tempfile);
0 ignored issues
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...
968
        $this->debug(3, 'Locking and replacing cache file.');
969
        $lockFile = $this->cachefile . '.lock';
970
        $fh       = fopen($lockFile, 'w');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fh. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
971
        if (!$fh) {
972
            return $this->error('Could not open the lockfile for writing an image.');
973
        }
974
        if (flock($fh, LOCK_EX)) {
975
            @unlink($this->cachefile); //rename generally overwrites, but doing this in case of platform specific quirks. File might not exist yet.
0 ignored issues
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...
976
            rename($tempfile4, $this->cachefile);
977
            flock($fh, LOCK_UN);
978
            fclose($fh);
979
            @unlink($lockFile);
0 ignored issues
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...
980
        } else {
981
            fclose($fh);
982
            @unlink($lockFile);
0 ignored issues
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...
983
            @unlink($tempfile4);
0 ignored issues
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...
984
985
            return $this->error('Could not get a lock for writing.');
986
        }
987
        $this->debug(3, 'Done image replace with security header. Cleaning up and running cleanCache()');
988
        imagedestroy($canvas);
989
        imagedestroy($image);
990
991
        return true;
992
    }
993
994
    protected function calcDocRoot()
0 ignored issues
show
Coding Style introduced by
calcDocRoot uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
995
    {
996
        $docRoot = @$_SERVER['DOCUMENT_ROOT'];
997
        if (defined('LOCAL_FILE_BASE_DIRECTORY')) {
998
            $docRoot = LOCAL_FILE_BASE_DIRECTORY;
999
        }
1000 View Code Duplication
        if (!isset($docRoot)) {
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...
1001
            $this->debug(3, 'DOCUMENT_ROOT is not set. This is probably windows. Starting search 1.');
1002
            if (isset($_SERVER['SCRIPT_FILENAME'])) {
1003
                $docRoot = str_replace('\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen($_SERVER['PHP_SELF'])));
1004
                $this->debug(3, "Generated docRoot using SCRIPT_FILENAME and PHP_SELF as: $docRoot");
1005
            }
1006
        }
1007 View Code Duplication
        if (!isset($docRoot)) {
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...
1008
            $this->debug(3, 'DOCUMENT_ROOT still is not set. Starting search 2.');
1009
            if (isset($_SERVER['PATH_TRANSLATED'])) {
1010
                $docRoot = str_replace('\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - strlen($_SERVER['PHP_SELF'])));
1011
                $this->debug(3, "Generated docRoot using PATH_TRANSLATED and PHP_SELF as: $docRoot");
1012
            }
1013
        }
1014
        if ($docRoot && $_SERVER['DOCUMENT_ROOT'] !== '/') {
1015
            $docRoot = preg_replace('/\/$/', '', $docRoot);
1016
        }
1017
        $this->debug(3, 'Doc root is: ' . $docRoot);
1018
        $this->docRoot = $docRoot;
1019
    }
1020
1021
    /**
1022
     * @param $src
1023
     *
1024
     * @return bool|string
1025
     */
1026
    protected function getLocalImagePath($src)
0 ignored issues
show
Coding Style introduced by
getLocalImagePath uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1027
    {
1028
        $src = ltrim($src, '/'); //strip off the leading '/'
1029
        if (!$this->docRoot) {
1030
            $this->debug(3, 'We have no document root set, so as a last resort, lets check if the image is in the current dir and serve that.');
1031
            //We don't support serving images outside the current dir if we don't have a doc root for security reasons.
1032
            $file = preg_replace('/^.*?([^\/\\\\]+)$/', '$1', $src); //strip off any path info and just leave the filename.
1033
            if (is_file($file)) {
1034
                return $this->realpath($file);
1035
            }
1036
1037
            return $this->error("Could not find your website document root and the file specified doesn't exist in timthumbs directory. We don't support serving files outside timthumb's directory without a document root for security reasons.");
1038
        } else {
1039
            if (!is_dir($this->docRoot)) {
1040
                $this->error("Server path does not exist. Ensure variable \$_SERVER['DOCUMENT_ROOT'] is set correctly");
1041
            }
1042
        }
1043
1044
        //Do not go past this point without docRoot set
1045
1046
        //Try src under docRoot
1047
        if (file_exists($this->docRoot . '/' . $src)) {
1048
            $this->debug(3, 'Found file as ' . $this->docRoot . '/' . $src);
1049
            $real = $this->realpath($this->docRoot . '/' . $src);
1050
            if (stripos($real, $this->docRoot) === 0) {
1051
                return $real;
1052
            } else {
1053
                $this->debug(1, 'Security block: The file specified occurs outside the document root.');
1054
                //allow search to continue
1055
            }
1056
        }
1057
        //Check absolute paths and then verify the real path is under doc root
1058
        $absolute = $this->realpath('/' . $src);
1059
        if ($absolute && file_exists($absolute)) { //realpath does file_exists check, so can probably skip the exists check here
1060
            $this->debug(3, "Found absolute path: $absolute");
1061
            if (!$this->docRoot) {
1062
                $this->sanityFail('docRoot not set when checking absolute path.');
1063
            }
1064
            if (stripos($absolute, $this->docRoot) === 0) {
1065
                return $absolute;
1066
            } else {
1067
                $this->debug(1, 'Security block: The file specified occurs outside the document root.');
1068
                //and continue search
1069
            }
1070
        }
1071
1072
        $base = $this->docRoot;
1073
1074
        // account for Windows directory structure
1075
        if (false !== strpos($_SERVER['SCRIPT_FILENAME'], ':')) {
1076
            $subDirectories = explode('\\', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
1077
        } else {
1078
            $subDirectories = explode('/', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
1079
        }
1080
1081
        foreach ($subDirectories as $sub) {
1082
            $base .= $sub . '/';
1083
            $this->debug(3, 'Trying file as: ' . $base . $src);
1084
            if (file_exists($base . $src)) {
1085
                $this->debug(3, 'Found file as: ' . $base . $src);
1086
                $real = $this->realpath($base . $src);
1087
                if (stripos($real, $this->realpath($this->docRoot)) === 0) {
1088
                    return $real;
1089
                } else {
1090
                    $this->debug(1, 'Security block: The file specified occurs outside the document root.');
1091
                    //And continue search
1092
                }
1093
            }
1094
        }
1095
1096
        return false;
1097
    }
1098
1099
    /**
1100
     * @param $path
1101
     *
1102
     * @return string
1103
     */
1104
    protected function realpath($path)
1105
    {
1106
        //try to remove any relative paths
1107
        $removeRelatives = '/\w+\/\.\.\//';
1108
        while (preg_match($removeRelatives, $path)) {
1109
            $path = preg_replace($removeRelatives, '', $path);
1110
        }
1111
        //if any remain use PHP realpath to strip them out, otherwise return $path
1112
        //if using realpath, any symlinks will also be resolved
1113
        return preg_match('#^\.\./|/\.\./#', $path) ? realpath($path) : $path;
1114
    }
1115
1116
    /**
1117
     * @param $name
1118
     */
1119
    protected function toDelete($name)
1120
    {
1121
        $this->debug(3, "Scheduling file $name to delete on destruct.");
1122
        $this->toDeletes[] = $name;
1123
    }
1124
1125
    /**
1126
     * @return bool
1127
     */
1128
    protected function serveWebshot()
1129
    {
1130
        $this->debug(3, 'Starting serveWebshot');
1131
        $instr = 'Please follow the instructions at http://code.google.com/p/timthumb/ to set your server up for taking website screenshots.';
1132
        if (!is_file(WEBSHOT_CUTYCAPT)) {
1133
            return $this->error("CutyCapt is not installed. $instr");
1134
        }
1135
        if (!is_file(WEBSHOT_XVFB)) {
1136
            return $this->error("Xvfb is not installed. $instr");
1137
        }
1138
        $cuty      = WEBSHOT_CUTYCAPT;
1139
        $xv        = WEBSHOT_XVFB;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $xv. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1140
        $screenX   = WEBSHOT_SCREEN_X;
1141
        $screenY   = WEBSHOT_SCREEN_Y;
1142
        $colDepth  = WEBSHOT_COLOR_DEPTH;
1143
        $format    = WEBSHOT_IMAGE_FORMAT;
1144
        $timeout   = WEBSHOT_TIMEOUT * 1000;
1145
        $ua        = WEBSHOT_USER_AGENT;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ua. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1146
        $jsOn      = WEBSHOT_JAVASCRIPT_ON ? 'on' : 'off';
1147
        $javaOn    = WEBSHOT_JAVA_ON ? 'on' : 'off';
1148
        $pluginsOn = WEBSHOT_PLUGINS_ON ? 'on' : 'off';
1149
        $proxy     = WEBSHOT_PROXY ? ' --http-proxy=' . WEBSHOT_PROXY : '';
1150
        $tempfile  = tempnam($this->cacheDirectory, 'timthumb_webshot');
1151
        $url       = $this->src;
1152
        if (!preg_match('/^https?:\/\/[a-zA-Z0-9\.\-]+/i', $url)) {
1153
            return $this->error('Invalid URL supplied.');
1154
        }
1155
        $url = preg_replace('/[^A-Za-z0-9\-\.\_:\/\?\&\+\;\=]+/', '', $url); //RFC 3986 plus ()$ chars to prevent exploit below. Plus the following are also removed: @*!~#[]',
1156
        // 2014 update by Mark Maunder: This exploit: http://cxsecurity.com/issue/WLB-2014060134
1157
        // uses the $(command) shell execution syntax to execute arbitrary shell commands as the web server user.
1158
        // So we're now filtering out the characters: '$', '(' and ')' in the above regex to avoid this.
1159
        // We are also filtering out chars rarely used in URLs but legal accoring to the URL RFC which might be exploitable. These include: @*!~#[]',
1160
        // We're doing this because we're passing this URL to the shell and need to make very sure it's not going to execute arbitrary commands.
1161
        if (WEBSHOT_XVFB_RUNNING) {
1162
            putenv('DISPLAY=:100.0');
1163
            $command = "$cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
1164
        } else {
1165
            $command = "$xv --server-args=\"-screen 0, {$screenX}x{$screenY}x{$colDepth}\" $cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
1166
        }
1167
        $this->debug(3, "Executing command: $command");
1168
        $out = `$command`;
1169
        $this->debug(3, "Received output: $out");
1170
        if (!is_file($tempfile)) {
1171
            $this->set404();
1172
1173
            return $this->error('The command to create a thumbnail failed.');
1174
        }
1175
        $this->cropTop = true;
1176
        if ($this->processImageAndWriteToCache($tempfile)) {
1177
            $this->debug(3, 'Image processed successfully. Serving from cache');
1178
1179
            return $this->serveCacheFile();
1180
        } else {
1181
            return false;
1182
        }
1183
    }
1184
1185
    /**
1186
     * @return bool
1187
     */
1188
    protected function serveExternalImage()
1189
    {
1190
        if (!preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+/i', $this->src)) {
1191
            $this->error('Invalid URL supplied.');
1192
1193
            return false;
1194
        }
1195
        $tempfile = tempnam($this->cacheDirectory, 'timthumb');
1196
        $this->debug(3, "Fetching external image into temporary file $tempfile");
1197
        $this->toDelete($tempfile);
1198
        #fetch file here
1199
        if (!$this->getURL($this->src, $tempfile)) {
1200
            @unlink($this->cachefile);
0 ignored issues
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...
1201
            touch($this->cachefile);
1202
            $this->debug(3, 'Error fetching URL: ' . $this->lastURLError);
1203
            $this->error('Error reading the URL you specified from remote host.' . $this->lastURLError);
1204
1205
            return false;
1206
        }
1207
1208
        $mimeType = $this->getMimeType($tempfile);
1209
        if (!preg_match("/^image\/(?:jpg|jpeg|gif|png)$/i", $mimeType)) {
1210
            $this->debug(3, "Remote file has invalid mime type: $mimeType");
1211
            @unlink($this->cachefile);
0 ignored issues
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...
1212
            touch($this->cachefile);
1213
            $this->error("The remote file is not a valid image. Mimetype = '" . $mimeType . "'" . $tempfile);
1214
1215
            return false;
1216
        }
1217
        if ($this->processImageAndWriteToCache($tempfile)) {
1218
            $this->debug(3, 'Image processed successfully. Serving from cache');
1219
1220
            return $this->serveCacheFile();
1221
        } else {
1222
            return false;
1223
        }
1224
    }
1225
1226
    /**
1227
     * @param $h
1228
     * @param $d
1229
     *
1230
     * @return int
1231
     */
1232
    public static function curlWrite($h, $d)
0 ignored issues
show
Unused Code introduced by
The parameter $h 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...
Comprehensibility introduced by
Avoid variables with short names like $h. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
Comprehensibility introduced by
Avoid variables with short names like $d. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1233
    {
1234
        fwrite(self::$curlFH, $d);
1235
        self::$curlDataWritten += strlen($d);
1236
        if (self::$curlDataWritten > MAX_FILE_SIZE) {
1237
            return 0;
1238
        } else {
1239
            return strlen($d);
1240
        }
1241
    }
1242
1243
    /**
1244
     * @return bool
1245
     */
1246
    protected function serveCacheFile()
1247
    {
1248
        $this->debug(3, "Serving {$this->cachefile}");
1249
        if (!is_file($this->cachefile)) {
1250
            $this->error("serveCacheFile called in timthumb but we couldn't find the cached file.");
1251
1252
            return false;
1253
        }
1254
        $fp = fopen($this->cachefile, 'rb');
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fp. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1255
        if (!$fp) {
1256
            return $this->error('Could not open cachefile.');
1257
        }
1258
        fseek($fp, strlen($this->filePrependSecurityBlock), SEEK_SET);
1259
        $imgType = fread($fp, 3);
1260
        fseek($fp, 3, SEEK_CUR);
1261
        if (ftell($fp) != strlen($this->filePrependSecurityBlock) + 6) {
1262
            @unlink($this->cachefile);
0 ignored issues
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...
1263
1264
            return $this->error('The cached image file seems to be corrupt.');
1265
        }
1266
        $imageDataSize = filesize($this->cachefile) - (strlen($this->filePrependSecurityBlock) + 6);
1267
        $this->sendImageHeaders($imgType, $imageDataSize);
1268
        $bytesSent = @fpassthru($fp);
1269
        fclose($fp);
1270
        if ($bytesSent > 0) {
1271
            return true;
1272
        }
1273
        $content = file_get_contents($this->cachefile);
1274
        if ($content !== false) {
1275
            $content = substr($content, strlen($this->filePrependSecurityBlock) + 6);
1276
            echo $content;
1277
            $this->debug(3, 'Served using file_get_contents and echo');
1278
1279
            return true;
1280
        } else {
1281
            $this->error('Cache file could not be loaded.');
1282
1283
            return false;
1284
        }
1285
    }
1286
1287
    /**
1288
     * @param $mimeType
1289
     * @param $dataSize
1290
     *
1291
     * @return bool
1292
     */
1293
    protected function sendImageHeaders($mimeType, $dataSize)
1294
    {
1295
        if (!preg_match('/^image\//i', $mimeType)) {
1296
            $mimeType = 'image/' . $mimeType;
1297
        }
1298
        if (strtolower($mimeType) === 'image/jpg') {
1299
            $mimeType = 'image/jpeg';
1300
        }
1301
        $gmdate_expires  = gmdate('D, d M Y H:i:s', strtotime('now +10 days')) . ' GMT';
1302
        $gmdate_modified = gmdate('D, d M Y H:i:s') . ' GMT';
1303
        // send content headers then display image
1304
        header('Content-Type: ' . $mimeType);
1305
        header('Accept-Ranges: none'); //Changed this because we don't accept range requests
1306
        header('Last-Modified: ' . $gmdate_modified);
1307
        header('Content-Length: ' . $dataSize);
1308
        if (BROWSER_CACHE_DISABLE) {
1309
            $this->debug(3, 'Browser cache is disabled so setting non-caching headers.');
1310
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1311
            header('Pragma: no-cache');
1312
            header('Expires: ' . gmdate('D, d M Y H:i:s', time()));
1313
        } else {
1314
            $this->debug(3, 'Browser caching is enabled');
1315
            header('Cache-Control: max-age=' . BROWSER_CACHE_MAX_AGE . ', must-revalidate');
1316
            header('Expires: ' . $gmdate_expires);
1317
        }
1318
1319
        return true;
1320
    }
1321
1322
    protected function securityChecks()
1323
    {
1324
    }
1325
1326
    /**
1327
     * @param        $property
1328
     * @param string $default
1329
     *
1330
     * @return string
1331
     */
1332
    protected function param($property, $default = '')
0 ignored issues
show
Coding Style introduced by
param uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1333
    {
1334
        if (isset($_GET[$property])) {
1335
            return XoopsRequest::getString($property, '', 'GET');
1336
        } else {
1337
            return $default;
1338
        }
1339
    }
1340
1341
    /**
1342
     * @param $mimeType
1343
     * @param $src
1344
     *
1345
     * @return resource
0 ignored issues
show
Documentation introduced by
Should the return type not be resource|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1346
     */
1347
    protected function openImage($mimeType, $src)
1348
    {
1349
        $image = '';
1350
        switch ($mimeType) {
1351
            case 'image/jpeg':
1352
                $image = imagecreatefromjpeg($src);
1353
                break;
1354
1355
            case 'image/png':
1356
                $image = imagecreatefrompng($src);
0 ignored issues
show
Security File Exposure introduced by
$src can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
1357
                imagealphablending($image, true);
1358
                imagesavealpha($image, true);
1359
                break;
1360
1361
            case 'image/gif':
1362
                $image = imagecreatefromgif($src);
0 ignored issues
show
Security File Exposure introduced by
$src can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
1363
                break;
1364
1365
            default:
1366
                $this->error('Unrecognised mimeType');
1367
        }
1368
1369
        return $image;
1370
    }
1371
1372
    /**
1373
     * @return string
1374
     */
1375
    protected function getIP()
0 ignored issues
show
Coding Style introduced by
getIP uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1376
    {
1377
        $rem = @$_SERVER['REMOTE_ADDR'];
1378
        $ff  = @$_SERVER['HTTP_X_FORWARDED_FOR'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ff. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1379
        $ci  = @$_SERVER['HTTP_CLIENT_IP'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ci. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1380
        if (preg_match('/^(?:192\.168|172\.16|10\.|127\.)/', $rem)) {
1381
            if ($ff) {
1382
                return $ff;
1383
            }
1384
            if ($ci) {
1385
                return $ci;
1386
            }
1387
1388
            return $rem;
1389
        } else {
1390
            if ($rem) {
1391
                return $rem;
1392
            }
1393
            if ($ff) {
1394
                return $ff;
1395
            }
1396
            if ($ci) {
1397
                return $ci;
1398
            }
1399
1400
            return 'UNKNOWN';
1401
        }
1402
    }
1403
1404
    /**
1405
     * @param $level
1406
     * @param $msg
1407
     */
1408
    protected function debug($level, $msg)
1409
    {
1410
        if (DEBUG_ON && $level <= DEBUG_LEVEL) {
1411
            $execTime = sprintf('%.6f', microtime(true) - $this->startTime);
1412
            $tick     = sprintf('%.6f', 0);
1413
            if ($this->lastBenchTime > 0) {
1414
                $tick = sprintf('%.6f', microtime(true) - $this->lastBenchTime);
1415
            }
1416
            $this->lastBenchTime = microtime(true);
1417
            error_log('TimThumb Debug line ' . __LINE__ . " [$execTime : $tick]: $msg");
1418
        }
1419
    }
1420
1421
    /**
1422
     * @param $msg
1423
     *
1424
     * @return bool
1425
     */
1426
    protected function sanityFail($msg)
1427
    {
1428
        return $this->error("There is a problem in the timthumb code. Message: Please report this error at <a href='http://code.google.com/p/timthumb/issues/list'>timthumb's bug tracking page</a>: $msg");
1429
    }
1430
1431
    /**
1432
     * @param $file
1433
     *
1434
     * @return string
1435
     */
1436
    protected function getMimeType($file)
1437
    {
1438
        $info = getimagesize($file);
1439
        if (is_array($info) && $info['mime']) {
1440
            return $info['mime'];
1441
        }
1442
1443
        return '';
1444
    }
1445
1446
    protected function setMemoryLimit()
1447
    {
1448
        $inimem   = ini_get('memory_limit');
1449
        $inibytes = Timthumb::returnBytes($inimem);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1450
        $ourbytes = Timthumb::returnBytes(MEMORY_LIMIT);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
1451
        if ($inibytes < $ourbytes) {
1452
            ini_set('memory_limit', MEMORY_LIMIT);
1453
            $this->debug(3, "Increased memory from $inimem to " . MEMORY_LIMIT);
1454
        } else {
1455
            $this->debug(3, 'Not adjusting memory size because the current setting is ' . $inimem . ' and our size of ' . MEMORY_LIMIT . ' is smaller.');
1456
        }
1457
    }
1458
1459
    /**
1460
     * @param $size_str
1461
     *
1462
     * @return int
1463
     */
1464
    protected static function returnBytes($size_str)
1465
    {
1466
        switch (substr($size_str, -1)) {
1467
            case 'M':
1468
            case 'm':
1469
                return (int)$size_str * 1048576;
1470
            case 'K':
1471
            case 'k':
1472
                return (int)$size_str * 1024;
1473
            case 'G':
1474
            case 'g':
1475
                return (int)$size_str * 1073741824;
1476
            default:
1477
                return $size_str;
1478
        }
1479
    }
1480
1481
    /**
1482
     * @param $url
1483
     * @param $tempfile
1484
     *
1485
     * @return bool
1486
     */
1487
    protected function getURL($url, $tempfile)
1488
    {
1489
        $this->lastURLError = false;
1490
        $url                = preg_replace('/ /', '%20', $url);
1491
        if (function_exists('curl_init')) {
1492
            $this->debug(3, 'Curl is installed so using it to fetch URL.');
1493
            self::$curlFH = fopen($tempfile, 'w');
0 ignored issues
show
Documentation Bug introduced by
It seems like fopen($tempfile, 'w') of type resource is incompatible with the declared type boolean of property $curlFH.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1494
            if (!self::$curlFH) {
1495
                $this->error("Could not open $tempfile for writing.");
1496
1497
                return false;
1498
            }
1499
            self::$curlDataWritten = 0;
1500
            $this->debug(3, "Fetching url with curl: $url");
1501
            $curl = curl_init($url);
1502
            curl_setopt($curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
1503
            curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30');
1504
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1505
            curl_setopt($curl, CURLOPT_HEADER, 0);
1506
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
1507
            curl_setopt($curl, CURLOPT_WRITEFUNCTION, 'timthumb::curlWrite');
1508
            @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
0 ignored issues
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...
1509
            @curl_setopt($curl, CURLOPT_MAXREDIRS, 10);
0 ignored issues
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...
1510
1511
            $curlResult = curl_exec($curl);
1512
            fclose(self::$curlFH);
1513
            $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1514
            if ($httpStatus == 404) {
1515
                $this->set404();
1516
            }
1517
            if ($httpStatus == 302) {
1518
                $this->error('External Image is Redirecting. Try alternate image url');
1519
1520
                return false;
1521
            }
1522
            if ($curlResult) {
1523
                curl_close($curl);
1524
1525
                return true;
1526
            } else {
1527
                $this->lastURLError = curl_error($curl);
0 ignored issues
show
Documentation Bug introduced by
The property $lastURLError was declared of type boolean, but curl_error($curl) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1528
                curl_close($curl);
1529
1530
                return false;
1531
            }
1532
        } else {
1533
            $img = @file_get_contents($url);
0 ignored issues
show
Security File Exposure introduced by
$url can contain request data and is used in file inclusion context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
1534
            if ($img === false) {
1535
                $err                = error_get_last();
1536
                $this->lastURLError = $err;
0 ignored issues
show
Documentation Bug introduced by
It seems like $err of type array is incompatible with the declared type boolean of property $lastURLError.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1537
                if (is_array($err) && $err['message']) {
1538
                    $this->lastURLError = $err['message'];
1539
                }
1540
                if (false !== strpos($this->lastURLError, '404')) {
1541
                    $this->set404();
1542
                }
1543
1544
                return false;
1545
            }
1546
            if (!file_put_contents($tempfile, $img)) {
1547
                $this->error("Could not write to $tempfile.");
1548
1549
                return false;
1550
            }
1551
1552
            return true;
1553
        }
1554
    }
1555
1556
    /**
1557
     * @param $file
1558
     *
1559
     * @return bool
1560
     */
1561
    protected function serveImg($file)
1562
    {
1563
        $s = getimagesize($file);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $s. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
1564
        if (!($s && $s['mime'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $s 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...
1565
            return false;
1566
        }
1567
        header('Content-Type: ' . $s['mime']);
1568
        header('Content-Length: ' . filesize($file));
1569
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1570
        header('Pragma: no-cache');
1571
        $bytes = @readfile($file);
1572
        if ($bytes > 0) {
1573
            return true;
1574
        }
1575
        $content = @file_get_contents($file);
1576
        if ($content !== false) {
1577
            echo $content;
1578
1579
            return true;
1580
        }
1581
1582
        return false;
1583
    }
1584
1585
    protected function set404()
1586
    {
1587
        $this->is404 = true;
1588
    }
1589
1590
    /**
1591
     * @return bool
1592
     */
1593
    protected function is404()
1594
    {
1595
        return $this->is404;
1596
    }
1597
}
1598