PageRenderTime 59ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 2ms

/phpthumb.class.php

https://github.com/dbruenig/phpThumb
PHP | 4304 lines | 3563 code | 464 blank | 277 comment | 810 complexity | b22dbbd15e7d4165cb11b3605c0416ac MD5 | raw file
Possible License(s): GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. //////////////////////////////////////////////////////////////
  3. // phpThumb() by James Heinrich <info@silisoftware.com> //
  4. // available at http://phpthumb.sourceforge.net //
  5. // and/or https://github.com/JamesHeinrich/phpThumb //
  6. //////////////////////////////////////////////////////////////
  7. /// //
  8. // See: phpthumb.readme.txt for usage instructions //
  9. // ///
  10. //////////////////////////////////////////////////////////////
  11. ob_start();
  12. if (!include_once(dirname(__FILE__).'/phpthumb.functions.php')) {
  13. ob_end_flush();
  14. die('failed to include_once("'.realpath(dirname(__FILE__).'/phpthumb.functions.php').'")');
  15. }
  16. ob_end_clean();
  17. class phpthumb {
  18. // public:
  19. // START PARAMETERS (for object mode and phpThumb.php)
  20. // See phpthumb.readme.txt for descriptions of what each of these values are
  21. var $src = null; // SouRCe filename
  22. var $new = null; // NEW image (phpThumb.php only)
  23. var $w = null; // Width
  24. var $h = null; // Height
  25. var $wp = null; // Width (Portrait Images Only)
  26. var $hp = null; // Height (Portrait Images Only)
  27. var $wl = null; // Width (Landscape Images Only)
  28. var $hl = null; // Height (Landscape Images Only)
  29. var $ws = null; // Width (Square Images Only)
  30. var $hs = null; // Height (Square Images Only)
  31. var $f = null; // output image Format
  32. var $q = 75; // jpeg output Quality
  33. var $sx = null; // Source crop top-left X position
  34. var $sy = null; // Source crop top-left Y position
  35. var $sw = null; // Source crop Width
  36. var $sh = null; // Source crop Height
  37. var $zc = null; // Zoom Crop
  38. var $bc = null; // Border Color
  39. var $bg = null; // BackGround color
  40. var $fltr = array(); // FiLTeRs
  41. var $goto = null; // GO TO url after processing
  42. var $err = null; // default ERRor image filename
  43. var $xto = null; // extract eXif Thumbnail Only
  44. var $ra = null; // Rotate by Angle
  45. var $ar = null; // Auto Rotate
  46. var $aoe = null; // Allow Output Enlargement
  47. var $far = null; // Fixed Aspect Ratio
  48. var $iar = null; // Ignore Aspect Ratio
  49. var $maxb = null; // MAXimum Bytes
  50. var $down = null; // DOWNload thumbnail filename
  51. var $md5s = null; // MD5 hash of Source image
  52. var $sfn = 0; // Source Frame Number
  53. var $dpi = 150; // Dots Per Inch for vector source formats
  54. var $sia = null; // Save Image As filename
  55. var $file = null; // >>>deprecated, DO NOT USE, will be removed in future versions<<<
  56. var $phpThumbDebug = null;
  57. // END PARAMETERS
  58. // public:
  59. // START CONFIGURATION OPTIONS (for object mode only)
  60. // See phpThumb.config.php for descriptions of what each of these settings do
  61. // * Directory Configuration
  62. var $config_cache_directory = null;
  63. var $config_cache_directory_depth = 0;
  64. var $config_cache_disable_warning = true;
  65. var $config_cache_source_enabled = false;
  66. var $config_cache_source_directory = null;
  67. var $config_temp_directory = null;
  68. var $config_document_root = null;
  69. // * Default output configuration:
  70. var $config_output_format = 'jpeg';
  71. var $config_output_maxwidth = 0;
  72. var $config_output_maxheight = 0;
  73. var $config_output_interlace = true;
  74. // * Error message configuration
  75. var $config_error_image_width = 400;
  76. var $config_error_image_height = 100;
  77. var $config_error_message_image_default = '';
  78. var $config_error_bgcolor = 'CCCCFF';
  79. var $config_error_textcolor = 'FF0000';
  80. var $config_error_fontsize = 1;
  81. var $config_error_die_on_error = false;
  82. var $config_error_silent_die_on_error = false;
  83. var $config_error_die_on_source_failure = true;
  84. // * Anti-Hotlink Configuration:
  85. var $config_nohotlink_enabled = true;
  86. var $config_nohotlink_valid_domains = array();
  87. var $config_nohotlink_erase_image = true;
  88. var $config_nohotlink_text_message = 'Off-server thumbnailing is not allowed';
  89. // * Off-server Linking Configuration:
  90. var $config_nooffsitelink_enabled = false;
  91. var $config_nooffsitelink_valid_domains = array();
  92. var $config_nooffsitelink_require_refer = false;
  93. var $config_nooffsitelink_erase_image = true;
  94. var $config_nooffsitelink_watermark_src = '';
  95. var $config_nooffsitelink_text_message = 'Off-server linking is not allowed';
  96. // * Border & Background default colors
  97. var $config_border_hexcolor = '000000';
  98. var $config_background_hexcolor = 'FFFFFF';
  99. // * TrueType Fonts
  100. var $config_ttf_directory = './fonts';
  101. var $config_max_source_pixels = null;
  102. var $config_use_exif_thumbnail_for_speed = false;
  103. var $allow_local_http_src = false;
  104. var $config_imagemagick_path = null;
  105. var $config_prefer_imagemagick = true;
  106. var $config_imagemagick_use_thumbnail = true;
  107. var $config_cache_maxage = null;
  108. var $config_cache_maxsize = null;
  109. var $config_cache_maxfiles = null;
  110. var $config_cache_source_filemtime_ignore_local = false;
  111. var $config_cache_source_filemtime_ignore_remote = true;
  112. var $config_cache_default_only_suffix = false;
  113. var $config_cache_force_passthru = true;
  114. var $config_cache_prefix = ''; // default value set in the constructor below
  115. // * MySQL
  116. var $config_mysql_query = null;
  117. var $config_mysql_hostname = null;
  118. var $config_mysql_username = null;
  119. var $config_mysql_password = null;
  120. var $config_mysql_database = null;
  121. // * Security
  122. var $config_high_security_enabled = true;
  123. var $config_high_security_password = null;
  124. var $config_high_security_url_separator = '&';
  125. var $config_disable_debug = true;
  126. var $config_allow_src_above_docroot = false;
  127. var $config_allow_src_above_phpthumb = true;
  128. var $config_auto_allow_symlinks = true; // allow symlink target directories without explicitly whitelisting them
  129. var $config_additional_allowed_dirs = array(); // additional directories to allow source images to be read from
  130. // * HTTP fopen
  131. var $config_http_fopen_timeout = 10;
  132. var $config_http_follow_redirect = true;
  133. // * Compatability
  134. var $config_disable_pathinfo_parsing = false;
  135. var $config_disable_imagecopyresampled = false;
  136. var $config_disable_onlycreateable_passthru = false;
  137. var $config_http_user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.12) Gecko/20050915 Firefox/1.0.7';
  138. // END CONFIGURATION OPTIONS
  139. // public: error messages (read-only; persistant)
  140. var $debugmessages = array();
  141. var $debugtiming = array();
  142. var $fatalerror = null;
  143. // private: (should not be modified directly)
  144. var $thumbnailQuality = 75;
  145. var $thumbnailFormat = null;
  146. var $sourceFilename = null;
  147. var $rawImageData = null;
  148. var $IMresizedData = null;
  149. var $outputImageData = null;
  150. var $useRawIMoutput = false;
  151. var $gdimg_output = null;
  152. var $gdimg_source = null;
  153. var $getimagesizeinfo = null;
  154. var $source_width = null;
  155. var $source_height = null;
  156. var $thumbnailCropX = null;
  157. var $thumbnailCropY = null;
  158. var $thumbnailCropW = null;
  159. var $thumbnailCropH = null;
  160. var $exif_thumbnail_width = null;
  161. var $exif_thumbnail_height = null;
  162. var $exif_thumbnail_type = null;
  163. var $exif_thumbnail_data = null;
  164. var $exif_raw_data = null;
  165. var $thumbnail_width = null;
  166. var $thumbnail_height = null;
  167. var $thumbnail_image_width = null;
  168. var $thumbnail_image_height = null;
  169. var $tempFilesToDelete = array();
  170. var $cache_filename = null;
  171. var $AlphaCapableFormats = array('png', 'ico', 'gif');
  172. var $is_alpha = false;
  173. var $iswindows = null;
  174. var $issafemode = null;
  175. var $phpthumb_version = '1.7.12-201406011225';
  176. //////////////////////////////////////////////////////////////////////
  177. // public: constructor
  178. function phpThumb() {
  179. $this->DebugTimingMessage('phpThumb() constructor', __FILE__, __LINE__);
  180. $this->DebugMessage('phpThumb() v'.$this->phpthumb_version, __FILE__, __LINE__);
  181. $this->config_max_source_pixels = round(max(intval(ini_get('memory_limit')), intval(get_cfg_var('memory_limit'))) * 1048576 * 0.20); // 20% of memory_limit
  182. $this->iswindows = (bool) (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN');
  183. $this->issafemode = (bool) preg_match('#(1|ON)#i', ini_get('safe_mode'));
  184. $this->config_document_root = (!empty($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : $this->config_document_root);
  185. $this->config_cache_prefix = ( isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'].'_' : '');
  186. $this->purgeTempFiles(); // purge existing temp files if re-initializing object
  187. $php_sapi_name = strtolower(function_exists('php_sapi_name') ? php_sapi_name() : '');
  188. if ($php_sapi_name == 'cli') {
  189. $this->config_allow_src_above_docroot = true;
  190. }
  191. if (!$this->config_disable_debug) {
  192. // if debug mode is enabled, force phpThumbDebug output, do not allow normal thumbnails to be generated
  193. $this->phpThumbDebug = (is_null($this->phpThumbDebug) ? 9 : max(1, intval($this->phpThumbDebug)));
  194. }
  195. }
  196. function __destruct() {
  197. $this->purgeTempFiles();
  198. }
  199. // public:
  200. function purgeTempFiles() {
  201. foreach ($this->tempFilesToDelete as $tempFileToDelete) {
  202. if (file_exists($tempFileToDelete)) {
  203. $this->DebugMessage('Deleting temp file "'.$tempFileToDelete.'"', __FILE__, __LINE__);
  204. @unlink($tempFileToDelete);
  205. }
  206. }
  207. $this->tempFilesToDelete = array();
  208. return true;
  209. }
  210. // public:
  211. function setSourceFilename($sourceFilename) {
  212. //$this->resetObject();
  213. //$this->rawImageData = null;
  214. $this->sourceFilename = $sourceFilename;
  215. $this->src = $sourceFilename;
  216. if (is_null($this->config_output_format)) {
  217. $sourceFileExtension = strtolower(substr(strrchr($sourceFilename, '.'), 1));
  218. if (preg_match('#^[a-z]{3,4}$#', $sourceFileExtension)) {
  219. $this->config_output_format = $sourceFileExtension;
  220. $this->DebugMessage('setSourceFilename('.$sourceFilename.') set $this->config_output_format to "'.$sourceFileExtension.'"', __FILE__, __LINE__);
  221. } else {
  222. $this->DebugMessage('setSourceFilename('.$sourceFilename.') did NOT set $this->config_output_format to "'.$sourceFileExtension.'" because it did not seem like an appropriate image format', __FILE__, __LINE__);
  223. }
  224. }
  225. $this->DebugMessage('setSourceFilename('.$sourceFilename.') set $this->sourceFilename to "'.$this->sourceFilename.'"', __FILE__, __LINE__);
  226. return true;
  227. }
  228. // public:
  229. function setSourceData($rawImageData, $sourceFilename='') {
  230. //$this->resetObject();
  231. //$this->sourceFilename = null;
  232. $this->rawImageData = $rawImageData;
  233. $this->DebugMessage('setSourceData() setting $this->rawImageData ('.strlen($this->rawImageData).' bytes; magic="'.substr($this->rawImageData, 0, 4).'" ('.phpthumb_functions::HexCharDisplay(substr($this->rawImageData, 0, 4)).'))', __FILE__, __LINE__);
  234. if ($this->config_cache_source_enabled) {
  235. $sourceFilename = ($sourceFilename ? $sourceFilename : md5($rawImageData));
  236. if (!is_dir($this->config_cache_source_directory)) {
  237. $this->ErrorImage('$this->config_cache_source_directory ('.$this->config_cache_source_directory.') is not a directory');
  238. } elseif (!@is_writable($this->config_cache_source_directory)) {
  239. $this->ErrorImage('$this->config_cache_source_directory ('.$this->config_cache_source_directory.') is not writable');
  240. }
  241. $this->DebugMessage('setSourceData() attempting to save source image to "'.$this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename).'"', __FILE__, __LINE__);
  242. if ($fp = @fopen($this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename), 'wb')) {
  243. fwrite($fp, $rawImageData);
  244. fclose($fp);
  245. } elseif (!$this->phpThumbDebug) {
  246. $this->ErrorImage('setSourceData() failed to write to source cache ('.$this->config_cache_source_directory.DIRECTORY_SEPARATOR.urlencode($sourceFilename).')');
  247. }
  248. }
  249. return true;
  250. }
  251. // public:
  252. function setSourceImageResource($gdimg) {
  253. //$this->resetObject();
  254. $this->gdimg_source = $gdimg;
  255. return true;
  256. }
  257. // public:
  258. function setParameter($param, $value) {
  259. if ($param == 'src') {
  260. $this->setSourceFilename($this->ResolveFilenameToAbsolute($value));
  261. } elseif (@is_array($this->$param)) {
  262. if (is_array($value)) {
  263. foreach ($value as $arraykey => $arrayvalue) {
  264. array_push($this->$param, $arrayvalue);
  265. }
  266. } else {
  267. array_push($this->$param, $value);
  268. }
  269. } else {
  270. $this->$param = $value;
  271. }
  272. return true;
  273. }
  274. // public:
  275. function getParameter($param) {
  276. //if (property_exists('phpThumb', $param)) {
  277. return $this->$param;
  278. //}
  279. //$this->DebugMessage('setParameter() attempting to get non-existant parameter "'.$param.'"', __FILE__, __LINE__);
  280. //return false;
  281. }
  282. // public:
  283. function GenerateThumbnail() {
  284. $this->setOutputFormat();
  285. $this->phpThumbDebug('8a');
  286. $this->ResolveSource();
  287. $this->phpThumbDebug('8b');
  288. $this->SetCacheFilename();
  289. $this->phpThumbDebug('8c');
  290. $this->ExtractEXIFgetImageSize();
  291. $this->phpThumbDebug('8d');
  292. if ($this->useRawIMoutput) {
  293. $this->DebugMessage('Skipping rest of GenerateThumbnail() because ($this->useRawIMoutput == true)', __FILE__, __LINE__);
  294. return true;
  295. }
  296. $this->phpThumbDebug('8e');
  297. if (!$this->SourceImageToGD()) {
  298. $this->DebugMessage('SourceImageToGD() failed', __FILE__, __LINE__);
  299. return false;
  300. }
  301. $this->phpThumbDebug('8f');
  302. $this->Rotate();
  303. $this->phpThumbDebug('8g');
  304. $this->CreateGDoutput();
  305. $this->phpThumbDebug('8h');
  306. switch ($this->far) {
  307. case 'L':
  308. case 'TL':
  309. case 'BL':
  310. $destination_offset_x = 0;
  311. $destination_offset_y = round(($this->thumbnail_height - $this->thumbnail_image_height) / 2);
  312. break;
  313. case 'R':
  314. case 'TR':
  315. case 'BR':
  316. $destination_offset_x = round($this->thumbnail_width - $this->thumbnail_image_width);
  317. $destination_offset_y = round(($this->thumbnail_height - $this->thumbnail_image_height) / 2);
  318. break;
  319. case 'T':
  320. case 'TL':
  321. case 'TR':
  322. $destination_offset_x = round(($this->thumbnail_width - $this->thumbnail_image_width) / 2);
  323. $destination_offset_y = 0;
  324. break;
  325. case 'B':
  326. case 'BL':
  327. case 'BR':
  328. $destination_offset_x = round(($this->thumbnail_width - $this->thumbnail_image_width) / 2);
  329. $destination_offset_y = round($this->thumbnail_height - $this->thumbnail_image_height);
  330. break;
  331. case 'C':
  332. default:
  333. $destination_offset_x = round(($this->thumbnail_width - $this->thumbnail_image_width) / 2);
  334. $destination_offset_y = round(($this->thumbnail_height - $this->thumbnail_image_height) / 2);
  335. }
  336. // // copy/resize image to appropriate dimensions
  337. // $borderThickness = 0;
  338. // if (!empty($this->fltr)) {
  339. // foreach ($this->fltr as $key => $value) {
  340. // if (preg_match('#^bord\|([0-9]+)#', $value, $matches)) {
  341. // $borderThickness = $matches[1];
  342. // break;
  343. // }
  344. // }
  345. // }
  346. // if ($borderThickness > 0) {
  347. // //$this->DebugMessage('Skipping ImageResizeFunction() because BorderThickness="'.$borderThickness.'"', __FILE__, __LINE__);
  348. // $this->thumbnail_image_height /= 2;
  349. // }
  350. $this->ImageResizeFunction(
  351. $this->gdimg_output,
  352. $this->gdimg_source,
  353. $destination_offset_x,
  354. $destination_offset_y,
  355. $this->thumbnailCropX,
  356. $this->thumbnailCropY,
  357. $this->thumbnail_image_width,
  358. $this->thumbnail_image_height,
  359. $this->thumbnailCropW,
  360. $this->thumbnailCropH
  361. );
  362. $this->DebugMessage('memory_get_usage() after copy-resize = '.(function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__);
  363. ImageDestroy($this->gdimg_source);
  364. $this->DebugMessage('memory_get_usage() after ImageDestroy = '.(function_exists('memory_get_usage') ? @memory_get_usage() : 'n/a'), __FILE__, __LINE__);
  365. $this->phpThumbDebug('8i');
  366. $this->AntiOffsiteLinking();
  367. $this->phpThumbDebug('8j');
  368. $this->ApplyFilters();
  369. $this->phpThumbDebug('8k');
  370. $this->AlphaChannelFlatten();
  371. $this->phpThumbDebug('8l');
  372. $this->MaxFileSize();
  373. $this->phpThumbDebug('8m');
  374. $this->DebugMessage('GenerateThumbnail() completed successfully', __FILE__, __LINE__);
  375. return true;
  376. }
  377. // public:
  378. function RenderOutput() {
  379. if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) {
  380. $this->DebugMessage('RenderOutput() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__);
  381. return false;
  382. }
  383. if (!$this->thumbnailFormat) {
  384. $this->DebugMessage('RenderOutput() failed because $this->thumbnailFormat is empty', __FILE__, __LINE__);
  385. return false;
  386. }
  387. if ($this->useRawIMoutput) {
  388. $this->DebugMessage('RenderOutput copying $this->IMresizedData ('.strlen($this->IMresizedData).' bytes) to $this->outputImage', __FILE__, __LINE__);
  389. $this->outputImageData = $this->IMresizedData;
  390. return true;
  391. }
  392. $builtin_formats = array();
  393. if (function_exists('ImageTypes')) {
  394. $imagetypes = ImageTypes();
  395. $builtin_formats['wbmp'] = (bool) ($imagetypes & IMG_WBMP);
  396. $builtin_formats['jpg'] = (bool) ($imagetypes & IMG_JPG);
  397. $builtin_formats['gif'] = (bool) ($imagetypes & IMG_GIF);
  398. $builtin_formats['png'] = (bool) ($imagetypes & IMG_PNG);
  399. }
  400. $this->DebugMessage('RenderOutput() attempting Image'.strtoupper(@$this->thumbnailFormat).'($this->gdimg_output)', __FILE__, __LINE__);
  401. ob_start();
  402. switch ($this->thumbnailFormat) {
  403. case 'wbmp':
  404. if (!@$builtin_formats['wbmp']) {
  405. $this->DebugMessage('GD does not have required built-in support for WBMP output', __FILE__, __LINE__);
  406. ob_end_clean();
  407. return false;
  408. }
  409. ImageJPEG($this->gdimg_output, null, $this->thumbnailQuality);
  410. $this->outputImageData = ob_get_contents();
  411. break;
  412. case 'jpeg':
  413. case 'jpg': // should be "jpeg" not "jpg" but just in case...
  414. if (!@$builtin_formats['jpg']) {
  415. $this->DebugMessage('GD does not have required built-in support for JPEG output', __FILE__, __LINE__);
  416. ob_end_clean();
  417. return false;
  418. }
  419. ImageJPEG($this->gdimg_output, null, $this->thumbnailQuality);
  420. $this->outputImageData = ob_get_contents();
  421. break;
  422. case 'png':
  423. if (!@$builtin_formats['png']) {
  424. $this->DebugMessage('GD does not have required built-in support for PNG output', __FILE__, __LINE__);
  425. ob_end_clean();
  426. return false;
  427. }
  428. ImagePNG($this->gdimg_output);
  429. $this->outputImageData = ob_get_contents();
  430. break;
  431. case 'gif':
  432. if (!@$builtin_formats['gif']) {
  433. $this->DebugMessage('GD does not have required built-in support for GIF output', __FILE__, __LINE__);
  434. ob_end_clean();
  435. return false;
  436. }
  437. ImageGIF($this->gdimg_output);
  438. $this->outputImageData = ob_get_contents();
  439. break;
  440. case 'bmp':
  441. $ImageOutFunction = '"builtin BMP output"';
  442. if (!@include_once(dirname(__FILE__).'/phpthumb.bmp.php')) {
  443. $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__);
  444. ob_end_clean();
  445. return false;
  446. }
  447. $phpthumb_bmp = new phpthumb_bmp();
  448. $this->outputImageData = $phpthumb_bmp->GD2BMPstring($this->gdimg_output);
  449. unset($phpthumb_bmp);
  450. break;
  451. case 'ico':
  452. $ImageOutFunction = '"builtin ICO output"';
  453. if (!@include_once(dirname(__FILE__).'/phpthumb.ico.php')) {
  454. $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__);
  455. ob_end_clean();
  456. return false;
  457. }
  458. $phpthumb_ico = new phpthumb_ico();
  459. $arrayOfOutputImages = array($this->gdimg_output);
  460. $this->outputImageData = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages);
  461. unset($phpthumb_ico);
  462. break;
  463. default:
  464. $this->DebugMessage('RenderOutput failed because $this->thumbnailFormat "'.$this->thumbnailFormat.'" is not valid', __FILE__, __LINE__);
  465. ob_end_clean();
  466. return false;
  467. }
  468. ob_end_clean();
  469. if (!$this->outputImageData) {
  470. $this->DebugMessage('RenderOutput() for "'.$this->thumbnailFormat.'" failed', __FILE__, __LINE__);
  471. ob_end_clean();
  472. return false;
  473. }
  474. $this->DebugMessage('RenderOutput() completing with $this->outputImageData = '.strlen($this->outputImageData).' bytes', __FILE__, __LINE__);
  475. return true;
  476. }
  477. // public:
  478. function RenderToFile($filename) {
  479. if (preg_match('#^[a-z0-9]+://#i', $filename)) {
  480. $this->DebugMessage('RenderToFile() failed because $filename ('.$filename.') is a URL', __FILE__, __LINE__);
  481. return false;
  482. }
  483. // render thumbnail to this file only, do not cache, do not output to browser
  484. //$renderfilename = $this->ResolveFilenameToAbsolute(dirname($filename)).DIRECTORY_SEPARATOR.basename($filename);
  485. $renderfilename = $filename;
  486. if (($filename{0} != '/') && ($filename{0} != '\\') && ($filename{1} != ':')) {
  487. $renderfilename = $this->ResolveFilenameToAbsolute($renderfilename);
  488. }
  489. if (!@is_writable(dirname($renderfilename))) {
  490. $this->DebugMessage('RenderToFile() failed because "'.dirname($renderfilename).'/" is not writable', __FILE__, __LINE__);
  491. return false;
  492. }
  493. if (@is_file($renderfilename) && !@is_writable($renderfilename)) {
  494. $this->DebugMessage('RenderToFile() failed because "'.$renderfilename.'" is not writable', __FILE__, __LINE__);
  495. return false;
  496. }
  497. if ($this->RenderOutput()) {
  498. if (file_put_contents($renderfilename, $this->outputImageData)) {
  499. $this->DebugMessage('RenderToFile('.$renderfilename.') succeeded', __FILE__, __LINE__);
  500. return true;
  501. }
  502. if (!@file_exists($renderfilename)) {
  503. $this->DebugMessage('RenderOutput ['.$this->thumbnailFormat.'('.$renderfilename.')] did not appear to fail, but the output image does not exist either...', __FILE__, __LINE__);
  504. }
  505. } else {
  506. $this->DebugMessage('RenderOutput ['.$this->thumbnailFormat.'('.$renderfilename.')] failed', __FILE__, __LINE__);
  507. }
  508. return false;
  509. }
  510. // public:
  511. function OutputThumbnail() {
  512. $this->purgeTempFiles();
  513. if (!$this->useRawIMoutput && !is_resource($this->gdimg_output)) {
  514. $this->DebugMessage('OutputThumbnail() failed because !is_resource($this->gdimg_output)', __FILE__, __LINE__);
  515. return false;
  516. }
  517. if (headers_sent()) {
  518. return $this->ErrorImage('OutputThumbnail() failed - headers already sent');
  519. exit;
  520. }
  521. $downloadfilename = phpthumb_functions::SanitizeFilename(is_string($this->sia) ? $this->sia : ($this->down ? $this->down : 'phpThumb_generated_thumbnail'.'.'.$this->thumbnailFormat));
  522. $this->DebugMessage('Content-Disposition header filename set to "'.$downloadfilename.'"', __FILE__, __LINE__);
  523. if ($downloadfilename) {
  524. header('Content-Disposition: '.($this->down ? 'attachment' : 'inline').'; filename="'.$downloadfilename.'"');
  525. } else {
  526. $this->DebugMessage('failed to send Content-Disposition header because $downloadfilename is empty', __FILE__, __LINE__);
  527. }
  528. if ($this->useRawIMoutput) {
  529. header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
  530. echo $this->IMresizedData;
  531. } else {
  532. $this->DebugMessage('ImageInterlace($this->gdimg_output, '.intval($this->config_output_interlace).')', __FILE__, __LINE__);
  533. ImageInterlace($this->gdimg_output, intval($this->config_output_interlace));
  534. switch ($this->thumbnailFormat) {
  535. case 'jpeg':
  536. header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
  537. $ImageOutFunction = 'image'.$this->thumbnailFormat;
  538. @$ImageOutFunction($this->gdimg_output, null, $this->thumbnailQuality);
  539. break;
  540. case 'png':
  541. case 'gif':
  542. header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
  543. $ImageOutFunction = 'image'.$this->thumbnailFormat;
  544. @$ImageOutFunction($this->gdimg_output);
  545. break;
  546. case 'bmp':
  547. if (!@include_once(dirname(__FILE__).'/phpthumb.bmp.php')) {
  548. $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.bmp.php" which is required for BMP format output', __FILE__, __LINE__);
  549. return false;
  550. }
  551. $phpthumb_bmp = new phpthumb_bmp();
  552. if (is_object($phpthumb_bmp)) {
  553. $bmp_data = $phpthumb_bmp->GD2BMPstring($this->gdimg_output);
  554. unset($phpthumb_bmp);
  555. if (!$bmp_data) {
  556. $this->DebugMessage('$phpthumb_bmp->GD2BMPstring() failed', __FILE__, __LINE__);
  557. return false;
  558. }
  559. header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
  560. echo $bmp_data;
  561. } else {
  562. $this->DebugMessage('new phpthumb_bmp() failed', __FILE__, __LINE__);
  563. return false;
  564. }
  565. break;
  566. case 'ico':
  567. if (!@include_once(dirname(__FILE__).'/phpthumb.ico.php')) {
  568. $this->DebugMessage('Error including "'.dirname(__FILE__).'/phpthumb.ico.php" which is required for ICO format output', __FILE__, __LINE__);
  569. return false;
  570. }
  571. $phpthumb_ico = new phpthumb_ico();
  572. if (is_object($phpthumb_ico)) {
  573. $arrayOfOutputImages = array($this->gdimg_output);
  574. $ico_data = $phpthumb_ico->GD2ICOstring($arrayOfOutputImages);
  575. unset($phpthumb_ico);
  576. if (!$ico_data) {
  577. $this->DebugMessage('$phpthumb_ico->GD2ICOstring() failed', __FILE__, __LINE__);
  578. return false;
  579. }
  580. header('Content-Type: '.phpthumb_functions::ImageTypeToMIMEtype($this->thumbnailFormat));
  581. echo $ico_data;
  582. } else {
  583. $this->DebugMessage('new phpthumb_ico() failed', __FILE__, __LINE__);
  584. return false;
  585. }
  586. break;
  587. default:
  588. $this->DebugMessage('OutputThumbnail failed because $this->thumbnailFormat "'.$this->thumbnailFormat.'" is not valid', __FILE__, __LINE__);
  589. return false;
  590. break;
  591. }
  592. }
  593. return true;
  594. }
  595. // public:
  596. function CleanUpCacheDirectory() {
  597. $this->DebugMessage('CleanUpCacheDirectory() set to purge ('.(is_null($this->config_cache_maxage) ? 'NULL' : number_format($this->config_cache_maxage / 86400, 1)).' days; '.(is_null($this->config_cache_maxsize) ? 'NULL' : number_format($this->config_cache_maxsize / 1048576, 2)).' MB; '.(is_null($this->config_cache_maxfiles) ? 'NULL' : number_format($this->config_cache_maxfiles)).' files)', __FILE__, __LINE__);
  598. if (!is_writable($this->config_cache_directory)) {
  599. $this->DebugMessage('CleanUpCacheDirectory() skipped because "'.$this->config_cache_directory.'" is not writable', __FILE__, __LINE__);
  600. return true;
  601. }
  602. // cache status of cache directory for 1 hour to avoid hammering the filesystem functions
  603. $phpThumbCacheStats_filename = $this->config_cache_directory.DIRECTORY_SEPARATOR.'phpThumbCacheStats.txt';
  604. if (file_exists($phpThumbCacheStats_filename) && is_readable($phpThumbCacheStats_filename) && (filemtime($phpThumbCacheStats_filename) >= (time() - 3600))) {
  605. $this->DebugMessage('CleanUpCacheDirectory() skipped because "'.$phpThumbCacheStats_filename.'" is recently modified', __FILE__, __LINE__);
  606. return true;
  607. }
  608. if (!@touch($phpThumbCacheStats_filename)) {
  609. $this->DebugMessage('touch('.$phpThumbCacheStats_filename.') failed', __FILE__, __LINE__);
  610. }
  611. $DeletedKeys = array();
  612. $AllFilesInCacheDirectory = array();
  613. if (($this->config_cache_maxage > 0) || ($this->config_cache_maxsize > 0) || ($this->config_cache_maxfiles > 0)) {
  614. $CacheDirOldFilesAge = array();
  615. $CacheDirOldFilesSize = array();
  616. $AllFilesInCacheDirectory = phpthumb_functions::GetAllFilesInSubfolders($this->config_cache_directory);
  617. foreach ($AllFilesInCacheDirectory as $fullfilename) {
  618. if (preg_match('#'.preg_quote($this->config_cache_prefix).'#i', $fullfilename) && file_exists($fullfilename)) {
  619. $CacheDirOldFilesAge[$fullfilename] = @fileatime($fullfilename);
  620. if ($CacheDirOldFilesAge[$fullfilename] == 0) {
  621. $CacheDirOldFilesAge[$fullfilename] = @filemtime($fullfilename);
  622. }
  623. $CacheDirOldFilesSize[$fullfilename] = @filesize($fullfilename);
  624. }
  625. }
  626. if (empty($CacheDirOldFilesSize)) {
  627. $this->DebugMessage('CleanUpCacheDirectory() skipped because $CacheDirOldFilesSize is empty (phpthumb_functions::GetAllFilesInSubfolders('.$this->config_cache_directory.') found no files)', __FILE__, __LINE__);
  628. return true;
  629. }
  630. $DeletedKeys['zerobyte'] = array();
  631. foreach ($CacheDirOldFilesSize as $fullfilename => $filesize) {
  632. // purge all zero-size files more than an hour old (to prevent trying to delete just-created and/or in-use files)
  633. $cutofftime = time() - 3600;
  634. if (($filesize == 0) && ($CacheDirOldFilesAge[$fullfilename] < $cutofftime)) {
  635. $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
  636. if (@unlink($fullfilename)) {
  637. $DeletedKeys['zerobyte'][] = $fullfilename;
  638. unset($CacheDirOldFilesSize[$fullfilename]);
  639. unset($CacheDirOldFilesAge[$fullfilename]);
  640. }
  641. }
  642. }
  643. $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['zerobyte']).' zero-byte files', __FILE__, __LINE__);
  644. asort($CacheDirOldFilesAge);
  645. if ($this->config_cache_maxfiles > 0) {
  646. $TotalCachedFiles = count($CacheDirOldFilesAge);
  647. $DeletedKeys['maxfiles'] = array();
  648. foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
  649. if ($TotalCachedFiles > $this->config_cache_maxfiles) {
  650. $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
  651. if (@unlink($fullfilename)) {
  652. $TotalCachedFiles--;
  653. $DeletedKeys['maxfiles'][] = $fullfilename;
  654. }
  655. } else {
  656. // there are few enough files to keep the rest
  657. break;
  658. }
  659. }
  660. $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxfiles']).' files based on (config_cache_maxfiles='.$this->config_cache_maxfiles.')', __FILE__, __LINE__);
  661. foreach ($DeletedKeys['maxfiles'] as $fullfilename) {
  662. unset($CacheDirOldFilesAge[$fullfilename]);
  663. unset($CacheDirOldFilesSize[$fullfilename]);
  664. }
  665. }
  666. if ($this->config_cache_maxage > 0) {
  667. $mindate = time() - $this->config_cache_maxage;
  668. $DeletedKeys['maxage'] = array();
  669. foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
  670. if ($filedate > 0) {
  671. if ($filedate < $mindate) {
  672. $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
  673. if (@unlink($fullfilename)) {
  674. $DeletedKeys['maxage'][] = $fullfilename;
  675. }
  676. } else {
  677. // the rest of the files are new enough to keep
  678. break;
  679. }
  680. }
  681. }
  682. $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxage']).' files based on (config_cache_maxage='.$this->config_cache_maxage.')', __FILE__, __LINE__);
  683. foreach ($DeletedKeys['maxage'] as $fullfilename) {
  684. unset($CacheDirOldFilesAge[$fullfilename]);
  685. unset($CacheDirOldFilesSize[$fullfilename]);
  686. }
  687. }
  688. if ($this->config_cache_maxsize > 0) {
  689. $TotalCachedFileSize = array_sum($CacheDirOldFilesSize);
  690. $DeletedKeys['maxsize'] = array();
  691. foreach ($CacheDirOldFilesAge as $fullfilename => $filedate) {
  692. if ($TotalCachedFileSize > $this->config_cache_maxsize) {
  693. $this->DebugMessage('deleting "'.$fullfilename.'"', __FILE__, __LINE__);
  694. if (@unlink($fullfilename)) {
  695. $TotalCachedFileSize -= $CacheDirOldFilesSize[$fullfilename];
  696. $DeletedKeys['maxsize'][] = $fullfilename;
  697. }
  698. } else {
  699. // the total filesizes are small enough to keep the rest of the files
  700. break;
  701. }
  702. }
  703. $this->DebugMessage('CleanUpCacheDirectory() purged '.count($DeletedKeys['maxsize']).' files based on (config_cache_maxsize='.$this->config_cache_maxsize.')', __FILE__, __LINE__);
  704. foreach ($DeletedKeys['maxsize'] as $fullfilename) {
  705. unset($CacheDirOldFilesAge[$fullfilename]);
  706. unset($CacheDirOldFilesSize[$fullfilename]);
  707. }
  708. }
  709. } else {
  710. $this->DebugMessage('skipping CleanUpCacheDirectory() because config set to not use it', __FILE__, __LINE__);
  711. }
  712. $totalpurged = 0;
  713. foreach ($DeletedKeys as $key => $value) {
  714. $totalpurged += count($value);
  715. }
  716. $this->DebugMessage('CleanUpCacheDirectory() purged '.$totalpurged.' files (from '.count($AllFilesInCacheDirectory).') based on config settings', __FILE__, __LINE__);
  717. if ($totalpurged > 0) {
  718. $empty_dirs = array();
  719. foreach ($AllFilesInCacheDirectory as $fullfilename) {
  720. if (is_dir($fullfilename)) {
  721. $empty_dirs[realpath($fullfilename)] = 1;
  722. } else {
  723. unset($empty_dirs[realpath(dirname($fullfilename))]);
  724. }
  725. }
  726. krsort($empty_dirs);
  727. $totalpurgeddirs = 0;
  728. foreach ($empty_dirs as $empty_dir => $dummy) {
  729. if ($empty_dir == $this->config_cache_directory) {
  730. // shouldn't happen, but just in case, don't let it delete actual cache directory
  731. continue;
  732. } elseif (@rmdir($empty_dir)) {
  733. $totalpurgeddirs++;
  734. } else {
  735. $this->DebugMessage('failed to rmdir('.$empty_dir.')', __FILE__, __LINE__);
  736. }
  737. }
  738. $this->DebugMessage('purged '.$totalpurgeddirs.' empty directories', __FILE__, __LINE__);
  739. }
  740. return true;
  741. }
  742. //////////////////////////////////////////////////////////////////////
  743. // private: re-initializator (call between rendering multiple images with one object)
  744. function resetObject() {
  745. $class_vars = get_class_vars(get_class($this));
  746. foreach ($class_vars as $key => $value) {
  747. // do not clobber debug or config info
  748. if (!preg_match('#^(config_|debug|fatalerror)#i', $key)) {
  749. $this->$key = $value;
  750. }
  751. }
  752. $this->phpThumb(); // re-initialize some class variables
  753. return true;
  754. }
  755. //////////////////////////////////////////////////////////////////////
  756. function ResolveSource() {
  757. if (is_resource($this->gdimg_source)) {
  758. $this->DebugMessage('ResolveSource() exiting because is_resource($this->gdimg_source)', __FILE__, __LINE__);
  759. return true;
  760. }
  761. if ($this->rawImageData) {
  762. $this->sourceFilename = null;
  763. $this->DebugMessage('ResolveSource() exiting because $this->rawImageData is set ('.number_format(strlen($this->rawImageData)).' bytes)', __FILE__, __LINE__);
  764. return true;
  765. }
  766. if ($this->sourceFilename) {
  767. $this->sourceFilename = $this->ResolveFilenameToAbsolute($this->sourceFilename);
  768. $this->DebugMessage('$this->sourceFilename set to "'.$this->sourceFilename.'"', __FILE__, __LINE__);
  769. } elseif ($this->src) {
  770. $this->sourceFilename = $this->ResolveFilenameToAbsolute($this->src);
  771. $this->DebugMessage('$this->sourceFilename set to "'.$this->sourceFilename.'" from $this->src ('.$this->src.')', __FILE__, __LINE__);
  772. } else {
  773. return $this->ErrorImage('$this->sourceFilename and $this->src are both empty');
  774. }
  775. if ($this->iswindows && ((substr($this->sourceFilename, 0, 2) == '//') || (substr($this->sourceFilename, 0, 2) == '\\\\'))) {
  776. // Windows \\share\filename.ext
  777. } elseif (preg_match('#^[a-z0-9]+://#i', $this->sourceFilename, $protocol_matches)) {
  778. if (preg_match('#^(f|ht)tps?\://#i', $this->sourceFilename)) {
  779. // URL
  780. if ($this->config_http_user_agent) {
  781. ini_set('user_agent', $this->config_http_user_agent);
  782. }
  783. } else {
  784. return $this->ErrorImage('only FTP and HTTP/HTTPS protocols are allowed, "'.$protocol_matches[1].'" is not');
  785. }
  786. } elseif (!@file_exists($this->sourceFilename)) {
  787. return $this->ErrorImage('"'.$this->sourceFilename.'" does not exist');
  788. } elseif (!@is_file($this->sourceFilename)) {
  789. return $this->ErrorImage('"'.$this->sourceFilename.'" is not a file');
  790. }
  791. return true;
  792. }
  793. function setOutputFormat() {
  794. static $alreadyCalled = false;
  795. if ($this->thumbnailFormat && $alreadyCalled) {
  796. return true;
  797. }
  798. $alreadyCalled = true;
  799. $AvailableImageOutputFormats = array();
  800. $AvailableImageOutputFormats[] = 'text';
  801. if (@is_readable(dirname(__FILE__).'/phpthumb.ico.php')) {
  802. $AvailableImageOutputFormats[] = 'ico';
  803. }
  804. if (@is_readable(dirname(__FILE__).'/phpthumb.bmp.php')) {
  805. $AvailableImageOutputFormats[] = 'bmp';
  806. }
  807. $this->thumbnailFormat = 'ico';
  808. // Set default output format based on what image types are available
  809. if (function_exists('ImageTypes')) {
  810. $imagetypes = ImageTypes();
  811. if ($imagetypes & IMG_WBMP) {
  812. $this->thumbnailFormat = 'wbmp';
  813. $AvailableImageOutputFormats[] = 'wbmp';
  814. }
  815. if ($imagetypes & IMG_GIF) {
  816. $this->thumbnailFormat = 'gif';
  817. $AvailableImageOutputFormats[] = 'gif';
  818. }
  819. if ($imagetypes & IMG_PNG) {
  820. $this->thumbnailFormat = 'png';
  821. $AvailableImageOutputFormats[] = 'png';
  822. }
  823. if ($imagetypes & IMG_JPG) {
  824. $this->thumbnailFormat = 'jpeg';
  825. $AvailableImageOutputFormats[] = 'jpeg';
  826. }
  827. } else {
  828. //return $this->ErrorImage('ImageTypes() does not exist - GD support might not be enabled?');
  829. $this->DebugMessage('ImageTypes() does not exist - GD support might not be enabled?', __FILE__, __LINE__);
  830. }
  831. if ($this->ImageMagickVersion()) {
  832. $IMformats = array('jpeg', 'png', 'gif', 'bmp', 'ico', 'wbmp');
  833. $this->DebugMessage('Addding ImageMagick formats to $AvailableImageOutputFormats ('.implode(';', $AvailableImageOutputFormats).')', __FILE__, __LINE__);
  834. foreach ($IMformats as $key => $format) {
  835. $AvailableImageOutputFormats[] = $format;
  836. }
  837. }
  838. $AvailableImageOutputFormats = array_unique($AvailableImageOutputFormats);
  839. $this->DebugMessage('$AvailableImageOutputFormats = array('.implode(';', $AvailableImageOutputFormats).')', __FILE__, __LINE__);
  840. $this->f = preg_replace('#[^a-z]#', '', strtolower($this->f));
  841. if (strtolower($this->config_output_format) == 'jpg') {
  842. $this->config_output_format = 'jpeg';
  843. }
  844. if (strtolower($this->f) == 'jpg') {
  845. $this->f = 'jpeg';
  846. }
  847. if (phpthumb_functions::CaseInsensitiveInArray($this->config_output_format, $AvailableImageOutputFormats)) {
  848. // set output format to config default if that format is available
  849. $this->DebugMessage('$this->thumbnailFormat set to $this->config_output_format "'.strtolower($this->config_output_format).'"', __FILE__, __LINE__);
  850. $this->thumbnailFormat = strtolower($this->config_output_format);
  851. } elseif ($this->config_output_format) {
  852. $this->DebugMessage('$this->thumbnailFormat staying as "'.$this->thumbnailFormat.'" because $this->config_output_format ('.strtolower($this->config_output_format).') is not in $AvailableImageOutputFormats', __FILE__, __LINE__);
  853. }
  854. if ($this->f && (phpthumb_functions::CaseInsensitiveInArray($this->f, $AvailableImageOutputFormats))) {
  855. // override output format if $this->f is set and that format is available
  856. $this->DebugMessage('$this->thumbnailFormat set to $this->f "'.strtolower($this->f).'"', __FILE__, __LINE__);
  857. $this->thumbnailFormat = strtolower($this->f);
  858. } elseif ($this->f) {
  859. $this->DebugMessage('$this->thumbnailFormat staying as "'.$this->thumbnailFormat.'" because $this->f ('.strtolower($this->f).') is not in $AvailableImageOutputFormats', __FILE__, __LINE__);
  860. }
  861. // for JPEG images, quality 1 (worst) to 99 (best)
  862. // quality < 25 is nasty, with not much size savings - not recommended
  863. // problems with 100 - invalid JPEG?
  864. $this->thumbnailQuality = max(1, min(99, ($this->q ? intval($this->q) : 75)));
  865. $this->DebugMessage('$this->thumbnailQuality set to "'.$this->thumbnailQuality.'"', __FILE__, __LINE__);
  866. return true;
  867. }
  868. function setCacheDirectory() {
  869. // resolve cache directory to absolute pathname
  870. $this->DebugMessage('setCacheDirectory() starting with config_cache_directory = "'.$this->config_cache_directory.'"', __FILE__, __LINE__);
  871. if (substr($this->config_cache_directory, 0, 1) == '.') {
  872. if (preg_match('#^(f|ht)tps?\://#i', $this->src)) {
  873. if (!$this->config_cache_disable_warning) {
  874. $this->ErrorImage('$this->config_cache_directory ('.$this->config_cache_directory.') cannot be used for remote images. Adjust "cache_directory" or "cache_disable_warning" in phpThumb.config.php');
  875. }
  876. } elseif ($this->src) {
  877. // resolve relative cache directory to source image
  878. $this->config_cache_directory = dirname($this->ResolveFilenameToAbsolute($this->src)).DIRECTORY_SEPARATOR.$this->config_cache_directory;
  879. } else {
  880. // $this->new is probably set
  881. }
  882. }
  883. if (substr($this->config_cache_directory, -1) == '/') {
  884. $this->config_cache_directory = substr($this->config_cache_directory, 0, -1);
  885. }
  886. if ($this->iswindows) {
  887. $this->config_cache_directory = str_replace('/', DIRECTORY_SEPARATOR, $this->config_cache_directory);
  888. }
  889. if ($this->config_cache_directory) {
  890. $real_cache_path = realpath($this->config_cache_directory);
  891. if (!$real_cache_path) {
  892. $this->DebugMessage('realpath($this->config_cache_directory) failed for "'.$this->config_cache_directory.'"', __FILE__, __LINE__);
  893. if (!is_dir($this->config_cache_directory)) {
  894. $this->DebugMessage('!is_dir('.$this->config_cache_directory.')', __FILE__, __LINE__);
  895. }
  896. }
  897. if ($real_cache_path) {
  898. $this->DebugMessage('setting config_cache_directory to realpath('.$this->config_cache_directory.') = "'.$real_cache_path.'"', __FILE__, __LINE__);
  899. $this->config_cache_directory = $real_cache_path;
  900. }
  901. }
  902. if (!is_dir($this->config_cache_directory)) {
  903. if (!$this->config_cache_disable_warning) {
  904. $this->ErrorImage('$this->config_cache_directory ('.$this->config_cache_directory.') does not exist. Adjust "cache_directory" or "cache_disable_warning" in phpThumb.config.php');
  905. }
  906. $this->DebugMessage('$this->config_cache_directory ('.$this->config_cache_directory.') is not a directory', __FILE__, __LINE__);
  907. $this->config_cache_directory = null;
  908. } elseif (!@is_writable($this->config_cache_directory)) {
  909. $this->DebugMessage('$this->config_cache_directory is not writable ('.$this->config_cache_directory.')', __FILE__, __LINE__);
  910. }
  911. $this->InitializeTempDirSetting();
  912. if (!@is_dir($this->config_temp_directory) && !@is_writable($this->config_temp_directory) && @is_dir($this->config_cache_directory) && @is_writable($this->config_cache_directory)) {
  913. $this->DebugMessage('setting $this->config_temp_directory = $this->config_cache_directory ('.$this->config_cache_directory.')', __FILE__, __LINE__);
  914. $this->config_temp_directory = $this->config_cache_directory;
  915. }
  916. return true;
  917. }
  918. /* Takes the array of path segments up to now, and the next segment (maybe a modifier: empty, . or ..)
  919. Applies it, adding or removing from $segments as a result. Returns nothing. */
  920. // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
  921. function applyPathSegment(&$segments, $segment) {
  922. if ($segment == '.') {
  923. return; // always remove
  924. }
  925. if ($segment == '') {
  926. $test = array_pop($segments);
  927. if (is_null($test)) {
  928. $segments[] = $segment; // keep the first empty block
  929. } elseif ($test == '') {
  930. $test = array_pop($segments);
  931. if (is_null($test)) {
  932. $segments[] = $test;
  933. $segments[] = $segment; // keep the second one too
  934. } else { // put both back and ignore segment
  935. $segments[] = $test;
  936. $segments[] = $test;
  937. }
  938. } else {
  939. $segments[] = $test; // ignore empty blocks
  940. }
  941. } else {
  942. if ($segment == '..') {
  943. $test = array_pop($segments);
  944. if (is_null($test)) {
  945. $segments[] = $segment;
  946. } elseif ($test == '..') {
  947. $segments[] = $test;
  948. $segments[] = $segment;
  949. } else {
  950. if ($test == '') {
  951. $segments[] = $test;
  952. } // else nothing, remove both
  953. }
  954. } else {
  955. $segments[] = $segment;
  956. }
  957. }
  958. }
  959. /* Takes array of path components, normalizes it: removes empty slots and '.', collapses '..' and folder names. Returns array. */
  960. // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
  961. function normalizePath($segments) {
  962. $parts = array();
  963. foreach ($segments as $segment) {
  964. $this->applyPathSegment($parts, $segment);
  965. }
  966. return $parts;
  967. }
  968. /* True if the provided path points (without resolving symbolic links) into one of the allowed directories. */
  969. // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
  970. function matchPath($path, $allowed_dirs) {
  971. if (!empty($allowed_dirs)) {
  972. foreach ($allowed_dirs as $one_dir) {
  973. if (preg_match('#^'.preg_quote(str_replace(DIRECTORY_SEPARATOR, '/', realpath($one_dir))).'#', $path)) {
  974. return true;
  975. }
  976. }
  977. }
  978. return false;
  979. }
  980. /* True if the provided path points inside one of open_basedirs (or if open_basedirs are disabled) */
  981. // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
  982. function isInOpenBasedir($path) {
  983. static $open_basedirs = null;
  984. if (is_null($open_basedirs)) {
  985. $ini_text = ini_get('open_basedir');
  986. $this->DebugMessage('open_basedir: "'.$ini_text.'"', __FILE__, __LINE__);
  987. $open_basedirs = array();
  988. if (strlen($ini_text) > 0) {
  989. foreach (preg_split('#[;:]#', $ini_text) as $key => $value) {
  990. $open_basedirs[$key] = realpath($value);
  991. }
  992. }
  993. }
  994. return (empty($open_basedirs) || $this->matchPath($path, $open_basedirs));
  995. }
  996. /* Resolves all symlinks in $path, checking that each continuous part ends in an allowed zone. Returns null, if any component leads outside of allowed zone. */
  997. // http://support.silisoftware.com/phpBB3/viewtopic.php?t=961
  998. function resolvePath($path, $allowed_dirs) {
  999. $this->DebugMessage('resolvePath: '.$path.' (allowed_dirs: '.print_r($allowed_dirs, true).')', __FILE__, __LINE__);
  1000. // add base path to the top of the list
  1001. if (!$this->config_allow_src_above_docroot) {
  1002. array_unshift($allowed_dirs, realpath($this->config_document_root));
  1003. } else {
  1004. if (!$this->config_allow_src_above_phpthumb) {
  1005. array_unshift($allowed_dirs, realpath(dirname(__FILE__)));
  1006. } else {
  1007. // no checks are needed, offload the work to realpath and forget about it
  1008. $this->DebugMessage('resolvePath: checks disabled, returning '.realpath($path), __FILE__, __LINE__);
  1009. return realpath($path);
  1010. }
  1011. }
  1012. if ($path == '') {
  1013. return null; // save us trouble
  1014. }
  1015. do {
  1016. $this->DebugMessage('resolvePath: iteration, path='.$path.', base path = '.$allowed_dirs[0], __FILE__, __LINE__);
  1017. $parts = array();
  1018. foreach (explode(DIRECTORY_SEPARATOR, $path) as $this_segment) {
  1019. $this->applyPathSegment($parts, $this_segment);
  1020. $thispart = implode(DIRECTORY_SEPARATOR, $parts);
  1021. if ($this->isInOpenBasedir($thispart)) {
  1022. if (is_link($thispart)) {
  1023. break;
  1024. }
  1025. }
  1026. }
  1027. $this->DebugMessage('resolvePath: stop at component '.$i, __FILE__, __LINE__);
  1028. // test the part up to here
  1029. $path = implode(DIRECTORY_SEPARATOR, $parts);
  1030. $this->DebugMessage('resolvePath: stop at path='.$path, __FILE__, __LINE__);
  1031. if (!$this->matchPath($path, $allowed_dirs)) {
  1032. $this->DebugMessage('resolvePath: no match, returning null', __FILE__, __LINE__);
  1033. return null;
  1034. }
  1035. if ($i >= count($segments)) { // reached end
  1036. $this->DebugMessage('resolvePath: path parsed, over', __FILE__, __LINE__);
  1037. break;
  1038. }
  1039. // else it's symlink, rewrite path
  1040. $path = readlink($path);
  1041. $this->DebugMessage('resolvePath: symlink matched, target='.$path, __FILE__, __LINE__);
  1042. /*
  1043. Replace base path with symlink target.
  1044. Assuming:
  1045. /www/img/external -> /external
  1046. This is allowed:
  1047. GET /www/img/external/../external/test/pic.jpg
  1048. This isn't:
  1049. GET /www/img/external/../www/img/pic.jpg
  1050. So there's only one base path which is the last symlink target, but any number of stable whitelisted paths.
  1051. */
  1052. if ($this->config_auto_allow_symlinks) {
  1053. $allowed_dirs[0] = $path;
  1054. }
  1055. $path = $path.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, array_slice($segments,$i + 1));
  1056. } while (true);
  1057. return $path;
  1058. }
  1059. function ResolveFilenameToAbsolute($filename) {
  1060. if (empty($filename)) {
  1061. return false;
  1062. }
  1063. if (preg_match('#^[a-z0-9]+\:/{1,2}#i', $filename)) {
  1064. // eg: http://host/path/file.jpg (HTTP URL)
  1065. // eg: ftp://host/path/file.jpg (FTP URL)
  1066. // eg: data1:/path/file.jpg (Netware path)
  1067. //$AbsoluteFilename = $filename;
  1068. return $filename;
  1069. } elseif ($this->iswindows && isset($filename{1}) && ($filename{1} == ':')) {
  1070. // absolute pathname (Windows)
  1071. $AbsoluteFilename = $filename;
  1072. } elseif ($this->iswindows && ((substr($filename, 0, 2) == '//') || (substr($filename, 0, 2) == '\\\\'))) {
  1073. // absolute pathname (Windows)
  1074. $AbsoluteFilename = $filename;
  1075. } elseif ($filename{0} == '/') {
  1076. if (@is_readable($filename) && !@is_readable($this->config_document_root.$filename)) {
  1077. // absolute filename (*nix)
  1078. $AbsoluteFilename = $filename;
  1079. } elseif (isset($filename{1}) && ($filename{1} == '~')) {
  1080. // /~user/path
  1081. if ($ApacheLookupURIarray = phpthumb_functions::ApacheLookupURIarray($filename)) {
  1082. $AbsoluteFilename = $ApacheLookupURIarray['filename'];
  1083. } else {
  1084. $AbsoluteFilename = realpath($filename);
  1085. if (@is_readable($AbsoluteFilename)) {
  1086. $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.$filename.'", but the correct filename ('.$AbsoluteFilename.') seems to have been resolved with realpath($filename)', __FILE__, __LINE__);
  1087. } elseif (is_dir(dirname($AbsoluteFilename))) {
  1088. $this->DebugMessage('phpthumb_functions::ApacheLookupURIarray() failed for "'.dirname($filename).'", but the correct directory ('.dirname($AbsoluteFilename).') seems to have been resolved with realpath(.)', __FILE__, __LINE_

Large files files are truncated, but you can click here to view the full file