check($refererHost, $hotlinkingWhitelist); if ($allowedByWhitelist) { verbose("Hotlinking/leeching allowed by whitelist."); } else { errorPage("Hotlinking/leeching not allowed by whitelist. Referer: $referer.", 403); } } else { errorPage("Hotlinking/leeching not allowed.", 403); } } verbose("allow_hotlinking = $allowHotlinking"); verbose("referer = $referer"); verbose("referer host = $refererHost"); /** * Create the class for the image. */ $CImage = getConfig('CImage', 'CImage'); $img = new $CImage(); $img->setVerbose($verbose || $verboseFile); /** * Get the cachepath from config. */ $CCache = getConfig('CCache', 'CCache'); $cachePath = getConfig('cache_path', __DIR__ . '/../cache/'); $cache = new $CCache(); $cache->setDir($cachePath); /** * no-cache, nc - skip the cached version and process and create a new version in cache. */ $useCache = getDefined(array('no-cache', 'nc'), false, true); verbose("use cache = $useCache"); /** * Prepare fast track cache for swriting cache items. */ $fastTrackCache = "fasttrack"; $allowFastTrackCache = getConfig('fast_track_allow', false); $CFastTrackCache = getConfig('CFastTrackCache', 'CFastTrackCache'); $ftc = new $CFastTrackCache(); $ftc->setCacheDir($cache->getPathToSubdir($fastTrackCache)) ->enable($allowFastTrackCache) ->setFilename(array('no-cache', 'nc')); $img->injectDependency("fastTrackCache", $ftc); /** * Load and output images from fast track cache, if items are available * in cache. */ if ($useCache && $allowFastTrackCache) { if (CIMAGE_DEBUG) { trace("img.php fast track cache enabled and used"); } $ftc->output(); } /** * Allow or disallow remote download of images from other servers. * Passwords apply if used. * */ $allowRemote = getConfig('remote_allow', false); if ($allowRemote && $passwordMatch !== false) { $cacheRemote = $cache->getPathToSubdir("remote"); $pattern = getConfig('remote_pattern', null); $img->setRemoteDownload($allowRemote, $cacheRemote, $pattern); $whitelist = getConfig('remote_whitelist', null); $img->setRemoteHostWhitelist($whitelist); } /** * shortcut, sc - extend arguments with a constant value, defined * in config-file. */ $shortcut = get(array('shortcut', 'sc'), null); $shortcutConfig = getConfig('shortcut', array( 'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen", )); verbose("shortcut = $shortcut"); if (isset($shortcut) && isset($shortcutConfig[$shortcut])) { parse_str($shortcutConfig[$shortcut], $get); verbose("shortcut-constant = {$shortcutConfig[$shortcut]}"); $_GET = array_merge($_GET, $get); } /** * src - the source image file. */ $srcImage = urldecode(get('src')) or errorPage('Must set src-attribute.', 404); // Get settings for src-alt as backup image $srcAltImage = urldecode(get('src-alt', null)); $srcAltConfig = getConfig('src_alt', null); if (empty($srcAltImage)) { $srcAltImage = $srcAltConfig; } // Check for valid/invalid characters $imagePath = getConfig('image_path', __DIR__ . '/img/'); $imagePathConstraint = getConfig('image_path_constraint', true); $validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#'); // Source is remote $remoteSource = false; // Dummy image feature $dummyEnabled = getConfig('dummy_enabled', true); $dummyFilename = getConfig('dummy_filename', 'dummy'); $dummyImage = false; preg_match($validFilename, $srcImage) or errorPage('Source filename contains invalid characters.', 404); if ($dummyEnabled && $srcImage === $dummyFilename) { // Prepare to create a dummy image and use it as the source image. $dummyImage = true; } elseif ($allowRemote && $img->isRemoteSource($srcImage)) { // If source is a remote file, ignore local file checks. $remoteSource = true; } else { // Check if file exists on disk or try using src-alt $pathToImage = realpath($imagePath . $srcImage); if (!is_file($pathToImage) && !empty($srcAltImage)) { // Try using the src-alt instead $srcImage = $srcAltImage; $pathToImage = realpath($imagePath . $srcImage); preg_match($validFilename, $srcImage) or errorPage('Source (alt) filename contains invalid characters.', 404); if ($dummyEnabled && $srcImage === $dummyFilename) { // Check if src-alt is the dummy image $dummyImage = true; } } if (!$dummyImage) { is_file($pathToImage) or errorPage( 'Source image is not a valid file, check the filename and that a matching file exists on the filesystem.', 404 ); } } if ($imagePathConstraint && !$dummyImage && !$remoteSource) { // Check that the image is a file below the directory 'image_path'. $imageDir = realpath($imagePath); substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0 or errorPage( 'Security constraint: Source image is not below the directory "image_path" as specified in the config file img_config.php.', 404 ); } verbose("src = $srcImage"); /** * Manage size constants from config file, use constants to replace values * for width and height. */ $sizeConstant = getConfig('size_constant', function () { // Set sizes to map constant to value, easier to use with width or height $sizes = array( 'w1' => 613, 'w2' => 630, ); // Add grid column width, useful for use as predefined size for width (or height). $gridColumnWidth = 30; $gridGutterWidth = 10; $gridColumns = 24; for ($i = 1; $i <= $gridColumns; $i++) { $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth; } return $sizes; }); $sizes = call_user_func($sizeConstant); /** * width, w - set target width, affecting the resulting image width, height and resize options */ $newWidth = get(array('width', 'w')); $maxWidth = getConfig('max_width', 2000); // Check to replace predefined size if (isset($sizes[$newWidth])) { $newWidth = $sizes[$newWidth]; } // Support width as % of original width if ($newWidth[strlen($newWidth)-1] == '%') { is_numeric(substr($newWidth, 0, -1)) or errorPage('Width % not numeric.', 404); } else { is_null($newWidth) or ($newWidth > 10 && $newWidth <= $maxWidth) or errorPage('Width out of range.', 404); } verbose("new width = $newWidth"); /** * height, h - set target height, affecting the resulting image width, height and resize options */ $newHeight = get(array('height', 'h')); $maxHeight = getConfig('max_height', 2000); // Check to replace predefined size if (isset($sizes[$newHeight])) { $newHeight = $sizes[$newHeight]; } // height if ($newHeight[strlen($newHeight)-1] == '%') { is_numeric(substr($newHeight, 0, -1)) or errorPage('Height % out of range.', 404); } else { is_null($newHeight) or ($newHeight > 10 && $newHeight <= $maxHeight) or errorPage('Height out of range.', 404); } verbose("new height = $newHeight"); /** * aspect-ratio, ar - affecting the resulting image width, height and resize options */ $aspectRatio = get(array('aspect-ratio', 'ar')); $aspectRatioConstant = getConfig('aspect_ratio_constant', function () { return array( '3:1' => 3/1, '3:2' => 3/2, '4:3' => 4/3, '8:5' => 8/5, '16:10' => 16/10, '16:9' => 16/9, 'golden' => 1.618, ); }); // Check to replace predefined aspect ratio $aspectRatios = call_user_func($aspectRatioConstant); $negateAspectRatio = ($aspectRatio[0] == '!') ? true : false; $aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio; if (isset($aspectRatios[$aspectRatio])) { $aspectRatio = $aspectRatios[$aspectRatio]; } if ($negateAspectRatio) { $aspectRatio = 1 / $aspectRatio; } is_null($aspectRatio) or is_numeric($aspectRatio) or errorPage('Aspect ratio out of range', 404); verbose("aspect ratio = $aspectRatio"); /** * crop-to-fit, cf - affecting the resulting image width, height and resize options */ $cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false); verbose("crop to fit = $cropToFit"); /** * Set default background color from config file. */ $backgroundColor = getConfig('background_color', null); if ($backgroundColor) { $img->setDefaultBackgroundColor($backgroundColor); verbose("Using default background_color = $backgroundColor"); } /** * bgColor - Default background color to use */ $bgColor = get(array('bgColor', 'bg-color', 'bgc'), null); verbose("bgColor = $bgColor"); /** * Do or do not resample image when resizing. */ $resizeStrategy = getDefined(array('no-resample'), true, false); if ($resizeStrategy) { $img->setCopyResizeStrategy($img::RESIZE); verbose("Setting = Resize instead of resample"); } /** * fill-to-fit, ff - affecting the resulting image width, height and resize options */ $fillToFit = get(array('fill-to-fit', 'ff'), null); verbose("fill-to-fit = $fillToFit"); if ($fillToFit !== null) { if (!empty($fillToFit)) { $bgColor = $fillToFit; verbose("fillToFit changed bgColor to = $bgColor"); } $fillToFit = true; verbose("fill-to-fit (fixed) = $fillToFit"); } /** * no-ratio, nr, stretch - affecting the resulting image width, height and resize options */ $keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true); verbose("keep ratio = $keepRatio"); /** * crop, c - affecting the resulting image width, height and resize options */ $crop = get(array('crop', 'c')); verbose("crop = $crop"); /** * area, a - affecting the resulting image width, height and resize options */ $area = get(array('area', 'a')); verbose("area = $area"); /** * skip-original, so - skip the original image and always process a new image */ $useOriginal = getDefined(array('skip-original', 'so'), false, true); $useOriginalDefault = getConfig('skip_original', false); if ($useOriginalDefault === true) { verbose("skip original is default ON"); $useOriginal = false; } verbose("use original = $useOriginal"); /** * quality, q - set level of quality for jpeg images */ $quality = get(array('quality', 'q')); $qualityDefault = getConfig('jpg_quality', null); is_null($quality) or ($quality > 0 and $quality <= 100) or errorPage('Quality out of range', 404); if (is_null($quality) && !is_null($qualityDefault)) { $quality = $qualityDefault; } verbose("quality = $quality"); /** * compress, co - what strategy to use when compressing png images */ $compress = get(array('compress', 'co')); $compressDefault = getConfig('png_compression', null); is_null($compress) or ($compress > 0 and $compress <= 9) or errorPage('Compress out of range', 404); if (is_null($compress) && !is_null($compressDefault)) { $compress = $compressDefault; } verbose("compress = $compress"); /** * save-as, sa - what type of image to save */ $saveAs = get(array('save-as', 'sa')); verbose("save as = $saveAs"); /** * scale, s - Processing option, scale up or down the image prior actual resize */ $scale = get(array('scale', 's')); is_null($scale) or ($scale >= 0 and $scale <= 400) or errorPage('Scale out of range', 404); verbose("scale = $scale"); /** * palette, p - Processing option, create a palette version of the image */ $palette = getDefined(array('palette', 'p'), true, false); verbose("palette = $palette"); /** * sharpen - Processing option, post filter for sharpen effect */ $sharpen = getDefined('sharpen', true, null); verbose("sharpen = $sharpen"); /** * emboss - Processing option, post filter for emboss effect */ $emboss = getDefined('emboss', true, null); verbose("emboss = $emboss"); /** * blur - Processing option, post filter for blur effect */ $blur = getDefined('blur', true, null); verbose("blur = $blur"); /** * rotateBefore - Rotate the image with an angle, before processing */ $rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb')); is_null($rotateBefore) or ($rotateBefore >= -360 and $rotateBefore <= 360) or errorPage('RotateBefore out of range', 404); verbose("rotateBefore = $rotateBefore"); /** * rotateAfter - Rotate the image with an angle, before processing */ $rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r')); is_null($rotateAfter) or ($rotateAfter >= -360 and $rotateAfter <= 360) or errorPage('RotateBefore out of range', 404); verbose("rotateAfter = $rotateAfter"); /** * autoRotate - Auto rotate based on EXIF information */ $autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false); verbose("autoRotate = $autoRotate"); /** * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter() */ $filters = array(); $filter = get(array('filter', 'f')); if ($filter) { $filters[] = $filter; } for ($i = 0; $i < 10; $i++) { $filter = get(array("filter{$i}", "f{$i}")); if ($filter) { $filters[] = $filter; } } verbose("filters = " . print_r($filters, 1)); /** * json - output the image as a JSON object with details on the image. * ascii - output the image as ASCII art. */ $outputFormat = getDefined('json', 'json', null); $outputFormat = getDefined('ascii', 'ascii', $outputFormat); verbose("outputformat = $outputFormat"); if ($outputFormat == 'ascii') { $defaultOptions = getConfig( 'ascii-options', array( "characterSet" => 'two', "scale" => 14, "luminanceStrategy" => 3, "customCharacterSet" => null, ) ); $options = get('ascii'); $options = explode(',', $options); if (isset($options[0]) && !empty($options[0])) { $defaultOptions['characterSet'] = $options[0]; } if (isset($options[1]) && !empty($options[1])) { $defaultOptions['scale'] = $options[1]; } if (isset($options[2]) && !empty($options[2])) { $defaultOptions['luminanceStrategy'] = $options[2]; } if (count($options) > 3) { // Last option is custom character string unset($options[0]); unset($options[1]); unset($options[2]); $characterString = implode($options); $defaultOptions['customCharacterSet'] = $characterString; } $img->setAsciiOptions($defaultOptions); } /** * dpr - change to get larger image to easier support larger dpr, such as retina. */ $dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1); verbose("dpr = $dpr"); /** * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php */ $convolve = get('convolve', null); $convolutionConstant = getConfig('convolution_constant', array()); // Check if the convolve is matching an existing constant if ($convolve && isset($convolutionConstant)) { $img->addConvolveExpressions($convolutionConstant); verbose("convolve constant = " . print_r($convolutionConstant, 1)); } verbose("convolve = " . print_r($convolve, 1)); /** * no-upscale, nu - Do not upscale smaller image to larger dimension. */ $upscale = getDefined(array('no-upscale', 'nu'), false, true); verbose("upscale = $upscale"); /** * Get details for post processing */ $postProcessing = getConfig('postprocessing', array( 'png_lossy' => false, 'png_lossy_cmd' => '/usr/local/bin/pngquant --force --output', 'png_filter' => false, 'png_filter_cmd' => '/usr/local/bin/optipng -q', 'png_deflate' => false, 'png_deflate_cmd' => '/usr/local/bin/pngout -q', 'jpeg_optimize' => false, 'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize', )); /** * lossy - Do lossy postprocessing, if available. */ $lossy = getDefined(array('lossy'), true, null); verbose("lossy = $lossy"); /** * alias - Save resulting image to another alias name. * Password always apply, must be defined. */ $alias = get('alias', null); $aliasPath = getConfig('alias_path', null); $validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#'); $aliasTarget = null; if ($alias && $aliasPath && $passwordMatch) { $aliasTarget = $aliasPath . $alias; $useCache = false; is_writable($aliasPath) or errorPage("Directory for alias is not writable.", 403); preg_match($validAliasname, $alias) or errorPage('Filename for alias contains invalid characters. Do not add extension.', 404); } elseif ($alias) { errorPage('Alias is not enabled in the config file or password not matching.', 403); } verbose("alias = $alias"); /** * Add cache control HTTP header. */ $cacheControl = getConfig('cache_control', null); if ($cacheControl) { verbose("cacheControl = $cacheControl"); $img->addHTTPHeader("Cache-Control", $cacheControl); } /** * Prepare a dummy image and use it as source image. */ if ($dummyImage === true) { $dummyDir = $cache->getPathToSubdir("dummy"); $img->setSaveFolder($dummyDir) ->setSource($dummyFilename, $dummyDir) ->setOptions( array( 'newWidth' => $newWidth, 'newHeight' => $newHeight, 'bgColor' => $bgColor, ) ) ->setJpegQuality($quality) ->setPngCompression($compress) ->createDummyImage() ->generateFilename(null, false) ->save(null, null, false); $srcImage = $img->getTarget(); $imagePath = null; verbose("src (updated) = $srcImage"); } /** * Prepare a sRGB version of the image and use it as source image. */ $srgbDefault = getConfig('srgb_default', false); $srgbColorProfile = getConfig('srgb_colorprofile', __DIR__ . '/../icc/sRGB_IEC61966-2-1_black_scaled.icc'); $srgb = getDefined('srgb', true, null); if ($srgb || $srgbDefault) { $filename = $img->convert2sRGBColorSpace( $srcImage, $imagePath, $cache->getPathToSubdir("srgb"), $srgbColorProfile, $useCache ); if ($filename) { $srcImage = $img->getTarget(); $imagePath = null; verbose("srgb conversion and saved to cache = $srcImage"); } else { verbose("srgb not op"); } } /** * Display status */ if ($status) { $text = "img.php version = " . CIMAGE_VERSION . "\n"; $text .= "PHP version = " . PHP_VERSION . "\n"; $text .= "Running on: " . $_SERVER['SERVER_SOFTWARE'] . "\n"; $text .= "Allow remote images = $allowRemote\n"; $res = $cache->getStatusOfSubdir(""); $text .= "Cache $res\n"; $res = $cache->getStatusOfSubdir("remote"); $text .= "Cache remote $res\n"; $res = $cache->getStatusOfSubdir("dummy"); $text .= "Cache dummy $res\n"; $res = $cache->getStatusOfSubdir("srgb"); $text .= "Cache srgb $res\n"; $res = $cache->getStatusOfSubdir($fastTrackCache); $text .= "Cache fasttrack $res\n"; $text .= "Alias path writable = " . is_writable($aliasPath) . "\n"; $no = extension_loaded('exif') ? null : 'NOT'; $text .= "Extension exif is $no loaded.
"; $no = extension_loaded('curl') ? null : 'NOT'; $text .= "Extension curl is $no loaded.
"; $no = extension_loaded('imagick') ? null : 'NOT'; $text .= "Extension imagick is $no loaded.
"; $no = extension_loaded('gd') ? null : 'NOT'; $text .= "Extension gd is $no loaded.
"; $text .= checkExternalCommand("PNG LOSSY", $postProcessing["png_lossy"], $postProcessing["png_lossy_cmd"]); $text .= checkExternalCommand("PNG FILTER", $postProcessing["png_filter"], $postProcessing["png_filter_cmd"]); $text .= checkExternalCommand("PNG DEFLATE", $postProcessing["png_deflate"], $postProcessing["png_deflate_cmd"]); $text .= checkExternalCommand("JPEG OPTIMIZE", $postProcessing["jpeg_optimize"], $postProcessing["jpeg_optimize_cmd"]); if (!$no) { $text .= print_r(gd_info(), 1); } echo << CImage status
$text
EOD; exit; } /** * Log verbose details to file */ if ($verboseFile) { $img->setVerboseToFile("$cachePath/log.txt"); } /** * Hook after img.php configuration and before processing with CImage */ $hookBeforeCImage = getConfig('hook_before_CImage', null); if (is_callable($hookBeforeCImage)) { verbose("hookBeforeCImage activated"); $allConfig = $hookBeforeCImage($img, array( // Options for calculate dimensions 'newWidth' => $newWidth, 'newHeight' => $newHeight, 'aspectRatio' => $aspectRatio, 'keepRatio' => $keepRatio, 'cropToFit' => $cropToFit, 'fillToFit' => $fillToFit, 'crop' => $crop, 'area' => $area, 'upscale' => $upscale, // Pre-processing, before resizing is done 'scale' => $scale, 'rotateBefore' => $rotateBefore, 'autoRotate' => $autoRotate, // General processing options 'bgColor' => $bgColor, // Post-processing, after resizing is done 'palette' => $palette, 'filters' => $filters, 'sharpen' => $sharpen, 'emboss' => $emboss, 'blur' => $blur, 'convolve' => $convolve, 'rotateAfter' => $rotateAfter, // Output format 'outputFormat' => $outputFormat, 'dpr' => $dpr, // Other 'postProcessing' => $postProcessing, 'lossy' => $lossy, )); verbose(print_r($allConfig, 1)); extract($allConfig); } /** * Display image if verbose mode */ if ($verbose) { $query = array(); parse_str($_SERVER['QUERY_STRING'], $query); unset($query['verbose']); unset($query['v']); unset($query['nocache']); unset($query['nc']); unset($query['json']); $url1 = '?' . htmlentities(urldecode(http_build_query($query))); $url2 = '?' . urldecode(http_build_query($query)); echo << CImage verbose output $url1




EOD;
}



/**
 * Load, process and output the image
 */
$img->log("Incoming arguments: " . print_r(verbose(), 1))
    ->setSaveFolder($cachePath)
    ->useCache($useCache)
    ->setSource($srcImage, $imagePath)
    ->setOptions(
        array(
            // Options for calculate dimensions
            'newWidth'  => $newWidth,
            'newHeight' => $newHeight,
            'aspectRatio' => $aspectRatio,
            'keepRatio' => $keepRatio,
            'cropToFit' => $cropToFit,
            'fillToFit' => $fillToFit,
            'crop'      => $crop,
            'area'      => $area,
            'upscale'   => $upscale,

            // Pre-processing, before resizing is done
            'scale'        => $scale,
            'rotateBefore' => $rotateBefore,
            'autoRotate'   => $autoRotate,

            // General processing options
            'bgColor'    => $bgColor,

            // Post-processing, after resizing is done
            'palette'   => $palette,
            'filters'   => $filters,
            'sharpen'   => $sharpen,
            'emboss'    => $emboss,
            'blur'      => $blur,
            'convolve'  => $convolve,
            'rotateAfter' => $rotateAfter,

            // Output format
            'outputFormat' => $outputFormat,
            'dpr'          => $dpr,

            // Postprocessing using external tools
            'lossy' => $lossy,
        )
    )
    ->loadImageDetails()
    ->initDimensions()
    ->calculateNewWidthAndHeight()
    ->setSaveAsExtension($saveAs)
    ->setJpegQuality($quality)
    ->setPngCompression($compress)
    ->useOriginalIfPossible($useOriginal)
    ->generateFilename($cachePath)
    ->useCacheIfPossible($useCache)
    ->load()
    ->preResize()
    ->resize()
    ->postResize()
    ->setPostProcessingOptions($postProcessing)
    ->save()
    ->linkToCacheFile($aliasTarget)
    ->output();