Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
16591 anikendra 1
<?php
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
define ('VERSION', '2.8.13');																		// Version of this script 
24
//Load a config file if it exists. Otherwise, use the values below
25
if( file_exists(dirname(__FILE__) . '/timthumb-config.php'))	require_once('timthumb-config.php');
26
if(! defined('DEBUG_ON') )					define ('DEBUG_ON', false);								// Enable debug logging to web server error log (STDERR)
27
if(! defined('DEBUG_LEVEL') )				define ('DEBUG_LEVEL', 1);								// Debug level 1 is less noisy and 3 is the most noisy
28
if(! defined('MEMORY_LIMIT') )				define ('MEMORY_LIMIT', '30M');							// Set PHP memory limit
29
if(! defined('BLOCK_EXTERNAL_LEECHERS') ) 	define ('BLOCK_EXTERNAL_LEECHERS', false);				// If the image or webshot is being loaded on an external site, display a red "No Hotlinking" gif.
30
if(! defined('DISPLAY_ERROR_MESSAGES') )	define ('DISPLAY_ERROR_MESSAGES', true);				// Display error messages. Set to false to turn off errors (good for production websites)
31
//Image fetching and caching
32
if(! defined('ALLOW_EXTERNAL') )			define ('ALLOW_EXTERNAL', TRUE);						// Allow image fetching from external websites. Will check against ALLOWED_SITES if ALLOW_ALL_EXTERNAL_SITES is false
33
if(! defined('ALLOW_ALL_EXTERNAL_SITES') ) 	define ('ALLOW_ALL_EXTERNAL_SITES', true);				// Less secure. 
34
if(! defined('FILE_CACHE_ENABLED') ) 		define ('FILE_CACHE_ENABLED', TRUE);					// Should we store resized/modified images on disk to speed things up?
35
if(! defined('FILE_CACHE_TIME_BETWEEN_CLEANS'))	define ('FILE_CACHE_TIME_BETWEEN_CLEANS', 86400);	// How often the cache is cleaned 
36
 
37
if(! defined('FILE_CACHE_MAX_FILE_AGE') ) 	define ('FILE_CACHE_MAX_FILE_AGE', 86400);				// How old does a file have to be to be deleted from the cache
38
if(! defined('FILE_CACHE_SUFFIX') ) 		define ('FILE_CACHE_SUFFIX', '.timthumb.txt');			// What to put at the end of all files in the cache directory so we can identify them
39
if(! defined('FILE_CACHE_PREFIX') ) 		define ('FILE_CACHE_PREFIX', 'timthumb');				// What to put at the beg of all files in the cache directory so we can identify them
40
if(! defined('FILE_CACHE_DIRECTORY') ) 		define ('FILE_CACHE_DIRECTORY', './cache');				// Directory where images are cached. Left blank it will use the system temporary directory (which is better for security)
41
if(! defined('MAX_FILE_SIZE') )				define ('MAX_FILE_SIZE', 10485760);						// 10 Megs is 10485760. This is the max internal or external file size that we'll process.  
42
if(! defined('CURL_TIMEOUT') )				define ('CURL_TIMEOUT', 20);							// Timeout duration for Curl. This only applies if you have Curl installed and aren't using PHP's default URL fetching mechanism.
43
if(! defined('WAIT_BETWEEN_FETCH_ERRORS') )	define ('WAIT_BETWEEN_FETCH_ERRORS', 3600);				// Time to wait between errors fetching remote file
44
 
45
//Browser caching
46
if(! defined('BROWSER_CACHE_MAX_AGE') ) 	define ('BROWSER_CACHE_MAX_AGE', 864000);				// Time to cache in the browser
47
if(! defined('BROWSER_CACHE_DISABLE') ) 	define ('BROWSER_CACHE_DISABLE', false);				// Use for testing if you want to disable all browser caching
48
 
49
//Image size and defaults
50
if(! defined('MAX_WIDTH') )					define ('MAX_WIDTH', 1500);								// Maximum image width
51
if(! defined('MAX_HEIGHT') )				define ('MAX_HEIGHT', 1500);							// Maximum image height
52
if(! defined('NOT_FOUND_IMAGE') )			define ('NOT_FOUND_IMAGE', '');							// Image to serve if any 404 occurs 
53
if(! defined('ERROR_IMAGE') )				define ('ERROR_IMAGE', '');								// Image to serve if an error occurs instead of showing error message 
54
if(! defined('PNG_IS_TRANSPARENT') )		define ('PNG_IS_TRANSPARENT', FALSE);					// Define if a png image should have a transparent background color. Use False value if you want to display a custom coloured canvas_colour 
55
if(! defined('DEFAULT_Q') )					define ('DEFAULT_Q', 90);								// Default image quality. Allows overrid in timthumb-config.php
56
if(! defined('DEFAULT_ZC') )				define ('DEFAULT_ZC', 1);								// Default zoom/crop setting. Allows overrid in timthumb-config.php
57
if(! defined('DEFAULT_F') )					define ('DEFAULT_F', '');								// Default image filters. Allows overrid in timthumb-config.php
58
if(! defined('DEFAULT_S') )					define ('DEFAULT_S', 0);								// Default sharpen value. Allows overrid in timthumb-config.php
59
if(! defined('DEFAULT_CC') )				define ('DEFAULT_CC', 'ffffff');						// Default canvas colour. Allows overrid in timthumb-config.php
60
if(! defined('DEFAULT_WIDTH') )				define ('DEFAULT_WIDTH', 100);							// Default thumbnail width. Allows overrid in timthumb-config.php
61
if(! defined('DEFAULT_HEIGHT') )			define ('DEFAULT_HEIGHT', 100);							// Default thumbnail height. Allows overrid in timthumb-config.php
62
 
63
/**
64
 * Additional Parameters:
65
 * LOCAL_FILE_BASE_DIRECTORY = Override the DOCUMENT_ROOT. This is best used in timthumb-config.php
66
 */
67
 
68
//Image compression is enabled if either of these point to valid paths
69
 
70
//These are now disabled by default because the file sizes of PNGs (and GIFs) are much smaller than we used to generate. 
71
//They only work for PNGs. GIFs and JPEGs are not affected.
72
if(! defined('OPTIPNG_ENABLED') ) 		define ('OPTIPNG_ENABLED', false);  
73
if(! defined('OPTIPNG_PATH') ) 			define ('OPTIPNG_PATH', '/usr/bin/optipng'); //This will run first because it gives better compression than pngcrush. 
74
if(! defined('PNGCRUSH_ENABLED') ) 		define ('PNGCRUSH_ENABLED', false); 
75
if(! defined('PNGCRUSH_PATH') ) 		define ('PNGCRUSH_PATH', '/usr/bin/pngcrush'); //This will only run if OPTIPNG_PATH is not set or is not valid
76
 
77
/*
78
	-------====Website Screenshots configuration - BETA====-------
79
 
80
	If you just want image thumbnails and don't want website screenshots, you can safely leave this as is.	
81
 
82
	If you would like to get website screenshots set up, you will need root access to your own server.
83
 
84
	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.
85
	Enable BLOCK_EXTERNAL_LEECHERS so that your site doesn't generate thumbnails for the whole Internet.
86
 
87
	Instructions to get website screenshots enabled on Ubuntu Linux:
88
 
89
	1. Install Xvfb with the following command: sudo apt-get install subversion libqt4-webkit libqt4-dev g++ xvfb
90
	2. Go to a directory where you can download some code
91
	3. Check-out the latest version of CutyCapt with the following command: svn co https://cutycapt.svn.sourceforge.net/svnroot/cutycapt
92
	4. Compile CutyCapt by doing: cd cutycapt/CutyCapt
93
	5. qmake
94
	6. make
95
	7. cp CutyCapt /usr/local/bin/
96
	8. Test it by running: xvfb-run --server-args="-screen 0, 1024x768x24" CutyCapt --url="http://markmaunder.com/" --out=test.png
97
	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:
98
	10. http://yoursite.com/path/to/timthumb.php?src=http://markmaunder.com/&webshot=1
99
 
100
	Notes on performance: 
101
	The first time a webshot loads, it will take a few seconds.
102
	From then on it uses the regular timthumb caching mechanism with the configurable options above
103
	and loading will be very fast.
104
 
105
	--ADVANCED USERS ONLY--
106
	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.
107
	nohup Xvfb :100 -ac -nolisten tcp -screen 0, 1024x768x24 > /dev/null 2>&1 &
108
	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. 
109
	You will need to take responsibility for keeping Xvfb running in case it crashes. (It seems pretty stable)
110
	You will also need to take responsibility for server security if you're running Xvfb as root. 
111
 
112
 
113
*/
114
if(! defined('WEBSHOT_ENABLED') ) 	define ('WEBSHOT_ENABLED', false);			//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.
115
if(! defined('WEBSHOT_CUTYCAPT') ) 	define ('WEBSHOT_CUTYCAPT', '/usr/local/bin/CutyCapt'); //The path to CutyCapt. 
116
if(! defined('WEBSHOT_XVFB') ) 		define ('WEBSHOT_XVFB', '/usr/bin/xvfb-run');		//The path to the Xvfb server
117
if(! defined('WEBSHOT_SCREEN_X') ) 	define ('WEBSHOT_SCREEN_X', '1024');			//1024 works ok
118
if(! defined('WEBSHOT_SCREEN_Y') ) 	define ('WEBSHOT_SCREEN_Y', '768');			//768 works ok
119
if(! defined('WEBSHOT_COLOR_DEPTH') ) 	define ('WEBSHOT_COLOR_DEPTH', '24');			//I haven't tested anything besides 24
120
if(! defined('WEBSHOT_IMAGE_FORMAT') ) 	define ('WEBSHOT_IMAGE_FORMAT', 'png');			//png is about 2.5 times the size of jpg but is a LOT better quality
121
if(! defined('WEBSHOT_TIMEOUT') ) 	define ('WEBSHOT_TIMEOUT', '20');			//Seconds to wait for a webshot
122
if(! defined('WEBSHOT_USER_AGENT') ) 	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"); //I hate to do this, but a non-browser robot user agent might not show what humans see. So we pretend to be Firefox
123
if(! defined('WEBSHOT_JAVASCRIPT_ON') ) define ('WEBSHOT_JAVASCRIPT_ON', true);			//Setting to false might give you a slight speedup and block ads. But it could cause other issues.
124
if(! defined('WEBSHOT_JAVA_ON') ) 	define ('WEBSHOT_JAVA_ON', false);			//Have only tested this as fase
125
if(! defined('WEBSHOT_PLUGINS_ON') ) 	define ('WEBSHOT_PLUGINS_ON', true);			//Enable flash and other plugins
126
if(! defined('WEBSHOT_PROXY') ) 	define ('WEBSHOT_PROXY', '');				//In case you're behind a proxy server. 
127
if(! defined('WEBSHOT_XVFB_RUNNING') )	define ('WEBSHOT_XVFB_RUNNING', false);			//ADVANCED: Enable this if you've got Xvfb running in the background.
128
 
129
 
130
// 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. 
131
if(! isset($ALLOWED_SITES)){
132
	$ALLOWED_SITES = array (
133
		'flickr.com',
134
		'staticflickr.com',
135
		'picasa.com',
136
		'img.youtube.com',
137
		'upload.wikimedia.org',
138
		'photobucket.com',
139
		'imgur.com',
140
		'imageshack.us',
141
		'tinypic.com',
142
		'bhojpurinama.com',
143
		'songspk.name',
144
		'songspk.cc',
145
		'songs.pk',
146
	);
147
}
148
// -------------------------------------------------------------
149
// -------------- STOP EDITING CONFIGURATION HERE --------------
150
// -------------------------------------------------------------
151
 
152
timthumb::start();
153
 
154
class timthumb {
155
	protected $src = "";
156
	protected $is404 = false;
157
	protected $docRoot = "";
158
	protected $lastURLError = false;
159
	protected $localImage = "";
160
	protected $localImageMTime = 0;
161
	protected $url = false;
162
	protected $myHost = "";
163
	protected $isURL = false;
164
	protected $cachefile = '';
165
	protected $errors = array();
166
	protected $toDeletes = array();
167
	protected $cacheDirectory = '';
168
	protected $startTime = 0;
169
	protected $lastBenchTime = 0;
170
	protected $cropTop = false;
171
	protected $salt = "";
172
	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.
173
	protected $filePrependSecurityBlock = "<?php die('Execution denied!'); //"; //Designed to have three letter mime type, space, question mark and greater than symbol appended. 6 bytes total.
174
	protected static $curlDataWritten = 0;
175
	protected static $curlFH = false;
176
	public static function start(){
177
		$tim = new timthumb();
178
		$tim->handleErrors();
179
		$tim->securityChecks();
180
		if($tim->tryBrowserCache()){
181
			exit(0);
182
		}
183
		$tim->handleErrors();
184
		if(FILE_CACHE_ENABLED && $tim->tryServerCache()){
185
			exit(0);
186
		}
187
		$tim->handleErrors();
188
		$tim->run();
189
		$tim->handleErrors();
190
		exit(0);
191
	}
192
	public function __construct(){
193
		global $ALLOWED_SITES;
194
		$this->startTime = microtime(true);
195
		date_default_timezone_set('UTC');
196
		$this->debug(1, "Starting new request from " . $this->getIP() . " to " . $_SERVER['REQUEST_URI']);
197
		$this->calcDocRoot();
198
		//On windows systems I'm assuming fileinode returns an empty string or a number that doesn't change. Check this.
199
		$this->salt = @filemtime(__FILE__) . '-' . @fileinode(__FILE__);
200
		$this->debug(3, "Salt is: " . $this->salt);
201
		if(FILE_CACHE_DIRECTORY){
202
			if(! is_dir(FILE_CACHE_DIRECTORY)){
203
				@mkdir(FILE_CACHE_DIRECTORY);
204
				if(! is_dir(FILE_CACHE_DIRECTORY)){
205
					$this->error("Could not create the file cache directory.");
206
					return false;
207
				}
208
			}
209
			$this->cacheDirectory = FILE_CACHE_DIRECTORY;
210
			if (!touch($this->cacheDirectory . '/index.html')) {
211
				$this->error("Could not create the index.html file - to fix this create an empty file named index.html file in the cache directory.");
212
			}
213
		} else {
214
			$this->cacheDirectory = sys_get_temp_dir();
215
		}
216
		//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. 
217
		$this->cleanCache();
218
 
219
		$this->myHost = preg_replace('/^www\./i', '', $_SERVER['HTTP_HOST']);
220
		$this->src = $this->param('src');
221
		$this->url = parse_url($this->src);
222
		$this->src = preg_replace('/https?:\/\/(?:www\.)?' . $this->myHost . '/i', '', $this->src);
223
 
224
		if(strlen($this->src) <= 3){
225
			$this->error("No image specified");
226
			return false;
227
		}
228
		if(BLOCK_EXTERNAL_LEECHERS && array_key_exists('HTTP_REFERER', $_SERVER) && (! preg_match('/^https?:\/\/(?:www\.)?' . $this->myHost . '(?:$|\/)/i', $_SERVER['HTTP_REFERER']))){
229
			// base64 encoded red image that says 'no hotlinkers'
230
			// nothing to worry about! :)
231
			$imgData = base64_decode("R0lGODlhUAAMAIAAAP8AAP///yH5BAAHAP8ALAAAAABQAAwAAAJpjI+py+0Po5y0OgAMjjv01YUZ\nOGplhWXfNa6JCLnWkXplrcBmW+spbwvaVr/cDyg7IoFC2KbYVC2NQ5MQ4ZNao9Ynzjl9ScNYpneb\nDULB3RP6JuPuaGfuuV4fumf8PuvqFyhYtjdoeFgAADs=");
232
			header('Content-Type: image/gif');
233
			header('Content-Length: ' . strlen($imgData));
234
			header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
235
			header("Pragma: no-cache");
236
			header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
237
			echo $imgData;
238
			return false;
239
			exit(0);
240
		}
241
		if(preg_match('/^https?:\/\/[^\/]+/i', $this->src)){
242
			$this->debug(2, "Is a request for an external URL: " . $this->src);
243
			$this->isURL = true;
244
		} else {
245
			$this->debug(2, "Is a request for an internal file: " . $this->src);
246
		}
247
		if($this->isURL && (! ALLOW_EXTERNAL)){
248
			$this->error("You are not allowed to fetch images from an external website.");
249
			return false;
250
		}
251
		if($this->isURL){
252
			if(ALLOW_ALL_EXTERNAL_SITES){
253
				$this->debug(2, "Fetching from all external sites is enabled.");
254
			} else {
255
				$this->debug(2, "Fetching only from selected external sites is enabled.");
256
				$allowed = false;
257
				foreach($ALLOWED_SITES as $site){
258
					if ((strtolower(substr($this->url['host'],-strlen($site)-1)) === strtolower(".$site")) || (strtolower($this->url['host'])===strtolower($site))) {
259
						$this->debug(3, "URL hostname {$this->url['host']} matches $site so allowing.");
260
						$allowed = true;
261
					}
262
				}
263
				if(! $allowed){
264
					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.");
265
				}
266
			}
267
		}
268
 
269
		$cachePrefix = ($this->isURL ? '_ext_' : '_int_');
270
		if($this->isURL){
271
			$arr = explode('&', $_SERVER ['QUERY_STRING']);
272
			asort($arr);
273
			$this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . implode('', $arr) . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
274
		} else {
275
			$this->localImage = $this->getLocalImagePath($this->src);
276
			if(! $this->localImage){
277
				$this->debug(1, "Could not find the local image: {$this->localImage}");
278
				$this->error("Could not find the internal image you specified.");
279
				$this->set404();
280
				return false;
281
			}
282
			$this->debug(1, "Local image path is {$this->localImage}");
283
			$this->localImageMTime = @filemtime($this->localImage);
284
			//We include the mtime of the local file in case in changes on disk.
285
			$this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . $this->localImageMTime . $_SERVER ['QUERY_STRING'] . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
286
		}
287
		$this->debug(2, "Cache file is: " . $this->cachefile);
288
 
289
		return true;
290
	}
291
	public function __destruct(){
292
		foreach($this->toDeletes as $del){
293
			$this->debug(2, "Deleting temp file $del");
294
			@unlink($del);
295
		}
296
	}
297
	public function run(){
298
		if($this->isURL){
299
			if(! ALLOW_EXTERNAL){
300
				$this->debug(1, "Got a request for an external image but ALLOW_EXTERNAL is disabled so returning error msg.");
301
				$this->error("You are not allowed to fetch images from an external website.");
302
				return false;
303
			}
304
			$this->debug(3, "Got request for external image. Starting serveExternalImage.");
305
			if($this->param('webshot')){
306
				if(WEBSHOT_ENABLED){
307
					$this->debug(3, "webshot param is set, so we're going to take a webshot.");
308
					$this->serveWebshot();
309
				} else {
310
					$this->error("You added the webshot parameter but webshots are disabled on this server. You need to set WEBSHOT_ENABLED == true to enable webshots.");
311
				}
312
			} else {
313
				$this->debug(3, "webshot is NOT set so we're going to try to fetch a regular image.");
314
				$this->serveExternalImage();
315
 
316
			}
317
		} else {
318
			$this->debug(3, "Got request for internal image. Starting serveInternalImage()");
319
			$this->serveInternalImage();
320
		}
321
		return true;
322
	}
323
	protected function handleErrors(){
324
		if($this->haveErrors()){ 
325
			if(NOT_FOUND_IMAGE && $this->is404()){
326
				if($this->serveImg(NOT_FOUND_IMAGE)){
327
					exit(0);
328
				} else {
329
					$this->error("Additionally, the 404 image that is configured could not be found or there was an error serving it.");
330
				}
331
			}
332
			if(ERROR_IMAGE){
333
				if($this->serveImg(ERROR_IMAGE)){
334
					exit(0);
335
				} else {
336
					$this->error("Additionally, the error image that is configured could not be found or there was an error serving it.");
337
				}
338
			}
339
			$this->serveErrors(); 
340
			exit(0); 
341
		}
342
		return false;
343
	}
344
	protected function tryBrowserCache(){
345
		if(BROWSER_CACHE_DISABLE){ $this->debug(3, "Browser caching is disabled"); return false; }
346
		if(!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ){
347
			$this->debug(3, "Got a conditional get");
348
			$mtime = false;
349
			//We've already checked if the real file exists in the constructor
350
			if(! is_file($this->cachefile)){
351
				//If we don't have something cached, regenerate the cached image.
352
				return false;
353
			}
354
			if($this->localImageMTime){
355
				$mtime = $this->localImageMTime;
356
				$this->debug(3, "Local real file's modification time is $mtime");
357
			} else if(is_file($this->cachefile)){ //If it's not a local request then use the mtime of the cached file to determine the 304
358
				$mtime = @filemtime($this->cachefile);
359
				$this->debug(3, "Cached file's modification time is $mtime");
360
			}
361
			if(! $mtime){ return false; }
362
 
363
			$iftime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
364
			$this->debug(3, "The conditional get's if-modified-since unixtime is $iftime");
365
			if($iftime < 1){
366
				$this->debug(3, "Got an invalid conditional get modified since time. Returning false.");
367
				return false;
368
			}
369
			if($iftime < $mtime){ //Real file or cache file has been modified since last request, so force refetch.
370
				$this->debug(3, "File has been modified since last fetch.");
371
				return false;
372
			} else { //Otherwise serve a 304
373
				$this->debug(3, "File has not been modified since last get, so serving a 304.");
374
				header ($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
375
				$this->debug(1, "Returning 304 not modified");
376
				return true;
377
			}
378
		}
379
		return false;
380
	}
381
	protected function tryServerCache(){
382
		$this->debug(3, "Trying server cache");
383
		if(file_exists($this->cachefile)){
384
			$this->debug(3, "Cachefile {$this->cachefile} exists");
385
			if($this->isURL){
386
				$this->debug(3, "This is an external request, so checking if the cachefile is empty which means the request failed previously.");
387
				if(filesize($this->cachefile) < 1){
388
					$this->debug(3, "Found an empty cachefile indicating a failed earlier request. Checking how old it is.");
389
					//Fetching error occured previously
390
					if(time() - @filemtime($this->cachefile) > WAIT_BETWEEN_FETCH_ERRORS){
391
						$this->debug(3, "File is older than " . WAIT_BETWEEN_FETCH_ERRORS . " seconds. Deleting and returning false so app can try and load file.");
392
						@unlink($this->cachefile);
393
						return false; //to indicate we didn't serve from cache and app should try and load
394
					} else {
395
						$this->debug(3, "Empty cachefile is still fresh so returning message saying we had an error fetching this image from remote host.");
396
						$this->set404();
397
						$this->error("An error occured fetching image.");
398
						return false; 
399
					}
400
				}
401
			} else {
402
				$this->debug(3, "Trying to serve cachefile {$this->cachefile}");
403
			}
404
			if($this->serveCacheFile()){
405
				$this->debug(3, "Succesfully served cachefile {$this->cachefile}");
406
				return true;
407
			} else {
408
				$this->debug(3, "Failed to serve cachefile {$this->cachefile} - Deleting it from cache.");
409
				//Image serving failed. We can't retry at this point, but lets remove it from cache so the next request recreates it
410
				@unlink($this->cachefile);
411
				return true;
412
			}
413
		}
414
	}
415
	protected function error($err){
416
		$this->debug(3, "Adding error message: $err");
417
		$this->errors[] = $err;
418
		return false;
419
 
420
	}
421
	protected function haveErrors(){
422
		if(sizeof($this->errors) > 0){
423
			return true;
424
		}
425
		return false;
426
	}
427
	protected function serveErrors(){
428
		header ($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
429
		if ( ! DISPLAY_ERROR_MESSAGES ) {
430
			return;
431
		}
432
		$html = '<ul>';
433
		foreach($this->errors as $err){
434
			$html .= '<li>' . htmlentities($err) . '</li>';
435
		}
436
		$html .= '</ul>';
437
		echo '<h1>A TimThumb error has occured</h1>The following error(s) occured:<br />' . $html . '<br />';
438
		echo '<br />Query String : ' . htmlentities( $_SERVER['QUERY_STRING'], ENT_QUOTES );
439
		echo '<br />TimThumb version : ' . VERSION . '</pre>';
440
	}
441
	protected function serveInternalImage(){
442
		$this->debug(3, "Local image path is $this->localImage");
443
		if(! $this->localImage){
444
			$this->sanityFail("localImage not set after verifying it earlier in the code.");
445
			return false;
446
		}
447
		$fileSize = filesize($this->localImage);
448
		if($fileSize > MAX_FILE_SIZE){
449
			$this->error("The file you specified is greater than the maximum allowed file size.");
450
			return false;
451
		}
452
		if($fileSize <= 0){
453
			$this->error("The file you specified is <= 0 bytes.");
454
			return false;
455
		}
456
		$this->debug(3, "Calling processImageAndWriteToCache() for local image.");
457
		if($this->processImageAndWriteToCache($this->localImage)){
458
			$this->serveCacheFile();
459
			return true;
460
		} else { 
461
			return false;
462
		}
463
	}
464
	protected function cleanCache(){
465
		if (FILE_CACHE_TIME_BETWEEN_CLEANS < 0) {
466
			return;
467
		}
468
		$this->debug(3, "cleanCache() called");
469
		$lastCleanFile = $this->cacheDirectory . '/timthumb_cacheLastCleanTime.touch';
470
 
471
		//If this is a new timthumb installation we need to create the file
472
		if(! is_file($lastCleanFile)){
473
			$this->debug(1, "File tracking last clean doesn't exist. Creating $lastCleanFile");
474
			if (!touch($lastCleanFile)) {
475
				$this->error("Could not create cache clean timestamp file.");
476
			}
477
			return;
478
		}
479
		if(@filemtime($lastCleanFile) < (time() - FILE_CACHE_TIME_BETWEEN_CLEANS) ){ //Cache was last cleaned more than 1 day ago
480
			$this->debug(1, "Cache was last cleaned more than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago. Cleaning now.");
481
			// Very slight race condition here, but worst case we'll have 2 or 3 servers cleaning the cache simultaneously once a day.
482
			if (!touch($lastCleanFile)) {
483
				$this->error("Could not create cache clean timestamp file.");
484
			}
485
			$files = glob($this->cacheDirectory . '/*' . FILE_CACHE_SUFFIX);
486
			if ($files) {
487
				$timeAgo = time() - FILE_CACHE_MAX_FILE_AGE;
488
				foreach($files as $file){
489
					if(@filemtime($file) < $timeAgo){
490
						$this->debug(3, "Deleting cache file $file older than max age: " . FILE_CACHE_MAX_FILE_AGE . " seconds");
491
						@unlink($file);
492
					}
493
				}
494
			}
495
			return true;
496
		} else {
497
			$this->debug(3, "Cache was cleaned less than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago so no cleaning needed.");
498
		}
499
		return false;
500
	}
501
	protected function processImageAndWriteToCache($localImage){
502
		$sData = getimagesize($localImage);
503
		$origType = $sData[2];
504
		$mimeType = $sData['mime'];
505
 
506
		$this->debug(3, "Mime type of image is $mimeType");
507
		if(! preg_match('/^image\/(?:gif|jpg|jpeg|png)$/i', $mimeType)){
508
			return $this->error("The image being resized is not a valid gif, jpg or png.");
509
		}
510
 
511
		if (!function_exists ('imagecreatetruecolor')) {
512
		    return $this->error('GD Library Error: imagecreatetruecolor does not exist - please contact your webhost and ask them to install the GD library');
513
		}
514
 
515
		if (function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
516
			$imageFilters = array (
517
				1 => array (IMG_FILTER_NEGATE, 0),
518
				2 => array (IMG_FILTER_GRAYSCALE, 0),
519
				3 => array (IMG_FILTER_BRIGHTNESS, 1),
520
				4 => array (IMG_FILTER_CONTRAST, 1),
521
				5 => array (IMG_FILTER_COLORIZE, 4),
522
				6 => array (IMG_FILTER_EDGEDETECT, 0),
523
				7 => array (IMG_FILTER_EMBOSS, 0),
524
				8 => array (IMG_FILTER_GAUSSIAN_BLUR, 0),
525
				9 => array (IMG_FILTER_SELECTIVE_BLUR, 0),
526
				10 => array (IMG_FILTER_MEAN_REMOVAL, 0),
527
				11 => array (IMG_FILTER_SMOOTH, 0),
528
			);
529
		}
530
 
531
		// get standard input properties		
532
		$new_width =  (int) abs ($this->param('w', 0));
533
		$new_height = (int) abs ($this->param('h', 0));
534
		$zoom_crop = (int) $this->param('zc', DEFAULT_ZC);
535
		$quality = (int) abs ($this->param('q', DEFAULT_Q));
536
		$align = $this->cropTop ? 't' : $this->param('a', 'c');
537
		$filters = $this->param('f', DEFAULT_F);
538
		$sharpen = (bool) $this->param('s', DEFAULT_S);
539
		$canvas_color = $this->param('cc', DEFAULT_CC);
540
		$canvas_trans = (bool) $this->param('ct', '1');
541
 
542
		// set default width and height if neither are set already
543
		if ($new_width == 0 && $new_height == 0) {
544
		    $new_width = (int) DEFAULT_WIDTH;
545
		    $new_height = (int) DEFAULT_HEIGHT;
546
		}
547
 
548
		// ensure size limits can not be abused
549
		$new_width = min ($new_width, MAX_WIDTH);
550
		$new_height = min ($new_height, MAX_HEIGHT);
551
 
552
		// set memory limit to be able to have enough space to resize larger images
553
		$this->setMemoryLimit();
554
 
555
		// open the existing image
556
		$image = $this->openImage ($mimeType, $localImage);
557
		if ($image === false) {
558
			return $this->error('Unable to open image.');
559
		}
560
 
561
		// Get original width and height
562
		$width = imagesx ($image);
563
		$height = imagesy ($image);
564
		$origin_x = 0;
565
		$origin_y = 0;
566
 
567
		// generate new w/h if not provided
568
		if ($new_width && !$new_height) {
569
			$new_height = floor ($height * ($new_width / $width));
570
		} else if ($new_height && !$new_width) {
571
			$new_width = floor ($width * ($new_height / $height));
572
		}
573
 
574
		// scale down and add borders
575
		if ($zoom_crop == 3) {
576
 
577
			$final_height = $height * ($new_width / $width);
578
 
579
			if ($final_height > $new_height) {
580
				$new_width = $width * ($new_height / $height);
581
			} else {
582
				$new_height = $final_height;
583
			}
584
 
585
		}
586
 
587
		// create a new true color image
588
		$canvas = imagecreatetruecolor ($new_width, $new_height);
589
		imagealphablending ($canvas, false);
590
 
591
		if (strlen($canvas_color) == 3) { //if is 3-char notation, edit string into 6-char notation
592
			$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); 
593
		} else if (strlen($canvas_color) != 6) {
594
			$canvas_color = DEFAULT_CC; // on error return default canvas color
595
 		}
596
 
597
		$canvas_color_R = hexdec (substr ($canvas_color, 0, 2));
598
		$canvas_color_G = hexdec (substr ($canvas_color, 2, 2));
599
		$canvas_color_B = hexdec (substr ($canvas_color, 4, 2));
600
 
601
		// Create a new transparent color for image
602
	    // If is a png and PNG_IS_TRANSPARENT is false then remove the alpha transparency 
603
		// (and if is set a canvas color show it in the background)
604
		if(preg_match('/^image\/png$/i', $mimeType) && !PNG_IS_TRANSPARENT && $canvas_trans){ 
605
			$color = imagecolorallocatealpha ($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 127);		
606
		}else{
607
			$color = imagecolorallocatealpha ($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 0);
608
		}
609
 
610
 
611
		// Completely fill the background of the new image with allocated color.
612
		imagefill ($canvas, 0, 0, $color);
613
 
614
		// scale down and add borders
615
		if ($zoom_crop == 2) {
616
 
617
			$final_height = $height * ($new_width / $width);
618
 
619
			if ($final_height > $new_height) {
620
 
621
				$origin_x = $new_width / 2;
622
				$new_width = $width * ($new_height / $height);
623
				$origin_x = round ($origin_x - ($new_width / 2));
624
 
625
			} else {
626
 
627
				$origin_y = $new_height / 2;
628
				$new_height = $final_height;
629
				$origin_y = round ($origin_y - ($new_height / 2));
630
 
631
			}
632
 
633
		}
634
 
635
		// Restore transparency blending
636
		imagesavealpha ($canvas, true);
637
 
638
		if ($zoom_crop > 0) {
639
 
640
			$src_x = $src_y = 0;
641
			$src_w = $width;
642
			$src_h = $height;
643
 
644
			$cmp_x = $width / $new_width;
645
			$cmp_y = $height / $new_height;
646
 
647
			// calculate x or y coordinate and width or height of source
648
			if ($cmp_x > $cmp_y) {
649
 
650
				$src_w = round ($width / $cmp_x * $cmp_y);
651
				$src_x = round (($width - ($width / $cmp_x * $cmp_y)) / 2);
652
 
653
			} else if ($cmp_y > $cmp_x) {
654
 
655
				$src_h = round ($height / $cmp_y * $cmp_x);
656
				$src_y = round (($height - ($height / $cmp_y * $cmp_x)) / 2);
657
 
658
			}
659
 
660
			// positional cropping!
661
			if ($align) {
662
				if (strpos ($align, 't') !== false) {
663
					$src_y = 0;
664
				}
665
				if (strpos ($align, 'b') !== false) {
666
					$src_y = $height - $src_h;
667
				}
668
				if (strpos ($align, 'l') !== false) {
669
					$src_x = 0;
670
				}
671
				if (strpos ($align, 'r') !== false) {
672
					$src_x = $width - $src_w;
673
				}
674
			}
675
 
676
			imagecopyresampled ($canvas, $image, $origin_x, $origin_y, $src_x, $src_y, $new_width, $new_height, $src_w, $src_h);
677
 
678
		} else {
679
 
680
			// copy and resize part of an image with resampling
681
			imagecopyresampled ($canvas, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
682
 
683
		}
684
 
685
		if ($filters != '' && function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
686
			// apply filters to image
687
			$filterList = explode ('|', $filters);
688
			foreach ($filterList as $fl) {
689
 
690
				$filterSettings = explode (',', $fl);
691
				if (isset ($imageFilters[$filterSettings[0]])) {
692
 
693
					for ($i = 0; $i < 4; $i ++) {
694
						if (!isset ($filterSettings[$i])) {
695
							$filterSettings[$i] = null;
696
						} else {
697
							$filterSettings[$i] = (int) $filterSettings[$i];
698
						}
699
					}
700
 
701
					switch ($imageFilters[$filterSettings[0]][1]) {
702
 
703
						case 1:
704
 
705
							imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1]);
706
							break;
707
 
708
						case 2:
709
 
710
							imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2]);
711
							break;
712
 
713
						case 3:
714
 
715
							imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3]);
716
							break;
717
 
718
						case 4:
719
 
720
							imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3], $filterSettings[4]);
721
							break;
722
 
723
						default:
724
 
725
							imagefilter ($canvas, $imageFilters[$filterSettings[0]][0]);
726
							break;
727
 
728
					}
729
				}
730
			}
731
		}
732
 
733
		// sharpen image
734
		if ($sharpen && function_exists ('imageconvolution')) {
735
 
736
			$sharpenMatrix = array (
737
					array (-1,-1,-1),
738
					array (-1,16,-1),
739
					array (-1,-1,-1),
740
					);
741
 
742
			$divisor = 8;
743
			$offset = 0;
744
 
745
			imageconvolution ($canvas, $sharpenMatrix, $divisor, $offset);
746
 
747
		}
748
		//Straight from Wordpress core code. Reduces filesize by up to 70% for PNG's
749
		if ( (IMAGETYPE_PNG == $origType || IMAGETYPE_GIF == $origType) && function_exists('imageistruecolor') && !imageistruecolor( $image ) && imagecolortransparent( $image ) > 0 ){
750
			imagetruecolortopalette( $canvas, false, imagecolorstotal( $image ) );
751
		}
752
 
753
		$imgType = "";
754
		$tempfile = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
755
		if(preg_match('/^image\/(?:jpg|jpeg)$/i', $mimeType)){ 
756
			$imgType = 'jpg';
757
			imagejpeg($canvas, $tempfile, $quality); 
758
		} else if(preg_match('/^image\/png$/i', $mimeType)){ 
759
			$imgType = 'png';
760
			imagepng($canvas, $tempfile, floor($quality * 0.09));
761
		} else if(preg_match('/^image\/gif$/i', $mimeType)){
762
			$imgType = 'gif';
763
			imagegif($canvas, $tempfile);
764
		} else {
765
			return $this->sanityFail("Could not match mime type after verifying it previously.");
766
		}
767
 
768
		if($imgType == 'png' && OPTIPNG_ENABLED && OPTIPNG_PATH && @is_file(OPTIPNG_PATH)){
769
			$exec = OPTIPNG_PATH;
770
			$this->debug(3, "optipng'ing $tempfile");
771
			$presize = filesize($tempfile);
772
			$out = `$exec -o1 $tempfile`; //you can use up to -o7 but it really slows things down
773
			clearstatcache();
774
			$aftersize = filesize($tempfile);
775
			$sizeDrop = $presize - $aftersize;
776
			if($sizeDrop > 0){
777
				$this->debug(1, "optipng reduced size by $sizeDrop");
778
			} else if($sizeDrop < 0){
779
				$this->debug(1, "optipng increased size! Difference was: $sizeDrop");
780
			} else {
781
				$this->debug(1, "optipng did not change image size.");
782
			}
783
		} else if($imgType == 'png' && PNGCRUSH_ENABLED && PNGCRUSH_PATH && @is_file(PNGCRUSH_PATH)){
784
			$exec = PNGCRUSH_PATH;
785
			$tempfile2 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
786
			$this->debug(3, "pngcrush'ing $tempfile to $tempfile2");
787
			$out = `$exec $tempfile $tempfile2`;
788
			$todel = "";
789
			if(is_file($tempfile2)){
790
				$sizeDrop = filesize($tempfile) - filesize($tempfile2);
791
				if($sizeDrop > 0){
792
					$this->debug(1, "pngcrush was succesful and gave a $sizeDrop byte size reduction");
793
					$todel = $tempfile;
794
					$tempfile = $tempfile2;
795
				} else {
796
					$this->debug(1, "pngcrush did not reduce file size. Difference was $sizeDrop bytes.");
797
					$todel = $tempfile2;
798
				}
799
			} else {
800
				$this->debug(3, "pngcrush failed with output: $out");
801
				$todel = $tempfile2;
802
			}
803
			@unlink($todel);
804
		}
805
 
806
		$this->debug(3, "Rewriting image with security header.");
807
		$tempfile4 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
808
		$context = stream_context_create ();
809
		$fp = fopen($tempfile,'r',0,$context);
810
		file_put_contents($tempfile4, $this->filePrependSecurityBlock . $imgType . ' ?' . '>'); //6 extra bytes, first 3 being image type 
811
		file_put_contents($tempfile4, $fp, FILE_APPEND);
812
		fclose($fp);
813
		@unlink($tempfile);
814
		$this->debug(3, "Locking and replacing cache file.");
815
		$lockFile = $this->cachefile . '.lock';
816
		$fh = fopen($lockFile, 'w');
817
		if(! $fh){
818
			return $this->error("Could not open the lockfile for writing an image.");
819
		}
820
		if(flock($fh, LOCK_EX)){
821
			@unlink($this->cachefile); //rename generally overwrites, but doing this in case of platform specific quirks. File might not exist yet.
822
			rename($tempfile4, $this->cachefile);
823
			flock($fh, LOCK_UN);
824
			fclose($fh);
825
			@unlink($lockFile);
826
		} else {
827
			fclose($fh);
828
			@unlink($lockFile);
829
			@unlink($tempfile4);
830
			return $this->error("Could not get a lock for writing.");
831
		}
832
		$this->debug(3, "Done image replace with security header. Cleaning up and running cleanCache()");
833
		imagedestroy($canvas);
834
		imagedestroy($image);
835
		return true;
836
	}
837
	protected function calcDocRoot(){
838
		$docRoot = @$_SERVER['DOCUMENT_ROOT'];
839
		if (defined('LOCAL_FILE_BASE_DIRECTORY')) {
840
			$docRoot = LOCAL_FILE_BASE_DIRECTORY;   
841
		}
842
		if(!isset($docRoot)){ 
843
			$this->debug(3, "DOCUMENT_ROOT is not set. This is probably windows. Starting search 1.");
844
			if(isset($_SERVER['SCRIPT_FILENAME'])){
845
				$docRoot = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0-strlen($_SERVER['PHP_SELF'])));
846
				$this->debug(3, "Generated docRoot using SCRIPT_FILENAME and PHP_SELF as: $docRoot");
847
			} 
848
		}
849
		if(!isset($docRoot)){ 
850
			$this->debug(3, "DOCUMENT_ROOT still is not set. Starting search 2.");
851
			if(isset($_SERVER['PATH_TRANSLATED'])){
852
				$docRoot = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0-strlen($_SERVER['PHP_SELF'])));
853
				$this->debug(3, "Generated docRoot using PATH_TRANSLATED and PHP_SELF as: $docRoot");
854
			} 
855
		}
856
		if($docRoot && $_SERVER['DOCUMENT_ROOT'] != '/'){ $docRoot = preg_replace('/\/$/', '', $docRoot); }
857
		$this->debug(3, "Doc root is: " . $docRoot);
858
		$this->docRoot = $docRoot;
859
 
860
	}
861
	protected function getLocalImagePath($src){
862
		$src = ltrim($src, '/'); //strip off the leading '/'
863
		if(! $this->docRoot){
864
			$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.");
865
			//We don't support serving images outside the current dir if we don't have a doc root for security reasons.
866
			$file = preg_replace('/^.*?([^\/\\\\]+)$/', '$1', $src); //strip off any path info and just leave the filename.
867
			if(is_file($file)){
868
				return $this->realpath($file);
869
			}
870
			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.");
871
		} else if ( ! is_dir( $this->docRoot ) ) {
872
			$this->error("Server path does not exist. Ensure variable \$_SERVER['DOCUMENT_ROOT'] is set correctly");
873
		}
874
 
875
		//Do not go past this point without docRoot set
876
 
877
		//Try src under docRoot
878
		if(file_exists ($this->docRoot . '/' . $src)) {
879
			$this->debug(3, "Found file as " . $this->docRoot . '/' . $src);
880
			$real = $this->realpath($this->docRoot . '/' . $src);
881
			if(stripos($real, $this->docRoot) === 0){
882
				return $real;
883
			} else {
884
				$this->debug(1, "Security block: The file specified occurs outside the document root.");
885
				//allow search to continue
886
			}
887
		}
888
		//Check absolute paths and then verify the real path is under doc root
889
		$absolute = $this->realpath('/' . $src);
890
		if($absolute && file_exists($absolute)){ //realpath does file_exists check, so can probably skip the exists check here
891
			$this->debug(3, "Found absolute path: $absolute");
892
			if(! $this->docRoot){ $this->sanityFail("docRoot not set when checking absolute path."); }
893
			if(stripos($absolute, $this->docRoot) === 0){
894
				return $absolute;
895
			} else {
896
				$this->debug(1, "Security block: The file specified occurs outside the document root.");
897
				//and continue search
898
			}
899
		}
900
 
901
		$base = $this->docRoot;
902
 
903
		// account for Windows directory structure
904
		if (strstr($_SERVER['SCRIPT_FILENAME'],':')) {
905
			$sub_directories = explode('\\', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
906
		} else {
907
			$sub_directories = explode('/', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
908
		}
909
 
910
		foreach ($sub_directories as $sub){
911
			$base .= $sub . '/';
912
			$this->debug(3, "Trying file as: " . $base . $src);
913
			if(file_exists($base . $src)){
914
				$this->debug(3, "Found file as: " . $base . $src);
915
				$real = $this->realpath($base . $src);
916
				if(stripos($real, $this->realpath($this->docRoot)) === 0){
917
					return $real;
918
				} else {
919
					$this->debug(1, "Security block: The file specified occurs outside the document root.");
920
					//And continue search
921
				}
922
			}
923
		}
924
		return false;
925
	}
926
	protected function realpath($path){
927
		//try to remove any relative paths
928
		$remove_relatives = '/\w+\/\.\.\//';
929
		while(preg_match($remove_relatives,$path)){
930
		    $path = preg_replace($remove_relatives, '', $path);
931
		}
932
		//if any remain use PHP realpath to strip them out, otherwise return $path
933
		//if using realpath, any symlinks will also be resolved
934
		return preg_match('#^\.\./|/\.\./#', $path) ? realpath($path) : $path;
935
	}
936
	protected function toDelete($name){
937
		$this->debug(3, "Scheduling file $name to delete on destruct.");
938
		$this->toDeletes[] = $name;
939
	}
940
	protected function serveWebshot(){
941
		$this->debug(3, "Starting serveWebshot");
942
		$instr = "Please follow the instructions at http://code.google.com/p/timthumb/ to set your server up for taking website screenshots.";
943
		if(! is_file(WEBSHOT_CUTYCAPT)){
944
			return $this->error("CutyCapt is not installed. $instr");
945
		}
946
		if(! is_file(WEBSHOT_XVFB)){
947
			return $this->Error("Xvfb is not installed. $instr");
948
		}
949
		$cuty = WEBSHOT_CUTYCAPT;
950
		$xv = WEBSHOT_XVFB;
951
		$screenX = WEBSHOT_SCREEN_X;
952
		$screenY = WEBSHOT_SCREEN_Y;
953
		$colDepth = WEBSHOT_COLOR_DEPTH;
954
		$format = WEBSHOT_IMAGE_FORMAT;
955
		$timeout = WEBSHOT_TIMEOUT * 1000;
956
		$ua = WEBSHOT_USER_AGENT;
957
		$jsOn = WEBSHOT_JAVASCRIPT_ON ? 'on' : 'off';
958
		$javaOn = WEBSHOT_JAVA_ON ? 'on' : 'off';
959
		$pluginsOn = WEBSHOT_PLUGINS_ON ? 'on' : 'off';
960
		$proxy = WEBSHOT_PROXY ? ' --http-proxy=' . WEBSHOT_PROXY : '';
961
		$tempfile = tempnam($this->cacheDirectory, 'timthumb_webshot');
962
		$url = $this->src;
963
		if(! preg_match('/^https?:\/\/[a-zA-Z0-9\.\-]+/i', $url)){
964
			return $this->error("Invalid URL supplied.");
965
		}
966
		$url = preg_replace('/[^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=]+/', '', $url); //RFC 3986
967
		//Very important we don't allow injection of shell commands here. URL is between quotes and we are only allowing through chars allowed by a the RFC 
968
		// which AFAIKT can't be used for shell injection. 
969
		if(WEBSHOT_XVFB_RUNNING){
970
			putenv('DISPLAY=:100.0');
971
			$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";
972
		} else {
973
			$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";
974
		}
975
		$this->debug(3, "Executing command: $command");
976
		$out = `$command`;
977
		$this->debug(3, "Received output: $out");
978
		if(! is_file($tempfile)){
979
			$this->set404();
980
			return $this->error("The command to create a thumbnail failed.");
981
		}
982
		$this->cropTop = true;
983
		if($this->processImageAndWriteToCache($tempfile)){
984
			$this->debug(3, "Image processed succesfully. Serving from cache");
985
			return $this->serveCacheFile();
986
		} else {
987
			return false;
988
		}
989
	}
990
	protected function serveExternalImage(){
991
		if(! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+/i', $this->src)){
992
			$this->error("Invalid URL supplied.");
993
			return false;
994
		}
995
		$tempfile = tempnam($this->cacheDirectory, 'timthumb');
996
		$this->debug(3, "Fetching external image into temporary file $tempfile");
997
		$this->toDelete($tempfile);
998
		#fetch file here
999
		if(! $this->getURL($this->src, $tempfile)){
1000
			@unlink($this->cachefile);
1001
			touch($this->cachefile);
1002
			$this->debug(3, "Error fetching URL: " . $this->lastURLError);
1003
			$this->error("Error reading the URL you specified from remote host." . $this->lastURLError);
1004
			return false;
1005
		}
1006
 
1007
		$mimeType = $this->getMimeType($tempfile);
1008
		if(! preg_match("/^image\/(?:jpg|jpeg|gif|png)$/i", $mimeType)){
1009
			$this->debug(3, "Remote file has invalid mime type: $mimeType");
1010
			@unlink($this->cachefile);
1011
			touch($this->cachefile);
1012
			$this->error("The remote file is not a valid image. Mimetype = '" . $mimeType . "'" . $tempfile);
1013
			return false;
1014
		}
1015
		if($this->processImageAndWriteToCache($tempfile)){
1016
			$this->debug(3, "Image processed succesfully. Serving from cache");
1017
			return $this->serveCacheFile();
1018
		} else {
1019
			return false;
1020
		}
1021
	}
1022
	public static function curlWrite($h, $d){
1023
		fwrite(self::$curlFH, $d);
1024
		self::$curlDataWritten += strlen($d);
1025
		if(self::$curlDataWritten > MAX_FILE_SIZE){
1026
			return 0;
1027
		} else {
1028
			return strlen($d);
1029
		}
1030
	}
1031
	protected function serveCacheFile(){
1032
		$this->debug(3, "Serving {$this->cachefile}");
1033
		if(! is_file($this->cachefile)){
1034
			$this->error("serveCacheFile called in timthumb but we couldn't find the cached file.");
1035
			return false;
1036
		}
1037
		$fp = fopen($this->cachefile, 'rb');
1038
		if(! $fp){ return $this->error("Could not open cachefile."); }
1039
		fseek($fp, strlen($this->filePrependSecurityBlock), SEEK_SET);
1040
		$imgType = fread($fp, 3);
1041
		fseek($fp, 3, SEEK_CUR);
1042
		if(ftell($fp) != strlen($this->filePrependSecurityBlock) + 6){
1043
			@unlink($this->cachefile);
1044
			return $this->error("The cached image file seems to be corrupt.");
1045
		}
1046
		$imageDataSize = filesize($this->cachefile) - (strlen($this->filePrependSecurityBlock) + 6);
1047
		$this->sendImageHeaders($imgType, $imageDataSize);
1048
		$bytesSent = @fpassthru($fp);
1049
		fclose($fp);
1050
		if($bytesSent > 0){
1051
			return true;
1052
		}
1053
		$content = file_get_contents ($this->cachefile);
1054
		if ($content != FALSE) {
1055
			$content = substr($content, strlen($this->filePrependSecurityBlock) + 6);
1056
			echo $content;
1057
			$this->debug(3, "Served using file_get_contents and echo");
1058
			return true;
1059
		} else {
1060
			$this->error("Cache file could not be loaded.");
1061
			return false;
1062
		}
1063
	}
1064
	protected function sendImageHeaders($mimeType, $dataSize){
1065
		if(! preg_match('/^image\//i', $mimeType)){
1066
			$mimeType = 'image/' . $mimeType;
1067
		}
1068
		if(strtolower($mimeType) == 'image/jpg'){
1069
			$mimeType = 'image/jpeg';
1070
		}
1071
		$gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +10 days')) . ' GMT';
1072
		$gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
1073
		// send content headers then display image
1074
		header ('Content-Type: ' . $mimeType);
1075
		header ('Accept-Ranges: none'); //Changed this because we don't accept range requests
1076
		header ('Last-Modified: ' . $gmdate_modified);
1077
		header ('Content-Length: ' . $dataSize);
1078
		if(BROWSER_CACHE_DISABLE){
1079
			$this->debug(3, "Browser cache is disabled so setting non-caching headers.");
1080
			header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1081
			header("Pragma: no-cache");
1082
			header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
1083
		} else {
1084
			$this->debug(3, "Browser caching is enabled");
1085
			header('Cache-Control: max-age=' . BROWSER_CACHE_MAX_AGE . ', must-revalidate');
1086
			header('Expires: ' . $gmdate_expires);
1087
		}
1088
		return true;
1089
	}
1090
	protected function securityChecks(){
1091
	}
1092
	protected function param($property, $default = ''){
1093
		if (isset ($_GET[$property])) {
1094
			return $_GET[$property];
1095
		} else {
1096
			return $default;
1097
		}
1098
	}
1099
	protected function openImage($mimeType, $src){
1100
		switch ($mimeType) {
1101
			case 'image/jpeg':
1102
				$image = imagecreatefromjpeg ($src);
1103
				break;
1104
 
1105
			case 'image/png':
1106
				$image = imagecreatefrompng ($src);
1107
				imagealphablending( $image, true );
1108
				imagesavealpha( $image, true );
1109
				break;
1110
 
1111
			case 'image/gif':
1112
				$image = imagecreatefromgif ($src);
1113
				break;
1114
 
1115
			default:
1116
				$this->error("Unrecognised mimeType");
1117
		}
1118
 
1119
		return $image;
1120
	}
1121
	protected function getIP(){
1122
		$rem = @$_SERVER["REMOTE_ADDR"];
1123
		$ff = @$_SERVER["HTTP_X_FORWARDED_FOR"];
1124
		$ci = @$_SERVER["HTTP_CLIENT_IP"];
1125
		if(preg_match('/^(?:192\.168|172\.16|10\.|127\.)/', $rem)){ 
1126
			if($ff){ return $ff; }
1127
			if($ci){ return $ci; }
1128
			return $rem;
1129
		} else {
1130
			if($rem){ return $rem; }
1131
			if($ff){ return $ff; }
1132
			if($ci){ return $ci; }
1133
			return "UNKNOWN";
1134
		}
1135
	}
1136
	protected function debug($level, $msg){
1137
		if(DEBUG_ON && $level <= DEBUG_LEVEL){
1138
			$execTime = sprintf('%.6f', microtime(true) - $this->startTime);
1139
			$tick = sprintf('%.6f', 0);
1140
			if($this->lastBenchTime > 0){
1141
				$tick = sprintf('%.6f', microtime(true) - $this->lastBenchTime);
1142
			}
1143
			$this->lastBenchTime = microtime(true);
1144
			error_log("TimThumb Debug line " . __LINE__ . " [$execTime : $tick]: $msg");
1145
		}
1146
	}
1147
	protected function sanityFail($msg){
1148
		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");
1149
	}
1150
	protected function getMimeType($file){
1151
		$info = getimagesize($file);
1152
		if(is_array($info) && $info['mime']){
1153
			return $info['mime'];
1154
		}
1155
		return '';
1156
	}
1157
	protected function setMemoryLimit(){
1158
		$inimem = ini_get('memory_limit');
1159
		$inibytes = timthumb::returnBytes($inimem);
1160
		$ourbytes = timthumb::returnBytes(MEMORY_LIMIT);
1161
		if($inibytes < $ourbytes){
1162
			ini_set ('memory_limit', MEMORY_LIMIT);
1163
			$this->debug(3, "Increased memory from $inimem to " . MEMORY_LIMIT);
1164
		} else {
1165
			$this->debug(3, "Not adjusting memory size because the current setting is " . $inimem . " and our size of " . MEMORY_LIMIT . " is smaller.");
1166
		}
1167
	}
1168
	protected static function returnBytes($size_str){
1169
		switch (substr ($size_str, -1))
1170
		{
1171
			case 'M': case 'm': return (int)$size_str * 1048576;
1172
			case 'K': case 'k': return (int)$size_str * 1024;
1173
			case 'G': case 'g': return (int)$size_str * 1073741824;
1174
			default: return $size_str;
1175
		}
1176
	}
1177
 
1178
	protected function getURL($url, $tempfile){
1179
		$this->lastURLError = false;
1180
		$url = preg_replace('/ /', '%20', $url);
1181
		if(function_exists('curl_init')){
1182
			$this->debug(3, "Curl is installed so using it to fetch URL.");
1183
			self::$curlFH = fopen($tempfile, 'w');
1184
			if(! self::$curlFH){
1185
				$this->error("Could not open $tempfile for writing.");
1186
				return false;
1187
			}
1188
			self::$curlDataWritten = 0;
1189
			$this->debug(3, "Fetching url with curl: $url");
1190
			$curl = curl_init($url);
1191
			curl_setopt ($curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
1192
			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");
1193
			curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
1194
			curl_setopt ($curl, CURLOPT_HEADER, 0);
1195
			curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
1196
			curl_setopt ($curl, CURLOPT_WRITEFUNCTION, 'timthumb::curlWrite');
1197
			@curl_setopt ($curl, CURLOPT_FOLLOWLOCATION, true);
1198
			@curl_setopt ($curl, CURLOPT_MAXREDIRS, 10);
1199
 
1200
			$curlResult = curl_exec($curl);
1201
			fclose(self::$curlFH);
1202
			$httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
1203
			if($httpStatus == 404){
1204
				$this->set404();
1205
			}
1206
			if($httpStatus == 302){
1207
				$this->error("External Image is Redirecting. Try alternate image url");
1208
				return false;
1209
			}
1210
			if($curlResult){
1211
				curl_close($curl);
1212
				return true;
1213
			} else {
1214
				$this->lastURLError = curl_error($curl);
1215
				curl_close($curl);
1216
				return false;
1217
			}
1218
		} else {
1219
			$img = @file_get_contents ($url);
1220
			if($img === false){
1221
				$err = error_get_last();
1222
				if(is_array($err) && $err['message']){
1223
					$this->lastURLError = $err['message'];
1224
				} else {
1225
					$this->lastURLError = $err;
1226
				}
1227
				if(preg_match('/404/', $this->lastURLError)){
1228
					$this->set404();
1229
				}
1230
 
1231
				return false;
1232
			}
1233
			if(! file_put_contents($tempfile, $img)){
1234
				$this->error("Could not write to $tempfile.");
1235
				return false;
1236
			}
1237
			return true;
1238
		}
1239
 
1240
	}
1241
	protected function serveImg($file){
1242
		$s = getimagesize($file);
1243
		if(! ($s && $s['mime'])){
1244
			return false;
1245
		}
1246
		header ('Content-Type: ' . $s['mime']);
1247
		header ('Content-Length: ' . filesize($file) );
1248
		header ('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
1249
		header ("Pragma: no-cache");
1250
		$bytes = @readfile($file);
1251
		if($bytes > 0){
1252
			return true;
1253
		}
1254
		$content = @file_get_contents ($file);
1255
		if ($content != FALSE){
1256
			echo $content;
1257
			return true;
1258
		}
1259
		return false;
1260
 
1261
	}
1262
	protected function set404(){
1263
		$this->is404 = true;
1264
	}
1265
	protected function is404(){
1266
		return $this->is404;
1267
	}
1268
}