Timthumb::__construct()   F
last analyzed

Complexity

Conditions 23
Paths 212

Size

Total Lines 105
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 23
eloc 73
nc 212
nop 0
dl 0
loc 105
rs 3.1833
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
/**
3
 * TimThumb by Ben Gillbanks and Mark Maunder
4
 * Based on work done by Tim McDaniels and Darren Hoyt
5
 * https://code.google.com/p/timthumb/
6
 *
7
 * GNU General Public License, version 2
8
 * https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9
 *
10
 * Examples and documentation available on the project homepage
11
 * https://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
use Xmf\Request;
1 ignored issue
show
Bug introduced by
This use statement conflicts with another class in this namespace, Request. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
25
26
require_once __DIR__ . '/header.php';
27
28
define('VERSION', '2.8.14');                                                                        // Version of this script
29
//Load a config file if it exists. Otherwise, use the values below
30
if (file_exists(__DIR__ . '/timthumb-config.php')) {
31
    require_once __DIR__ . '/timthumb-config.php';
32
}
33
if (!defined('DEBUG_ON')) {
34
    define('DEBUG_ON', false);
35
}                                // Enable debug logging to web server error log (STDERR)
36
if (!defined('DEBUG_LEVEL')) {
37
    define('DEBUG_LEVEL', 1);
38
}                                // Debug level 1 is less noisy and 3 is the most noisy
39
if (!defined('MEMORY_LIMIT')) {
40
    define('MEMORY_LIMIT', '30M');
41
}                            // Set PHP memory limit
42
if (!defined('BLOCK_EXTERNAL_LEECHERS')) {
43
    define('BLOCK_EXTERNAL_LEECHERS', false);
44
}                // If the image or webshot is being loaded on an external site, display a red "No Hotlinking" gif.
45
if (!defined('DISPLAY_ERROR_MESSAGES')) {
46
    define('DISPLAY_ERROR_MESSAGES', true);
47
}                // Display error messages. Set to false to turn off errors (good for production websites)
48
//Image fetching and caching
49
if (!defined('ALLOW_EXTERNAL')) {
50
    define('ALLOW_EXTERNAL', true);
51
}                        // Allow image fetching from external websites. Will check against ALLOWED_SITES if ALLOW_ALL_EXTERNAL_SITES is false
52
if (!defined('ALLOW_ALL_EXTERNAL_SITES')) {
53
    define('ALLOW_ALL_EXTERNAL_SITES', false);
54
}                // Less secure.
55
if (!defined('FILE_CACHE_ENABLED')) {
56
    define('FILE_CACHE_ENABLED', true);
57
}                    // Should we store resized/modified images on disk to speed things up?
58
if (!defined('FILE_CACHE_TIME_BETWEEN_CLEANS')) {
59
    define('FILE_CACHE_TIME_BETWEEN_CLEANS', 86400);
60
}    // How often the cache is cleaned
61
62
if (!defined('FILE_CACHE_MAX_FILE_AGE')) {
63
    define('FILE_CACHE_MAX_FILE_AGE', 86400);
64
}                // How old does a file have to be to be deleted from the cache
65
if (!defined('FILE_CACHE_SUFFIX')) {
66
    define('FILE_CACHE_SUFFIX', '.timthumb.txt');
67
}            // What to put at the end of all files in the cache directory so we can identify them
68
if (!defined('FILE_CACHE_PREFIX')) {
69
    define('FILE_CACHE_PREFIX', 'timthumb');
70
}                // What to put at the beg of all files in the cache directory so we can identify them
71
if (!defined('FILE_CACHE_DIRECTORY')) {
72
    define('FILE_CACHE_DIRECTORY', '../../cache');
73
} // Directory where images are cached. Left blank it will use the system temporary directory (which is better for security)
74
if (!defined('MAX_FILE_SIZE')) {
75
    define('MAX_FILE_SIZE', 10485760);
76
}                        // 10 Megs is 10485760. This is the max internal or external file size that we'll process.
77
if (!defined('CURL_TIMEOUT')) {
78
    define('CURL_TIMEOUT', 20);
79
}                            // Timeout duration for Curl. This only applies if you have Curl installed and aren't using PHP's default URL fetching mechanism.
80
if (!defined('WAIT_BETWEEN_FETCH_ERRORS')) {
81
    define('WAIT_BETWEEN_FETCH_ERRORS', 3600);
82
}                // Time to wait between errors fetching remote file
83
84
//Browser caching
85
if (!defined('BROWSER_CACHE_MAX_AGE')) {
86
    define('BROWSER_CACHE_MAX_AGE', 864000);
87
}                // Time to cache in the browser
88
if (!defined('BROWSER_CACHE_DISABLE')) {
89
    define('BROWSER_CACHE_DISABLE', false);
90
}                // Use for testing if you want to disable all browser caching
91
92
//Image size and defaults
93
if (!defined('MAX_WIDTH')) {
94
    define('MAX_WIDTH', 1500);
95
}                                // Maximum image width
96
if (!defined('MAX_HEIGHT')) {
97
    define('MAX_HEIGHT', 1500);
98
}                            // Maximum image height
99
if (!defined('NOT_FOUND_IMAGE')) {
100
    define('NOT_FOUND_IMAGE', '');
101
}                            // Image to serve if any 404 occurs
102
if (!defined('ERROR_IMAGE')) {
103
    define('ERROR_IMAGE', '');
104
}                                // Image to serve if an error occurs instead of showing error message
105
if (!defined('PNG_IS_TRANSPARENT')) {
106
    define('PNG_IS_TRANSPARENT', false);
107
}                    // Define if a png image should have a transparent background color. Use False value if you want to display a custom coloured canvas_colour
108
if (!defined('DEFAULT_Q')) {
109
    define('DEFAULT_Q', 90);
110
}                                // Default image quality. Allows overrid in timthumb-config.php
111
if (!defined('DEFAULT_ZC')) {
112
    define('DEFAULT_ZC', 1);
113
}                                // Default zoom/crop setting. Allows overrid in timthumb-config.php
114
if (!defined('DEFAULT_F')) {
115
    define('DEFAULT_F', '');
116
}                                // Default image filters. Allows overrid in timthumb-config.php
117
if (!defined('DEFAULT_S')) {
118
    define('DEFAULT_S', 0);
119
}                                // Default sharpen value. Allows overrid in timthumb-config.php
120
if (!defined('DEFAULT_CC')) {
121
    define('DEFAULT_CC', 'ffffff');
122
}                        // Default canvas colour. Allows overrid in timthumb-config.php
123
if (!defined('DEFAULT_WIDTH')) {
124
    define('DEFAULT_WIDTH', 100);
125
}                            // Default thumbnail width. Allows overrid in timthumb-config.php
126
if (!defined('DEFAULT_HEIGHT')) {
127
    define('DEFAULT_HEIGHT', 100);
128
}                            // Default thumbnail height. Allows overrid in timthumb-config.php
129
130
/**
131
 * Additional Parameters:
132
 * LOCAL_FILE_BASE_DIRECTORY = Override the DOCUMENT_ROOT. This is best used in timthumb-config.php
133
 */
134
135
//Image compression is enabled if either of these point to valid paths
136
137
//These are now disabled by default because the file sizes of PNGs (and GIFs) are much smaller than we used to generate.
138
//They only work for PNGs. GIFs and JPEGs are not affected.
139
if (!defined('OPTIPNG_ENABLED')) {
140
    define('OPTIPNG_ENABLED', false);
141
}
142
if (!defined('OPTIPNG_PATH')) {
143
    define('OPTIPNG_PATH', '/usr/bin/optipng');
144
} //This will run first because it gives better compression than pngcrush.
145
if (!defined('PNGCRUSH_ENABLED')) {
146
    define('PNGCRUSH_ENABLED', false);
147
}
148
if (!defined('PNGCRUSH_PATH')) {
149
    define('PNGCRUSH_PATH', '/usr/bin/pngcrush');
150
} //This will only run if OPTIPNG_PATH is not set or is not valid
151
152
/*
153
    -------====Website Screenshots configuration - BETA====-------
154
155
    If you just want image thumbnails and don't want website screenshots, you can safely leave this as is.
156
157
    If you would like to get website screenshots set up, you will need root access to your own server.
158
159
    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.
160
    Enable BLOCK_EXTERNAL_LEECHERS so that your site doesn't generate thumbnails for the whole Internet.
161
162
    Instructions to get website screenshots enabled on Ubuntu Linux:
163
164
    1. Install Xvfb with the following command: sudo apt-get install subversion libqt4-webkit libqt4-dev g++ xvfb
165
    2. Go to a directory where you can download some code
166
    3. Check-out the latest version of CutyCapt with the following command: svn co https://cutycapt.svn.sourceforge.net/svnroot/cutycapt
167
    4. Compile CutyCapt by doing: cd cutycapt/CutyCapt
168
    5. qmake
169
    6. make
170
    7. cp CutyCapt /usr/local/bin/
171
    8. Test it by running: xvfb-run --server-args="-screen 0, 1024x768x24" CutyCapt --url="https://markmaunder.com/" --out=test.png
172
    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:
173
    10. https://yoursite.com/path/to/timthumb.php?src=https://markmaunder.com/&webshot=1
174
175
    Notes on performance:
176
    The first time a webshot loads, it will take a few seconds.
177
    From then on it uses the regular timthumb caching mechanism with the configurable options above
178
    and loading will be very fast.
179
180
    --ADVANCED USERS ONLY--
181
    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.
182
    nohup Xvfb :100 -ac -nolisten tcp -screen 0, 1024x768x24 > /dev/null 2>&1 &
183
    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.
184
    You will need to take responsibility for keeping Xvfb running in case it crashes. (It seems pretty stable)
185
    You will also need to take responsibility for server security if you're running Xvfb as root.
186
187
188
*/
189
if (!defined('WEBSHOT_ENABLED')) {
190
    define('WEBSHOT_ENABLED', false);
191
} //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.
192
if (!defined('WEBSHOT_CUTYCAPT')) {
193
    define('WEBSHOT_CUTYCAPT', '/usr/local/bin/CutyCapt');
194
} //The path to CutyCapt.
195
if (!defined('WEBSHOT_XVFB')) {
196
    define('WEBSHOT_XVFB', '/usr/bin/xvfb-run');
197
}        //The path to the Xvfb server
198
if (!defined('WEBSHOT_SCREEN_X')) {
199
    define('WEBSHOT_SCREEN_X', '1024');
200
}            //1024 works ok
201
if (!defined('WEBSHOT_SCREEN_Y')) {
202
    define('WEBSHOT_SCREEN_Y', '768');
203
}            //768 works ok
204
if (!defined('WEBSHOT_COLOR_DEPTH')) {
205
    define('WEBSHOT_COLOR_DEPTH', '24');
206
}            //I haven't tested anything besides 24
207
if (!defined('WEBSHOT_IMAGE_FORMAT')) {
208
    define('WEBSHOT_IMAGE_FORMAT', 'png');
209
}            //png is about 2.5 times the size of jpg but is a LOT better quality
210
if (!defined('WEBSHOT_TIMEOUT')) {
211
    define('WEBSHOT_TIMEOUT', '20');
212
}            //Seconds to wait for a webshot
213
if (!defined('WEBSHOT_USER_AGENT')) {
214
    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');
215
} //I hate to do this, but a non-browser robot user agent might not show what humans see. So we pretend to be Firefox
216
if (!defined('WEBSHOT_JAVASCRIPT_ON')) {
217
    define('WEBSHOT_JAVASCRIPT_ON', true);
218
}            //Setting to false might give you a slight speedup and block ads. But it could cause other issues.
219
if (!defined('WEBSHOT_JAVA_ON')) {
220
    define('WEBSHOT_JAVA_ON', false);
221
}            //Have only tested this as fase
222
if (!defined('WEBSHOT_PLUGINS_ON')) {
223
    define('WEBSHOT_PLUGINS_ON', true);
224
}            //Enable flash and other plugins
225
if (!defined('WEBSHOT_PROXY')) {
226
    define('WEBSHOT_PROXY', '');
227
}                //In case you're behind a proxy server.
228
if (!defined('WEBSHOT_XVFB_RUNNING')) {
229
    define('WEBSHOT_XVFB_RUNNING', false);
230
}            //ADVANCED: Enable this if you've got Xvfb running in the background.
231
232
// 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.
233
if (!isset($allowedSites)) {
234
    $allowedSites = [
235
        'flickr.com',
236
        'staticflickr.com',
237
        'picasa.com',
238
        'img.youtube.com',
239
        'upload.wikimedia.org',
240
        'photobucket.com',
241
        'imgur.com',
242
        'imageshack.us',
243
        'tinypic.com',
244
    ];
245
}
246
// -------------------------------------------------------------
247
// -------------- STOP EDITING CONFIGURATION HERE --------------
248
// -------------------------------------------------------------
249
250
Timthumb::start();
251
252
/**
253
 * Class timthumb
254
 */
255
class Timthumb
256
{
257
    protected        $src                      = '';
258
    protected        $is404                    = false;
259
    protected        $docRoot                  = '';
260
    protected        $lastURLError             = false;
261
    protected        $localImage               = '';
262
    protected        $localImageMTime          = 0.0;
263
    protected        $url                      = false;
264
    protected        $myHost                   = '';
265
    protected        $isURL                    = false;
266
    protected        $cachefile                = '';
267
    protected        $errors                   = [];
268
    protected        $toDeletes                = [];
269
    protected        $cacheDirectory           = '';
270
    protected        $startTime                = 0.0;
271
    protected        $lastBenchTime            = 0.0;
272
    protected        $cropTop                  = false;
273
    protected        $salt                     = '';
274
    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.
275
    protected        $filePrependSecurityBlock = "<?php exit('Execution denied!'); //"; //Designed to have three letter mime type, space, question mark and greater than symbol appended. 6 bytes total.
276
    protected static $curlDataWritten          = 0;
277
    protected static $curlFH                   = false;
278
279
    public static function start()
280
    {
281
        $tim = new self();
282
        $tim->handleErrors();
283
        $tim->securityChecks();
284
        if ($tim->tryBrowserCache()) {
285
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
286
        }
287
        $tim->handleErrors();
288
        if (FILE_CACHE_ENABLED && $tim->tryServerCache()) {
289
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
290
        }
291
        $tim->handleErrors();
292
        $tim->run();
293
        $tim->handleErrors();
294
        exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
295
    }
296
297
    public function __construct()
298
    {
299
        global $allowedSites;
300
        $this->startTime = microtime(true);
301
        date_default_timezone_set('UTC');
302
        $this->debug(1, 'Starting new request from ' . $this->getIP() . ' to ' . Request::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__);
0 ignored issues
show
Bug introduced by
Are you sure @filemtime(__FILE__) of type false|integer can be used in concatenation? ( Ignorable by Annotation )

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

305
        $this->salt = /** @scrutinizer ignore-type */ @filemtime(__FILE__) . '-' . @fileinode(__FILE__);
Loading history...
Bug introduced by
Are you sure @fileinode(__FILE__) of type false|integer can be used in concatenation? ( Ignorable by Annotation )

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

305
        $this->salt = @filemtime(__FILE__) . '-' . /** @scrutinizer ignore-type */ @fileinode(__FILE__);
Loading history...
306
        $this->debug(3, 'Salt is: ' . $this->salt);
307
        if (FILE_CACHE_DIRECTORY) {
308
            if (!is_dir(FILE_CACHE_DIRECTORY)) {
309
                if (!mkdir($concurrentDirectory = FILE_CACHE_DIRECTORY) && !is_dir($concurrentDirectory)) {
310
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
311
                }
312
                if (!is_dir(FILE_CACHE_DIRECTORY)) {
313
                    $this->error('Could not create the file cache directory.');
314
315
                    return false;
316
                }
317
            }
318
            $this->cacheDirectory = FILE_CACHE_DIRECTORY;
319
            if (!touch($this->cacheDirectory . '/index.html')) {
320
                $this->error('Could not create the index.html file - to fix this create an empty file named index.html file in the cache directory.');
321
            }
322
        } else {
323
            $this->cacheDirectory = sys_get_temp_dir();
324
        }
325
        //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.
326
        $this->cleanCache();
327
328
        $this->myHost = preg_replace('/^www\./i', '', \Xmf\Request::getString('HTTP_HOST', '', 'SERVER'));
329
        $this->src    = $this->param('src');
330
        $this->url    = parse_url($this->src);
331
        $this->src    = preg_replace('/https?:\/\/(?:www\.)?' . $this->myHost . '/i', '', $this->src);
332
333
        if (mb_strlen($this->src) <= 3) {
334
            $this->error('No image specified');
335
336
            return false;
337
        }
338
        if (BLOCK_EXTERNAL_LEECHERS && array_key_exists('HTTP_REFERER', $_SERVER) && (!preg_match('/^https?:\/\/(?:www\.)?' . $this->myHost . '(?:$|\/)/i', \Xmf\Request::getString('HTTP_REFERER', '', 'SERVER')))) {
339
            // base64 encoded red image that says 'no hotlinkers'
340
            // nothing to worry about! :)
341
            $imgData = base64_decode("R0lGODlhUAAMAIAAAP8AAP///yH5BAAHAP8ALAAAAABQAAwAAAJpjI+py+0Po5y0OgAMjjv01YUZ\nOGplhWXfNa6JCLnWkXplrcBmW+spbwvaVr/cDyg7IoFC2KbYVC2NQ5MQ4ZNao9Ynzjl9ScNYpneb\nDULB3RP6JuPuaGfuuV4fumf8PuvqFyhYtjdoeFgAADs=", true);
342
            header('Content-Type: image/gif');
343
            header('Content-Length: ' . mb_strlen($imgData));
344
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
345
            header('Pragma: no-cache');
346
            header('Expires: ' . gmdate('D, d M Y H:i:s', time()));
347
            echo $imgData;
348
349
            return false;
350
        }
351
        if (preg_match('/^https?:\/\/[^\/]+/i', $this->src)) {
352
            $this->debug(2, 'Is a request for an external URL: ' . $this->src);
353
            $this->isURL = true;
354
        } else {
355
            $this->debug(2, 'Is a request for an internal file: ' . $this->src);
356
        }
357
        if ($this->isURL && (!ALLOW_EXTERNAL)) {
358
            $this->error('You are not allowed to fetch images from an external website.');
359
360
            return false;
361
        }
362
        if ($this->isURL) {
363
            if (ALLOW_ALL_EXTERNAL_SITES) {
364
                $this->debug(2, 'Fetching from all external sites is enabled.');
365
            } else {
366
                $this->debug(2, 'Fetching only from selected external sites is enabled.');
367
                $allowed = false;
368
                foreach ($allowedSites as $site) {
369
                    if ((mb_strtolower($this->url['host']) === \mb_strtolower($site)) || (mb_strtolower(mb_substr($this->url['host'], -mb_strlen($site) - 1)) === \mb_strtolower(".$site"))) {
370
                        $this->debug(3, "URL hostname {$this->url['host']} matches $site so allowing.");
371
                        $allowed = true;
372
                    }
373
                }
374
                if (!$allowed) {
375
                    return $this->error('You may not fetch images from that site. To enable this site in timthumb, you can either add it to $allowedSites and set ALLOW_EXTERNAL=true. Or you can set ALLOW_ALL_EXTERNAL_SITES=true, depending on your security needs.');
376
                }
377
            }
378
        }
379
380
        $cachePrefix = ($this->isURL ? '_ext_' : '_int_');
381
        if ($this->isURL) {
382
            $arr = explode('&', $_SERVER['QUERY_STRING']);
383
            asort($arr);
384
            $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . implode('', $arr) . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
385
        } else {
386
            $this->localImage = $this->getLocalImagePath($this->src);
387
            if (!$this->localImage) {
388
                $this->debug(1, "Could not find the local image: {$this->localImage}");
389
                $this->error('Could not find the internal image you specified.');
390
                $this->set404();
391
392
                return false;
393
            }
394
            $this->debug(1, "Local image path is {$this->localImage}");
395
            $this->localImageMTime = @filemtime($this->localImage);
396
            //We include the mtime of the local file in case in changes on disk.
397
            $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . $this->localImageMTime . $_SERVER['QUERY_STRING'] . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
0 ignored issues
show
Bug introduced by
Are you sure $this->localImageMTime of type false|integer can be used in concatenation? ( Ignorable by Annotation )

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

397
            $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . /** @scrutinizer ignore-type */ $this->localImageMTime . $_SERVER['QUERY_STRING'] . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
Loading history...
398
        }
399
        $this->debug(2, 'Cache file is: ' . $this->cachefile);
400
401
        return true;
402
    }
403
404
    public function __destruct()
405
    {
406
        foreach ($this->toDeletes as $del) {
407
            $this->debug(2, "Deleting temp file $del");
408
            @unlink($del);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

408
            /** @scrutinizer ignore-unhandled */ @unlink($del);

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...
409
        }
410
    }
411
412
    /**
413
     * @return bool
414
     */
415
    public function run()
416
    {
417
        if ($this->isURL) {
418
            if (!ALLOW_EXTERNAL) {
419
                $this->debug(1, 'Got a request for an external image but ALLOW_EXTERNAL is disabled so returning error msg.');
420
                $this->error('You are not allowed to fetch images from an external website.');
421
422
                return false;
423
            }
424
            $this->debug(3, 'Got request for external image. Starting serveExternalImage.');
425
            if ($this->param('webshot')) {
426
                if (WEBSHOT_ENABLED) {
427
                    $this->debug(3, 'webshot param is set, so we\'re going to take a webshot.');
428
                    $this->serveWebshot();
429
                } else {
430
                    $this->error('You added the webshot parameter but webshots are disabled on this server. You need to set WEBSHOT_ENABLED === true to enable webshots.');
431
                }
432
            } else {
433
                $this->debug(3, 'webshot is NOT set so we\'re going to try to fetch a regular image.');
434
                $this->serveExternalImage();
435
            }
436
        } else {
437
            $this->debug(3, 'Got request for internal image. Starting serveInternalImage()');
438
            $this->serveInternalImage();
439
        }
440
441
        return true;
442
    }
443
444
    /**
445
     * @return bool
446
     */
447
    protected function handleErrors()
448
    {
449
        if ($this->haveErrors()) {
450
            if (NOT_FOUND_IMAGE && $this->is404()) {
451
                if ($this->serveImg(NOT_FOUND_IMAGE)) {
452
                    exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
453
                }
454
                $this->error('Additionally, the 404 image that is configured could not be found or there was an error serving it.');
455
            }
456
            if (ERROR_IMAGE) {
457
                if ($this->serveImg(ERROR_IMAGE)) {
458
                    exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
459
                }
460
                $this->error('Additionally, the error image that is configured could not be found or there was an error serving it.');
461
            }
462
            $this->serveErrors();
463
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
464
        }
465
466
        return false;
467
    }
468
469
    /**
470
     * @return bool
471
     */
472
    protected function tryBrowserCache()
473
    {
474
        if (BROWSER_CACHE_DISABLE) {
475
            $this->debug(3, 'Browser caching is disabled');
476
477
            return false;
478
        }
479
        if (\Xmf\Request::hasVar('HTTP_IF_MODIFIED_SINCE', 'SERVER')) {
480
            $this->debug(3, 'Got a conditional get');
481
            $mtime = false;
482
            //We've already checked if the real file exists in the constructor
483
            if (!is_file($this->cachefile)) {
484
                //If we don't have something cached, regenerate the cached image.
485
                return false;
486
            }
487
            if ($this->localImageMTime) {
488
                $mtime = $this->localImageMTime;
489
                $this->debug(3, "Local real file's modification time is $mtime");
490
            } elseif (\is_file($this->cachefile)) {
491
                //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 (false === $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) {
507
                //Real file or cache file has been modified since last request, so force refetch.
508
                $this->debug(3, 'File has been modified since last fetch.');
509
510
                return false;
511
            }
512
            //Otherwise serve a 304
513
            $this->debug(3, 'File has not been modified since last get, so serving a 304.');
514
            header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
515
            $this->debug(1, 'Returning 304 not modified');
516
517
            return true;
518
        }
519
520
        return false;
521
    }
522
523
    /**
524
     * @return bool
525
     */
526
    protected function tryServerCache()
527
    {
528
        $this->debug(3, 'Trying server cache');
529
        if (\is_file($this->cachefile)) {
530
            $this->debug(3, "Cachefile {$this->cachefile} exists");
531
            if ($this->isURL) {
532
                $this->debug(3, 'This is an external request, so checking if the cachefile is empty which means the request failed previously.');
533
                if (filesize($this->cachefile) < 1) {
534
                    $this->debug(3, 'Found an empty cachefile indicating a failed earlier request. Checking how old it is.');
535
                    //Fetching error occured previously
536
                    if (time() - @filemtime($this->cachefile) > WAIT_BETWEEN_FETCH_ERRORS) {
537
                        $this->debug(3, 'File is older than ' . WAIT_BETWEEN_FETCH_ERRORS . ' seconds. Deleting and returning false so app can try and load file.');
538
                        @unlink($this->cachefile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

538
                        /** @scrutinizer ignore-unhandled */ @unlink($this->cachefile);

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...
539
540
                        return false; //to indicate we didn't serve from cache and app should try and load
541
                    }
542
                    $this->debug(3, 'Empty cachefile is still fresh so returning message saying we had an error fetching this image from remote host.');
543
                    $this->set404();
544
                    $this->error('An error occured fetching image.');
545
546
                    return false;
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
            }
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);
559
560
            return true;
561
        }
562
563
        return null;
564
    }
565
566
    /**
567
     * @param $err
568
     *
569
     * @return bool
570
     */
571
    protected function error($err)
572
    {
573
        $this->debug(3, "Adding error message: $err");
574
        $this->errors[] = $err;
575
576
        return false;
577
    }
578
579
    /**
580
     * @return bool
581
     */
582
    protected function haveErrors()
583
    {
584
        return count($this->errors) > 0;
585
    }
586
587
    protected function serveErrors()
588
    {
589
        header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
590
        if (!DISPLAY_ERROR_MESSAGES) {
591
            return;
592
        }
593
        $html = '<ul>';
594
        foreach ($this->errors as $err) {
595
            $html .= '<li>' . htmlentities($err, ENT_QUOTES | ENT_HTML5) . '</li>';
596
        }
597
        $html .= '</ul>';
598
        echo '<h1>A TimThumb error has occured</h1>The following error(s) occured:<br>' . $html . '<br>';
599
        echo '<br>Query String : ' . htmlentities($_SERVER['QUERY_STRING'], ENT_QUOTES | ENT_HTML5);
600
        echo '<br>TimThumb version : ' . VERSION . '</pre>';
601
    }
602
603
    /**
604
     * @return bool
605
     */
606
    protected function serveInternalImage()
607
    {
608
        $this->debug(3, "Local image path is $this->localImage");
609
        if (!$this->localImage) {
610
            $this->sanityFail('localImage not set after verifying it earlier in the code.');
611
612
            return false;
613
        }
614
        $fileSize = filesize($this->localImage);
615
        if ($fileSize > MAX_FILE_SIZE) {
616
            $this->error('The file you specified is greater than the maximum allowed file size.');
617
618
            return false;
619
        }
620
        if ($fileSize <= 0) {
621
            $this->error('The file you specified is <= 0 bytes.');
622
623
            return false;
624
        }
625
        $this->debug(3, 'Calling processImageAndWriteToCache() for local image.');
626
        if ($this->processImageAndWriteToCache($this->localImage)) {
627
            $this->serveCacheFile();
628
629
            return true;
630
        }
631
632
        return false;
633
    }
634
635
    /**
636
     * @return bool|void
637
     */
638
    protected function cleanCache()
639
    {
640
        if (FILE_CACHE_TIME_BETWEEN_CLEANS < 0) {
641
            return null;
642
        }
643
        $this->debug(3, 'cleanCache() called');
644
        $lastCleanFile = $this->cacheDirectory . '/timthumb_cacheLastCleanTime.touch';
645
646
        //If this is a new timthumb installation we need to create the file
647
        if (!is_file($lastCleanFile)) {
648
            $this->debug(1, "File tracking last clean doesn't exist. Creating $lastCleanFile");
649
            if (!touch($lastCleanFile)) {
650
                $this->error('Could not create cache clean timestamp file.');
651
            }
652
653
            return null;
654
        }
655
        if (@filemtime($lastCleanFile) < (time() - FILE_CACHE_TIME_BETWEEN_CLEANS)) {
656
            //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, GLOB_NOSORT);
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 for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

668
                        /** @scrutinizer ignore-unhandled */ @unlink($file);

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
        }
675
        $this->debug(3, 'Cache was cleaned less than ' . FILE_CACHE_TIME_BETWEEN_CLEANS . ' seconds ago so no cleaning needed.');
676
677
        return false;
678
    }
679
680
    /**
681
     * @param $localImage
682
     *
683
     * @return bool
684
     */
685
    protected function processImageAndWriteToCache($localImage)
686
    {
687
        $sData    = getimagesize($localImage);
688
        $origType = $sData[2];
689
        $mimeType = $sData['mime'];
690
691
        $this->debug(3, "Mime type of image is $mimeType");
692
        if (!preg_match('/^image\/(?:gif|jpg|jpeg|png)$/i', $mimeType)) {
693
            return $this->error('The image being resized is not a valid gif, jpg or png.');
694
        }
695
696
        if (!function_exists('imagecreatetruecolor')) {
697
            return $this->error('GD Library Error: imagecreatetruecolor does not exist - please contact your webhost and ask them to install the GD library');
698
        }
699
700
        if (defined('IMG_FILTER_NEGATE') && function_exists('imagefilter')) {
701
            $imageFilters = [
702
                1  => [IMG_FILTER_NEGATE, 0],
703
                2  => [IMG_FILTER_GRAYSCALE, 0],
704
                3  => [IMG_FILTER_BRIGHTNESS, 1],
705
                4  => [IMG_FILTER_CONTRAST, 1],
706
                5  => [IMG_FILTER_COLORIZE, 4],
707
                6  => [IMG_FILTER_EDGEDETECT, 0],
708
                7  => [IMG_FILTER_EMBOSS, 0],
709
                8  => [IMG_FILTER_GAUSSIAN_BLUR, 0],
710
                9  => [IMG_FILTER_SELECTIVE_BLUR, 0],
711
                10 => [IMG_FILTER_MEAN_REMOVAL, 0],
712
                11 => [IMG_FILTER_SMOOTH, 0],
713
            ];
714
        }
715
716
        // get standard input properties
717
        $newWidth     = (int)abs((int)$this->param('w', 0));
718
        $newHeight    = (int)abs((int)$this->param('h', 0));
719
        $zoomCrop     = (int)$this->param('zc', DEFAULT_ZC);
720
        $quality      = (int)abs((int)$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 (0 == $newWidth && 0 == $newHeight) {
729
            $newWidth  = DEFAULT_WIDTH;
730
            $newHeight = 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 (false === $image) {
0 ignored issues
show
introduced by
The condition false === $image is always false.
Loading history...
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 (3 == $zoomCrop) {
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((int)$newWidth, (int)$newHeight);
772
        imagealphablending($canvas, false);
773
774
        if (3 == \mb_strlen($canvas_color)) {
775
            //if is 3-char notation, edit string into 6-char notation
776
            $canvas_color = str_repeat(mb_substr($canvas_color, 0, 1), 2) . str_repeat(mb_substr($canvas_color, 1, 1), 2) . str_repeat(mb_substr($canvas_color, 2, 1), 2);
777
        } elseif (6 != \mb_strlen($canvas_color)) {
778
            $canvas_color = DEFAULT_CC; // on error return default canvas color
779
        }
780
781
        $canvas_color_R = hexdec(mb_substr($canvas_color, 0, 2));
782
        $canvas_color_G = hexdec(mb_substr($canvas_color, 2, 2));
783
        $canvas_color_B = hexdec(mb_substr($canvas_color, 4, 2));
784
785
        // Create a new transparent color for image
786
        // If is a png and PNG_IS_TRANSPARENT is false then remove the alpha transparency
787
        // (and if is set a canvas color show it in the background)
788
        if (!PNG_IS_TRANSPARENT && $canvas_trans && preg_match('/^image\/png$/i', $mimeType)) {
789
            $color = imagecolorallocatealpha($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 127);
0 ignored issues
show
Bug introduced by
It seems like $canvas_color_R can also be of type double; however, parameter $red of imagecolorallocatealpha() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

789
            $color = imagecolorallocatealpha($canvas, /** @scrutinizer ignore-type */ $canvas_color_R, $canvas_color_G, $canvas_color_B, 127);
Loading history...
Bug introduced by
It seems like $canvas_color_G can also be of type double; however, parameter $green of imagecolorallocatealpha() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

789
            $color = imagecolorallocatealpha($canvas, $canvas_color_R, /** @scrutinizer ignore-type */ $canvas_color_G, $canvas_color_B, 127);
Loading history...
Bug introduced by
It seems like $canvas_color_B can also be of type double; however, parameter $blue of imagecolorallocatealpha() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

789
            $color = imagecolorallocatealpha($canvas, $canvas_color_R, $canvas_color_G, /** @scrutinizer ignore-type */ $canvas_color_B, 127);
Loading history...
790
        } else {
791
            $color = imagecolorallocatealpha($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 0);
792
        }
793
794
        // Completely fill the background of the new image with allocated color.
795
        imagefill($canvas, 0, 0, $color);
796
        // scale down and add borders
797
        if (2 == $zoomCrop) {
798
            $final_height = $height * ($newWidth / $width);
799
            if ($final_height > $newHeight) {
800
                $origin_x = $newWidth / 2;
801
                $newWidth = $width * ($newHeight / $height);
802
                $origin_x = round($origin_x - ($newWidth / 2));
803
            } else {
804
                $origin_y  = $newHeight / 2;
805
                $newHeight = $final_height;
806
                $origin_y  = round($origin_y - ($newHeight / 2));
807
            }
808
        }
809
810
        // Restore transparency blending
811
        imagesavealpha($canvas, true);
812
813
        if ($zoomCrop > 0) {
814
            $src_x = $src_y = 0;
815
            $src_w = $width;
816
            $src_h = $height;
817
818
            $cmp_x = $width / $newWidth;
819
            $cmp_y = $height / $newHeight;
820
821
            // calculate x or y coordinate and width or height of source
822
            if ($cmp_x > $cmp_y) {
823
                $src_w = round($width / $cmp_x * $cmp_y);
824
                $src_x = round(($width - ($width / $cmp_x * $cmp_y)) / 2);
825
            } elseif ($cmp_y > $cmp_x) {
826
                $src_h = round($height / $cmp_y * $cmp_x);
827
                $src_y = round(($height - ($height / $cmp_y * $cmp_x)) / 2);
828
            }
829
830
            // positional cropping!
831
            if ($align) {
832
                if (false !== mb_strpos($align, 't')) {
833
                    $src_y = 0;
834
                }
835
                if (false !== mb_strpos($align, 'b')) {
836
                    $src_y = $height - $src_h;
837
                }
838
                if (false !== mb_strpos($align, 'l')) {
839
                    $src_x = 0;
840
                }
841
                if (false !== mb_strpos($align, 'r')) {
842
                    $src_x = $width - $src_w;
843
                }
844
            }
845
846
            imagecopyresampled($canvas, $image, (int)$origin_x, (int)$origin_y, (int)$src_x, (int)$src_y, (int)$newWidth, (int)$newHeight, (int)$src_w, (int)$src_h);
847
        } else {
848
            // copy and resize part of an image with resampling
849
            imagecopyresampled($canvas, $image, 0, 0, 0, 0, (int)$newWidth, (int)$newHeight, (int)$width, (int)$height);
850
        }
851
852
        if (defined('IMG_FILTER_NEGATE') && '' != $filters && function_exists('imagefilter')) {
853
            // apply filters to image
854
            $filterList = explode('|', $filters);
855
            foreach ($filterList as $fl) {
856
                $filterSettings = explode(',', $fl);
857
                if (isset($imageFilters[$filterSettings[0]])) {
858
                    for ($i = 0; $i < 4; ++$i) {
859
                        if (isset($filterSettings[$i])) {
860
                            $filterSettings[$i] = (int)$filterSettings[$i];
861
                        } else {
862
                            $filterSettings[$i] = null;
863
                        }
864
                    }
865
866
                    switch ($imageFilters[$filterSettings[0]][1]) {
867
                        case 1:
868
869
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1]);
870
871
                            break;
872
                        case 2:
873
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2]);
874
875
                            break;
876
                        case 3:
877
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3]);
878
879
                            break;
880
                        case 4:
881
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3], $filterSettings[4]);
882
883
                            break;
884
                        default:
885
                            imagefilter($canvas, $imageFilters[$filterSettings[0]][0]);
886
                            break;
887
                    }
888
                }
889
            }
890
        }
891
892
        // sharpen image
893
        if ($sharpen && function_exists('imageconvolution')) {
894
            $sharpenMatrix = [
895
                [-1, -1, -1],
896
                [-1, 16, -1],
897
                [-1, -1, -1],
898
            ];
899
900
            $divisor = 8;
901
            $offset  = 0;
902
903
            imageconvolution($canvas, $sharpenMatrix, $divisor, $offset);
904
        }
905
        //Straight from Wordpress core code. Reduces filesize by up to 70% for PNG's
906
        if ((IMAGETYPE_PNG == $origType || IMAGETYPE_GIF == $origType) && function_exists('imageistruecolor') && !imageistruecolor($image) && imagecolortransparent($image) > 0) {
907
            imagetruecolortopalette($canvas, false, imagecolorstotal($image));
908
        }
909
910
        $imgType  = '';
911
        $tempfile = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
912
        if (preg_match('/^image\/(?:jpg|jpeg)$/i', $mimeType)) {
913
            $imgType = 'jpg';
914
            imagejpeg($canvas, $tempfile, $quality);
915
        } elseif (preg_match('/^image\/png$/i', $mimeType)) {
916
            $imgType = 'png';
917
            imagepng($canvas, $tempfile, (int)floor($quality * 0.09));
918
        } elseif (preg_match('/^image\/gif$/i', $mimeType)) {
919
            $imgType = 'gif';
920
            imagegif($canvas, $tempfile);
921
        } else {
922
            return $this->sanityFail('Could not match mime type after verifying it previously.');
923
        }
924
925
        if ('png' === $imgType && OPTIPNG_ENABLED && OPTIPNG_PATH && @is_file(OPTIPNG_PATH)) {
926
            $exec = OPTIPNG_PATH;
0 ignored issues
show
Unused Code introduced by
The assignment to $exec is dead and can be removed.
Loading history...
927
            $this->debug(3, "optipng'ing $tempfile");
928
            $presize = filesize($tempfile);
929
            $out     = shell_exec('$exec -o1 $tempfile'); //you can use up to -o7 but it really slows things down
0 ignored issues
show
Unused Code introduced by
The assignment to $out is dead and can be removed.
Loading history...
930
            clearstatcache();
931
            $aftersize = filesize($tempfile);
932
            $sizeDrop  = $presize - $aftersize;
933
            if ($sizeDrop > 0) {
934
                $this->debug(1, "optipng reduced size by $sizeDrop");
935
            } elseif ($sizeDrop < 0) {
936
                $this->debug(1, "optipng increased size! Difference was: $sizeDrop");
937
            } else {
938
                $this->debug(1, 'optipng did not change image size.');
939
            }
940
        } elseif ('png' === $imgType && PNGCRUSH_ENABLED && PNGCRUSH_PATH && @is_file(PNGCRUSH_PATH)) {
941
            $exec      = PNGCRUSH_PATH;
942
            $tempfile2 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
943
            $this->debug(3, "pngcrush'ing $tempfile to $tempfile2");
944
            $out   = shell_exec('$exec $tempfile $tempfile2');
945
            $todel = '';
946
            if (\is_file($tempfile2)) {
947
                $sizeDrop = filesize($tempfile) - filesize($tempfile2);
948
                if ($sizeDrop > 0) {
949
                    $this->debug(1, "pngcrush was succesful and gave a $sizeDrop byte size reduction");
950
                    $todel    = $tempfile;
951
                    $tempfile = $tempfile2;
952
                } else {
953
                    $this->debug(1, "pngcrush did not reduce file size. Difference was $sizeDrop bytes.");
954
                    $todel = $tempfile2;
955
                }
956
            } else {
957
                $this->debug(3, "pngcrush failed with output: $out");
958
                $todel = $tempfile2;
959
            }
960
            @unlink($todel);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

960
            /** @scrutinizer ignore-unhandled */ @unlink($todel);

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...
961
        }
962
963
        $this->debug(3, 'Rewriting image with security header.');
964
        $tempfile4 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
965
        $context   = stream_context_create();
966
        $fp        = fopen($tempfile, 'rb', false, $context);
967
        file_put_contents($tempfile4, $this->filePrependSecurityBlock . $imgType . ' ?' . '>'); //6 extra bytes, first 3 being image type
968
        file_put_contents($tempfile4, $fp, FILE_APPEND);
969
        fclose($fp);
970
        @unlink($tempfile);
971
        $this->debug(3, 'Locking and replacing cache file.');
972
        $lockFile = $this->cachefile . '.lock';
973
        $fh       = fopen($lockFile, 'wb');
974
        if (!$fh) {
0 ignored issues
show
introduced by
$fh is of type resource, thus it always evaluated to false.
Loading history...
975
            return $this->error('Could not open the lockfile for writing an image.');
976
        }
977
        if (flock($fh, LOCK_EX)) {
978
            @unlink($this->cachefile); //rename generally overwrites, but doing this in case of platform specific quirks. File might not exist yet.
979
            rename($tempfile4, $this->cachefile);
980
            flock($fh, LOCK_UN);
981
            fclose($fh);
982
            @unlink($lockFile);
983
        } else {
984
            fclose($fh);
985
            @unlink($lockFile);
986
            @unlink($tempfile4);
987
988
            return $this->error('Could not get a lock for writing.');
989
        }
990
        $this->debug(3, 'Done image replace with security header. Cleaning up and running cleanCache()');
991
        imagedestroy($canvas);
992
        imagedestroy($image);
993
994
        return true;
995
    }
996
997
    protected function calcDocRoot()
998
    {
999
        $docRoot = @$_SERVER['DOCUMENT_ROOT'];
1000
        if (defined('LOCAL_FILE_BASE_DIRECTORY')) {
1001
            $docRoot = LOCAL_FILE_BASE_DIRECTORY;
0 ignored issues
show
Bug introduced by
The constant LOCAL_FILE_BASE_DIRECTORY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1002
        }
1003
        if (!isset($docRoot)) {
1004
            $this->debug(3, 'DOCUMENT_ROOT is not set. This is probably windows. Starting search 1.');
1005
            if (\Xmf\Request::hasVar('SCRIPT_FILENAME', 'SERVER')) {
1006
                $docRoot = str_replace('\\', '/', mb_substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - mb_strlen($_SERVER['SCRIPT_NAME'])));
1007
                $this->debug(3, "Generated docRoot using SCRIPT_FILENAME and SCRIPT_NAME as: $docRoot");
1008
            }
1009
        }
1010
        if (!isset($docRoot)) {
1011
            $this->debug(3, 'DOCUMENT_ROOT still is not set. Starting search 2.');
1012
            if (\Xmf\Request::hasVar('PATH_TRANSLATED', 'SERVER')) {
1013
                $docRoot = str_replace('\\', '/', mb_substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - mb_strlen($_SERVER['SCRIPT_NAME'])));
1014
                $this->debug(3, "Generated docRoot using PATH_TRANSLATED and SCRIPT_NAME as: $docRoot");
1015
            }
1016
        }
1017
        if ($docRoot && '/' !== $_SERVER['DOCUMENT_ROOT']) {
1018
            $docRoot = preg_replace('/\/$/', '', $docRoot);
1019
        }
1020
        $this->debug(3, 'Doc root is: ' . $docRoot);
1021
        $this->docRoot = $docRoot;
1022
    }
1023
1024
    /**
1025
     * @param $src
1026
     *
1027
     * @return bool|string
1028
     */
1029
    protected function getLocalImagePath($src)
1030
    {
1031
        $src = ltrim($src, '/'); //strip off the leading '/'
1032
        if (!$this->docRoot) {
1033
            $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.');
1034
            //We don't support serving images outside the current dir if we don't have a doc root for security reasons.
1035
            $file = preg_replace('/^.*?([^\/\\\\]+)$/', '$1', $src); //strip off any path info and just leave the filename.
1036
            if (\is_file($file)) {
1037
                return $this->realpath($file);
1038
            }
1039
1040
            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.");
1041
        }
1042
        if (!is_dir($this->docRoot)) {
1043
            $this->error("Server path does not exist. Ensure variable \$_SERVER['DOCUMENT_ROOT'] is set correctly");
1044
        }
1045
1046
        //Do not go past this point without docRoot set
1047
1048
        //Try src under docRoot
1049
        if (file_exists($this->docRoot . '/' . $src)) {
1050
            $this->debug(3, 'Found file as ' . $this->docRoot . '/' . $src);
1051
            $real = $this->realpath($this->docRoot . '/' . $src);
1052
            if (0 === mb_stripos($real, $this->docRoot)) {
1053
                return $real;
1054
            }
1055
            $this->debug(1, 'Security block: The file specified occurs outside the document root.');
1056
            //allow search to continue
1057
        }
1058
        //Check absolute paths and then verify the real path is under doc root
1059
        $absolute = $this->realpath('/' . $src);
1060
        if ($absolute && file_exists($absolute)) {
1061
            //realpath does file_exists check, so can probably skip the exists check here
1062
            $this->debug(3, "Found absolute path: $absolute");
1063
            if (!$this->docRoot) {
1064
                $this->sanityFail('docRoot not set when checking absolute path.');
1065
            }
1066
            if (0 === mb_stripos($absolute, $this->docRoot)) {
1067
                return $absolute;
1068
            }
1069
            $this->debug(1, 'Security block: The file specified occurs outside the document root.');
1070
            //and continue search
1071
        }
1072
1073
        $base = $this->docRoot;
1074
1075
        // account for Windows directory structure
1076
        if (false !== mb_strpos($_SERVER['SCRIPT_FILENAME'], ':')) {
1077
            $subDirectories = explode('\\', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
1078
        } else {
1079
            $subDirectories = explode('/', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
1080
        }
1081
1082
        foreach ($subDirectories as $sub) {
1083
            $base .= $sub . '/';
1084
            $this->debug(3, 'Trying file as: ' . $base . $src);
1085
            if (file_exists($base . $src)) {
1086
                $this->debug(3, 'Found file as: ' . $base . $src);
1087
                $real = $this->realpath($base . $src);
1088
                if (0 === mb_stripos($real, $this->realpath($this->docRoot))) {
1089
                    return $real;
1090
                }
1091
                $this->debug(1, 'Security block: The file specified occurs outside the document root.');
1092
                //And continue search
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 https://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;
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;
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: https://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 = shell_exec('$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
        }
1181
1182
        return false;
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 for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1200
            /** @scrutinizer ignore-unhandled */ @unlink($this->cachefile);

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);
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
        }
1222
1223
        return false;
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. ( Ignorable by Annotation )

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

1232
    public static function curlWrite(/** @scrutinizer ignore-unused */ $h, $d)

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

Loading history...
1233
    {
1234
        fwrite(self::$curlFH, $d);
0 ignored issues
show
Bug introduced by
self::curlFH of type boolean is incompatible with the type resource expected by parameter $stream of fwrite(). ( Ignorable by Annotation )

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

1234
        fwrite(/** @scrutinizer ignore-type */ self::$curlFH, $d);
Loading history...
1235
        self::$curlDataWritten += \mb_strlen($d);
1236
        if (self::$curlDataWritten > MAX_FILE_SIZE) {
1237
            return 0;
1238
        }
1239
1240
        return mb_strlen($d);
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');
1255
        if (!$fp) {
0 ignored issues
show
introduced by
$fp is of type resource, thus it always evaluated to false.
Loading history...
1256
            return $this->error('Could not open cachefile.');
1257
        }
1258
        fseek($fp, mb_strlen($this->filePrependSecurityBlock), SEEK_SET);
1259
        $imgType = fread($fp, 3);
1260
        fseek($fp, 3, SEEK_CUR);
1261
        if (ftell($fp) != \mb_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 for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1262
            /** @scrutinizer ignore-unhandled */ @unlink($this->cachefile);

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) - (mb_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 (false !== $content) {
1275
            $content = mb_substr($content, mb_strlen($this->filePrependSecurityBlock) + 6);
1276
            echo $content;
1277
            $this->debug(3, 'Served using file_get_contents and echo');
1278
1279
            return true;
1280
        }
1281
        $this->error('Cache file could not be loaded.');
1282
1283
        return false;
1284
    }
1285
1286
    /**
1287
     * @param $mimeType
1288
     * @param $dataSize
1289
     *
1290
     * @return bool
1291
     */
1292
    protected function sendImageHeaders($mimeType, $dataSize)
1293
    {
1294
        if (!preg_match('/^image\//i', $mimeType)) {
1295
            $mimeType = 'image/' . $mimeType;
1296
        }
1297
        if ('image/jpg' === \mb_strtolower($mimeType)) {
1298
            $mimeType = 'image/jpeg';
1299
        }
1300
        $gmdateExpires   = gmdate('D, d M Y H:i:s', strtotime('now +10 days')) . ' GMT';
1301
        $gmdate_modified = gmdate('D, d M Y H:i:s') . ' GMT';
1302
        // send content headers then display image
1303
        header('Content-Type: ' . $mimeType);
1304
        header('Accept-Ranges: none'); //Changed this because we don't accept range requests
1305
        header('Last-Modified: ' . $gmdate_modified);
1306
        header('Content-Length: ' . $dataSize);
1307
        if (BROWSER_CACHE_DISABLE) {
1308
            $this->debug(3, 'Browser cache is disabled so setting non-caching headers.');
1309
            header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1310
            header('Pragma: no-cache');
1311
            header('Expires: ' . gmdate('D, d M Y H:i:s', time()));
1312
        } else {
1313
            $this->debug(3, 'Browser caching is enabled');
1314
            header('Cache-Control: max-age=' . BROWSER_CACHE_MAX_AGE . ', must-revalidate');
1315
            header('Expires: ' . $gmdateExpires);
1316
        }
1317
1318
        return true;
1319
    }
1320
1321
    protected function securityChecks()
1322
    {
1323
    }
1324
1325
    /**
1326
     * @param        $property
1327
     * @param string $default
1328
     *
1329
     * @return string
1330
     */
1331
    protected function param($property, $default = '')
1332
    {
1333
        if (isset($_GET[$property])) {
1334
            return Request::getString($property, '', 'GET');
1335
        }
1336
1337
        return $default;
1338
    }
1339
1340
    /**
1341
     * @param $mimeType
1342
     * @param $src
1343
     *
1344
     * @return resource
1345
     */
1346
    protected function openImage($mimeType, $src)
1347
    {
1348
        $image = '';
1349
        switch ($mimeType) {
1350
            case 'image/jpeg':
1351
1352
                $image = imagecreatefromjpeg($src);
1353
1354
                break;
1355
            case 'image/png':
1356
                $image = imagecreatefrompng($src);
1357
                imagealphablending($image, true);
1358
                imagesavealpha($image, true);
1359
1360
                break;
1361
            case 'image/gif':
1362
                $image = imagecreatefromgif($src);
1363
1364
                break;
1365
            default:
1366
                $this->error('Unrecognised mimeType');
1367
        }
1368
1369
        return $image;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $image also could return the type GdImage|string which is incompatible with the documented return type resource.
Loading history...
1370
    }
1371
1372
    /**
1373
     * @return string
1374
     */
1375
    protected function getIP()
1376
    {
1377
        $rem = @$_SERVER['REMOTE_ADDR'];
1378
        $ff  = @$_SERVER['HTTP_X_FORWARDED_FOR'];
1379
        $ci  = @$_SERVER['HTTP_CLIENT_IP'];
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
        }
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
     * @param $level
1405
     * @param $msg
1406
     */
1407
    protected function debug($level, $msg)
1408
    {
1409
        if (DEBUG_ON && $level <= DEBUG_LEVEL) {
1410
            $execTime = sprintf('%.6f', microtime(true) - $this->startTime);
1411
            $tick     = sprintf('%.6f', 0);
1412
            if ($this->lastBenchTime > 0) {
1413
                $tick = sprintf('%.6f', microtime(true) - $this->lastBenchTime);
1414
            }
1415
            $this->lastBenchTime = microtime(true);
1416
            error_log('TimThumb Debug line ' . __LINE__ . " [$execTime : $tick]: $msg");
1417
        }
1418
    }
1419
1420
    /**
1421
     * @param $msg
1422
     *
1423
     * @return bool
1424
     */
1425
    protected function sanityFail($msg)
1426
    {
1427
        return $this->error("There is a problem in the timthumb code. Message: Please report this error at <a href='https://code.google.com/p/timthumb/issues/list'>timthumb's bug tracking page</a>: $msg");
1428
    }
1429
1430
    /**
1431
     * @param $file
1432
     *
1433
     * @return string
1434
     */
1435
    protected function getMimeType($file)
1436
    {
1437
        $info = getimagesize($file);
1438
        if (is_array($info) && $info['mime']) {
1439
            return $info['mime'];
1440
        }
1441
1442
        return '';
1443
    }
1444
1445
    protected function setMemoryLimit()
1446
    {
1447
        $inimem   = ini_get('memory_limit');
1448
        $inibytes = self::returnBytes($inimem);
1449
        $ourbytes = self::returnBytes(MEMORY_LIMIT);
1450
        if ($inibytes < $ourbytes) {
1451
            ini_set('memory_limit', MEMORY_LIMIT);
1452
            $this->debug(3, "Increased memory from $inimem to " . MEMORY_LIMIT);
1453
        } else {
1454
            $this->debug(3, 'Not adjusting memory size because the current setting is ' . $inimem . ' and our size of ' . MEMORY_LIMIT . ' is smaller.');
1455
        }
1456
    }
1457
1458
    /**
1459
     * @param $sizeString
1460
     *
1461
     * @return int
1462
     */
1463
    protected static function returnBytes($sizeString)
1464
    {
1465
        switch (mb_substr($sizeString, -1)) {
1466
            case 'M':
1467
1468
            case 'm':
1469
1470
                return (int)$sizeString * 1048576;
1471
            case 'K':
1472
            case 'k':
1473
1474
                return (int)$sizeString * 1024;
1475
            case 'G':
1476
            case 'g':
1477
1478
                return (int)$sizeString * 1073741824;
1479
            default:
1480
                return $sizeString;
1481
        }
1482
    }
1483
1484
    /**
1485
     * @param $url
1486
     * @param $tempfile
1487
     *
1488
     * @return bool
1489
     */
1490
    protected function getURL($url, $tempfile)
1491
    {
1492
        $this->lastURLError = false;
1493
        $url                = preg_replace('/ /', '%20', $url);
1494
        if (function_exists('curl_init')) {
1495
            $this->debug(3, 'Curl is installed so using it to fetch URL.');
1496
            self::$curlFH = fopen($tempfile, 'wb');
1497
            if (!self::$curlFH) {
1498
                $this->error("Could not open $tempfile for writing.");
1499
1500
                return false;
1501
            }
1502
            self::$curlDataWritten = 0;
1503
            $this->debug(3, "Fetching url with curl: $url");
1504
            $curl = curl_init($url);
1505
            curl_setopt($curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
1506
            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');
1507
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1508
            curl_setopt($curl, CURLOPT_HEADER, 0);
1509
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true); //was false before
1510
            curl_setopt($curl, CURLOPT_WRITEFUNCTION, 'timthumb::curlWrite');
1511
            @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 for curl_setopt(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1511
            /** @scrutinizer ignore-unhandled */ @curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);

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...
1512
            @curl_setopt($curl, CURLOPT_MAXREDIRS, 10);
1513
1514
            $curlResult = curl_exec($curl);
1515
            fclose(self::$curlFH);
1516
            $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1517
            if (404 == $httpStatus) {
1518
                $this->set404();
1519
            }
1520
            if (302 == $httpStatus) {
1521
                $this->error('External Image is Redirecting. Try alternate image url');
1522
1523
                return false;
1524
            }
1525
            if ($curlResult) {
1526
                curl_close($curl);
1527
1528
                return true;
1529
            }
1530
            $this->lastURLError = curl_error($curl);
1531
            curl_close($curl);
1532
1533
            return false;
1534
        }
1535
        $img = @file_get_contents($url);
1536
        if (false === $img) {
1537
            $err                = error_get_last();
1538
            $this->lastURLError = $err;
1539
            if (is_array($err) && $err['message']) {
1540
                $this->lastURLError = $err['message'];
1541
            }
1542
            if (false !== mb_strpos($this->lastURLError, '404')) {
1543
                $this->set404();
1544
            }
1545
1546
            return false;
1547
        }
1548
        if (!file_put_contents($tempfile, $img)) {
1549
            $this->error("Could not write to $tempfile.");
1550
1551
            return false;
1552
        }
1553
1554
        return true;
1555
    }
1556
1557
    /**
1558
     * @param $file
1559
     *
1560
     * @return bool
1561
     */
1562
    protected function serveImg($file)
1563
    {
1564
        $s = getimagesize($file);
1565
        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...
1566
            return false;
1567
        }
1568
        header('Content-Type: ' . $s['mime']);
1569
        header('Content-Length: ' . filesize($file));
1570
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1571
        header('Pragma: no-cache');
1572
        $bytes = @readfile($file);
1573
        if ($bytes > 0) {
1574
            return true;
1575
        }
1576
        $content = @file_get_contents($file);
1577
        if (false !== $content) {
1578
            echo $content;
1579
1580
            return true;
1581
        }
1582
1583
        return false;
1584
    }
1585
1586
    protected function set404()
1587
    {
1588
        $this->is404 = true;
1589
    }
1590
1591
    /**
1592
     * @return bool
1593
     */
1594
    protected function is404()
1595
    {
1596
        return $this->is404;
1597
    }
1598
}
1599