Issues (49)

Security Analysis    12 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting (10)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation (2)
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

webroot/img.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Resize and crop images on the fly, store generated images in a cache.
4
 *
5
 * @author  Mikael Roos [email protected]
6
 * @example http://dbwebb.se/opensource/cimage
7
 * @link    https://github.com/mosbth/cimage
8
 *
9
 */
10
11
/**
12
 * Custom exception handler.
13
 */
14
set_exception_handler(function ($exception) {
15
    errorPage(
16
        "<p><b>img.php: Uncaught exception:</b> <p>"
17
        . $exception->getMessage()
18
        . "</p><pre>"
19
        . $exception->getTraceAsString()
20
        . "</pre>",
21
        500
22
    );
23
});
24
25
26
27
/**
28
 * Get configuration options from file, if the file exists, else use $config
29
 * if its defined or create an empty $config.
30
 */
31
$configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php';
32
33
if (is_file($configFile)) {
34
    $config = require $configFile;
35
} elseif (!isset($config)) {
36
    $config = array();
37
}
38
39
// Make CIMAGE_DEBUG false by default, if not already defined
40
if (!defined("CIMAGE_DEBUG")) {
41
    define("CIMAGE_DEBUG", false);
42
}
43
44
45
46
/**
47
 * Setup the autoloader, but not when using a bundle.
48
 */
49
if (!defined("CIMAGE_BUNDLE")) {
50
    if (!isset($config["autoloader"])) {
51
        die("CImage: Missing autoloader.");
52
    }
53
54
    require $config["autoloader"];
55
}
56
57
58
59
/**
60
* verbose, v - do a verbose dump of what happens
61
* vf - do verbose dump to file
62
*/
63
$verbose = getDefined(array('verbose', 'v'), true, false);
64
$verboseFile = getDefined('vf', true, false);
65
verbose("img.php version = " . CIMAGE_VERSION);
66
67
68
69
/**
70
* status - do a verbose dump of the configuration
71
*/
72
$status = getDefined('status', true, false);
73
74
75
76
/**
77
 * Set mode as strict, production or development.
78
 * Default is production environment.
79
 */
80
$mode = getConfig('mode', 'production');
81
82
// Settings for any mode
83
set_time_limit(20);
84
ini_set('gd.jpeg_ignore_warning', 1);
85
86
if (!extension_loaded('gd')) {
87
    errorPage("Extension gd is not loaded.", 500);
88
}
89
90
// Specific settings for each mode
91
if ($mode == 'strict') {
92
93
    error_reporting(0);
94
    ini_set('display_errors', 0);
95
    ini_set('log_errors', 1);
96
    $verbose = false;
97
    $status = false;
98
    $verboseFile = false;
99
100
} elseif ($mode == 'production') {
101
102
    error_reporting(-1);
103
    ini_set('display_errors', 0);
104
    ini_set('log_errors', 1);
105
    $verbose = false;
106
    $status = false;
107
    $verboseFile = false;
108
109
} elseif ($mode == 'development') {
110
111
    error_reporting(-1);
112
    ini_set('display_errors', 1);
113
    ini_set('log_errors', 0);
114
    $verboseFile = false;
115
116
} elseif ($mode == 'test') {
117
118
    error_reporting(-1);
119
    ini_set('display_errors', 1);
120
    ini_set('log_errors', 0);
121
122
} else {
123
    errorPage("Unknown mode: $mode", 500);
124
}
125
126
verbose("mode = $mode");
127
verbose("error log = " . ini_get('error_log'));
128
129
130
131
/**
132
 * Set default timezone if not set or if its set in the config-file.
133
 */
134
$defaultTimezone = getConfig('default_timezone', null);
135
136
if ($defaultTimezone) {
137
    date_default_timezone_set($defaultTimezone);
138
} elseif (!ini_get('default_timezone')) {
139
    date_default_timezone_set('UTC');
140
}
141
142
143
144
/**
145
 * Check if passwords are configured, used and match.
146
 * Options decide themself if they require passwords to be used.
147
 */
148
$pwdConfig   = getConfig('password', false);
149
$pwdAlways   = getConfig('password_always', false);
150
$pwdType     = getConfig('password_type', 'text');
151
$pwd         = get(array('password', 'pwd'), null);
152
153
// Check if passwords match, if configured to use passwords
154
$passwordMatch = null;
155
if ($pwd) {
156
    switch ($pwdType) {
157
        case 'md5':
158
            $passwordMatch = ($pwdConfig === md5($pwd));
159
            break;
160
        case 'hash':
161
            $passwordMatch = password_verify($pwd, $pwdConfig);
162
            break;
163
        case 'text':
164
            $passwordMatch = ($pwdConfig === $pwd);
165
            break;
166
        default:
167
            $passwordMatch = false;
168
    }
169
}
170
171
if ($pwdAlways && $passwordMatch !== true) {
172
    errorPage("Password required and does not match or exists.", 403);
173
}
174
175
verbose("password match = $passwordMatch");
176
177
178
179
/**
180
 * Prevent hotlinking, leeching, of images by controlling who access them
181
 * from where.
182
 *
183
 */
184
$allowHotlinking = getConfig('allow_hotlinking', true);
185
$hotlinkingWhitelist = getConfig('hotlinking_whitelist', array());
186
187
$serverName  = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
188
$referer     = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
189
$refererHost = parse_url($referer, PHP_URL_HOST);
190
191
if (!$allowHotlinking) {
192
    if ($passwordMatch) {
193
        ; // Always allow when password match
194
        verbose("Hotlinking since passwordmatch");
195
    } elseif ($passwordMatch === false) {
196
        errorPage("Hotlinking/leeching not allowed when password missmatch.", 403);
197
    } elseif (!$referer) {
198
        errorPage("Hotlinking/leeching not allowed and referer is missing.", 403);
199
    } elseif (strcmp($serverName, $refererHost) == 0) {
200
        ; // Allow when serverName matches refererHost
201
        verbose("Hotlinking disallowed but serverName matches refererHost.");
202
    } elseif (!empty($hotlinkingWhitelist)) {
203
        $whitelist = new CWhitelist();
204
        $allowedByWhitelist = $whitelist->check($refererHost, $hotlinkingWhitelist);
0 ignored issues
show
It seems like $refererHost defined by parse_url($referer, PHP_URL_HOST) on line 189 can also be of type false; however, CWhitelist::check() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
205
206
        if ($allowedByWhitelist) {
207
            verbose("Hotlinking/leeching allowed by whitelist.");
208
        } else {
209
            errorPage("Hotlinking/leeching not allowed by whitelist. Referer: $referer.", 403);
210
        }
211
212
    } else {
213
        errorPage("Hotlinking/leeching not allowed.", 403);
214
    }
215
}
216
217
verbose("allow_hotlinking = $allowHotlinking");
218
verbose("referer = $referer");
219
verbose("referer host = $refererHost");
220
221
222
223
/**
224
 * Create the class for the image.
225
 */
226
$CImage = getConfig('CImage', 'CImage');
227
$img = new $CImage();
228
$img->setVerbose($verbose || $verboseFile);
229
230
231
232
/**
233
 * Get the cachepath from config.
234
 */
235
$CCache = getConfig('CCache', 'CCache');
236
$cachePath = getConfig('cache_path', __DIR__ . '/../cache/');
237
$cache = new $CCache();
238
$cache->setDir($cachePath);
239
240
241
242
/**
243
 * no-cache, nc - skip the cached version and process and create a new version in cache.
244
 */
245
$useCache = getDefined(array('no-cache', 'nc'), false, true);
246
247
verbose("use cache = $useCache");
248
249
250
251
/**
252
 * Prepare fast track cache for swriting cache items.
253
 */
254
$fastTrackCache = "fasttrack";
255
$allowFastTrackCache = getConfig('fast_track_allow', false);
256
257
$CFastTrackCache = getConfig('CFastTrackCache', 'CFastTrackCache');
258
$ftc = new $CFastTrackCache();
259
$ftc->setCacheDir($cache->getPathToSubdir($fastTrackCache))
260
    ->enable($allowFastTrackCache)
261
    ->setFilename(array('no-cache', 'nc'));
262
$img->injectDependency("fastTrackCache", $ftc);
263
264
265
266
/**
267
 *  Load and output images from fast track cache, if items are available
268
 * in cache.
269
 */
270
if ($useCache && $allowFastTrackCache) {
271
    if (CIMAGE_DEBUG) {
272
        trace("img.php fast track cache enabled and used");
273
    }
274
    $ftc->output();
275
}
276
277
278
279
/**
280
 * Allow or disallow remote download of images from other servers.
281
 * Passwords apply if used.
282
 *
283
 */
284
$allowRemote = getConfig('remote_allow', false);
285
286
if ($allowRemote && $passwordMatch !== false) {
287
    $cacheRemote = $cache->getPathToSubdir("remote");
288
289
    $pattern = getConfig('remote_pattern', null);
290
    $img->setRemoteDownload($allowRemote, $cacheRemote, $pattern);
291
292
    $whitelist = getConfig('remote_whitelist', null);
293
    $img->setRemoteHostWhitelist($whitelist);
294
}
295
296
297
298
/**
299
 * shortcut, sc - extend arguments with a constant value, defined
300
 * in config-file.
301
 */
302
$shortcut       = get(array('shortcut', 'sc'), null);
303
$shortcutConfig = getConfig('shortcut', array(
304
    'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen",
305
));
306
307
verbose("shortcut = $shortcut");
308
309
if (isset($shortcut)
310
    && isset($shortcutConfig[$shortcut])) {
311
312
    parse_str($shortcutConfig[$shortcut], $get);
313
    verbose("shortcut-constant = {$shortcutConfig[$shortcut]}");
314
    $_GET = array_merge($_GET, $get);
315
}
316
317
318
319
/**
320
 * src - the source image file.
321
 */
322
$srcImage = urldecode(get('src'))
323
    or errorPage('Must set src-attribute.', 404);
324
325
// Get settings for src-alt as backup image
326
$srcAltImage = urldecode(get('src-alt', null));
327
$srcAltConfig = getConfig('src_alt', null);
328
if (empty($srcAltImage)) {
329
    $srcAltImage = $srcAltConfig;
330
}
331
332
// Check for valid/invalid characters
333
$imagePath           = getConfig('image_path', __DIR__ . '/img/');
334
$imagePathConstraint = getConfig('image_path_constraint', true);
335
$validFilename       = getConfig('valid_filename', '#^[a-z0-9A-Z-/_ \.:]+$#');
336
337
// Source is remote
338
$remoteSource = false;
339
340
// Dummy image feature
341
$dummyEnabled  = getConfig('dummy_enabled', true);
342
$dummyFilename = getConfig('dummy_filename', 'dummy');
343
$dummyImage = false;
344
345
preg_match($validFilename, $srcImage)
346
    or errorPage('Source filename contains invalid characters.', 404);
347
348
if ($dummyEnabled && $srcImage === $dummyFilename) {
349
350
    // Prepare to create a dummy image and use it as the source image.
351
    $dummyImage = true;
352
353
} elseif ($allowRemote && $img->isRemoteSource($srcImage)) {
354
355
    // If source is a remote file, ignore local file checks.
356
    $remoteSource = true;
357
358
} else {
359
360
    // Check if file exists on disk or try using src-alt
361
    $pathToImage = realpath($imagePath . $srcImage);
362
363
    if (!is_file($pathToImage) && !empty($srcAltImage)) {
364
        // Try using the src-alt instead
365
        $srcImage = $srcAltImage;
366
        $pathToImage = realpath($imagePath . $srcImage);
367
368
        preg_match($validFilename, $srcImage)
369
            or errorPage('Source (alt) filename contains invalid characters.', 404);
370
371
        if ($dummyEnabled && $srcImage === $dummyFilename) {
372
            // Check if src-alt is the dummy image
373
            $dummyImage = true;
374
        }
375
    }
376
377
    if (!$dummyImage) {
378
        is_file($pathToImage)
379
            or errorPage(
380
                'Source image is not a valid file, check the filename and that a
381
                matching file exists on the filesystem.',
382
                404
383
            );
384
    }
385
}
386
387
if ($imagePathConstraint && !$dummyImage && !$remoteSource) {
388
    // Check that the image is a file below the directory 'image_path'.
389
    $imageDir = realpath($imagePath);
390
391
    substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0
392
        or errorPage(
393
            'Security constraint: Source image is not below the directory "image_path"
394
            as specified in the config file img_config.php.',
395
            404
396
        );
397
}
398
399
verbose("src = $srcImage");
400
401
402
403
/**
404
 * Manage size constants from config file, use constants to replace values
405
 * for width and height.
406
 */
407
$sizeConstant = getConfig('size_constant', function () {
408
409
    // Set sizes to map constant to value, easier to use with width or height
410
    $sizes = array(
411
        'w1' => 613,
412
        'w2' => 630,
413
    );
414
415
    // Add grid column width, useful for use as predefined size for width (or height).
416
    $gridColumnWidth = 30;
417
    $gridGutterWidth = 10;
418
    $gridColumns     = 24;
419
420
    for ($i = 1; $i <= $gridColumns; $i++) {
421
        $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth;
422
    }
423
424
    return $sizes;
425
});
426
427
$sizes = call_user_func($sizeConstant);
428
429
430
431
/**
432
 * width, w - set target width, affecting the resulting image width, height and resize options
433
 */
434
$newWidth     = get(array('width', 'w'));
435
$maxWidth     = getConfig('max_width', 2000);
436
437
// Check to replace predefined size
438
if (isset($sizes[$newWidth])) {
439
    $newWidth = $sizes[$newWidth];
440
}
441
442
// Support width as % of original width
443
if ($newWidth && $newWidth[strlen($newWidth)-1] == '%') {
444
    is_numeric(substr($newWidth, 0, -1))
445
        or errorPage('Width % not numeric.', 404);
446
} else {
447
    is_null($newWidth)
448
        or ($newWidth > 10 && $newWidth <= $maxWidth)
449
        or errorPage('Width out of range.', 404);
450
}
451
452
verbose("new width = $newWidth");
453
454
455
456
/**
457
 * height, h - set target height, affecting the resulting image width, height and resize options
458
 */
459
$newHeight = get(array('height', 'h'));
460
$maxHeight = getConfig('max_height', 2000);
461
462
// Check to replace predefined size
463
if (isset($sizes[$newHeight])) {
464
    $newHeight = $sizes[$newHeight];
465
}
466
467
// height
468
if ($newHeight && $newHeight[strlen($newHeight)-1] == '%') {
469
    is_numeric(substr($newHeight, 0, -1))
470
        or errorPage('Height % out of range.', 404);
471
} else {
472
    is_null($newHeight)
473
        or ($newHeight > 10 && $newHeight <= $maxHeight)
474
        or errorPage('Height out of range.', 404);
475
}
476
477
verbose("new height = $newHeight");
478
479
480
481
/**
482
 * aspect-ratio, ar - affecting the resulting image width, height and resize options
483
 */
484
$aspectRatio         = get(array('aspect-ratio', 'ar'));
485
$aspectRatioConstant = getConfig('aspect_ratio_constant', function () {
486
    return array(
487
        '3:1'    => 3/1,
488
        '3:2'    => 3/2,
489
        '4:3'    => 4/3,
490
        '8:5'    => 8/5,
491
        '16:10'  => 16/10,
492
        '16:9'   => 16/9,
493
        'golden' => 1.618,
494
    );
495
});
496
497
// Check to replace predefined aspect ratio
498
$aspectRatios = call_user_func($aspectRatioConstant);
499
$negateAspectRatio = ($aspectRatio && $aspectRatio[0] == '!') ? true : false;
500
$aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio;
501
502
if (isset($aspectRatios[$aspectRatio])) {
503
    $aspectRatio = $aspectRatios[$aspectRatio];
504
}
505
506
if ($negateAspectRatio) {
507
    $aspectRatio = 1 / $aspectRatio;
508
}
509
510
is_null($aspectRatio)
511
    or is_numeric($aspectRatio)
512
    or errorPage('Aspect ratio out of range', 404);
513
514
verbose("aspect ratio = $aspectRatio");
515
516
517
518
/**
519
 * crop-to-fit, cf - affecting the resulting image width, height and resize options
520
 */
521
$cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false);
522
523
verbose("crop to fit = $cropToFit");
524
525
526
527
/**
528
 * Set default background color from config file.
529
 */
530
$backgroundColor = getConfig('background_color', null);
531
532
if ($backgroundColor) {
533
    $img->setDefaultBackgroundColor($backgroundColor);
534
    verbose("Using default background_color = $backgroundColor");
535
}
536
537
538
539
/**
540
 * bgColor - Default background color to use
541
 */
542
$bgColor = get(array('bgColor', 'bg-color', 'bgc'), null);
543
544
verbose("bgColor = $bgColor");
545
546
547
548
/**
549
 * Do or do not resample image when resizing.
550
 */
551
$resizeStrategy = getDefined(array('no-resample'), true, false);
552
553
if ($resizeStrategy) {
554
    $img->setCopyResizeStrategy($img::RESIZE);
555
    verbose("Setting = Resize instead of resample");
556
}
557
558
559
560
561
/**
562
 * fill-to-fit, ff - affecting the resulting image width, height and resize options
563
 */
564
$fillToFit = get(array('fill-to-fit', 'ff'), null);
565
566
verbose("fill-to-fit = $fillToFit");
567
568
if ($fillToFit !== null) {
569
570
    if (!empty($fillToFit)) {
571
        $bgColor   = $fillToFit;
572
        verbose("fillToFit changed bgColor to = $bgColor");
573
    }
574
575
    $fillToFit = true;
576
    verbose("fill-to-fit (fixed) = $fillToFit");
577
}
578
579
580
581
/**
582
 * no-ratio, nr, stretch - affecting the resulting image width, height and resize options
583
 */
584
$keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true);
585
586
verbose("keep ratio = $keepRatio");
587
588
589
590
/**
591
 * crop, c - affecting the resulting image width, height and resize options
592
 */
593
$crop = get(array('crop', 'c'));
594
595
verbose("crop = $crop");
596
597
598
599
/**
600
 * area, a - affecting the resulting image width, height and resize options
601
 */
602
$area = get(array('area', 'a'));
603
604
verbose("area = $area");
605
606
607
608
/**
609
 * skip-original, so - skip the original image and always process a new image
610
 */
611
$useOriginal = getDefined(array('skip-original', 'so'), false, true);
612
$useOriginalDefault = getConfig('skip_original', false);
613
614
if ($useOriginalDefault === true) {
615
    verbose("skip original is default ON");
616
    $useOriginal = false;
617
}
618
619
verbose("use original = $useOriginal");
620
621
622
623
/**
624
 * quality, q - set level of quality for jpeg images
625
 */
626
$quality = get(array('quality', 'q'));
627
$qualityDefault = getConfig('jpg_quality', null);
628
629
is_null($quality)
630
    or ($quality > 0 and $quality <= 100)
631
    or errorPage('Quality out of range', 404);
632
633
if (is_null($quality) && !is_null($qualityDefault)) {
634
    $quality = $qualityDefault;
635
}
636
637
verbose("quality = $quality");
638
639
640
641
/**
642
 * compress, co - what strategy to use when compressing png images
643
 */
644
$compress = get(array('compress', 'co'));
645
$compressDefault = getConfig('png_compression', null);
646
647
is_null($compress)
648
    or ($compress > 0 and $compress <= 9)
649
    or errorPage('Compress out of range', 404);
650
651
if (is_null($compress) && !is_null($compressDefault)) {
652
    $compress = $compressDefault;
653
}
654
655
verbose("compress = $compress");
656
657
658
659
/**
660
 * save-as, sa - what type of image to save
661
 */
662
$saveAs = get(array('save-as', 'sa'));
663
664
verbose("save as = $saveAs");
665
666
667
668
/**
669
 * scale, s - Processing option, scale up or down the image prior actual resize
670
 */
671
$scale = get(array('scale', 's'));
672
673
is_null($scale)
674
    or ($scale >= 0 and $scale <= 400)
675
    or errorPage('Scale out of range', 404);
676
677
verbose("scale = $scale");
678
679
680
681
/**
682
 * palette, p - Processing option, create a palette version of the image
683
 */
684
$palette = getDefined(array('palette', 'p'), true, false);
685
686
verbose("palette = $palette");
687
688
689
690
/**
691
 * sharpen - Processing option, post filter for sharpen effect
692
 */
693
$sharpen = getDefined('sharpen', true, null);
694
695
verbose("sharpen = $sharpen");
696
697
698
699
/**
700
 * emboss - Processing option, post filter for emboss effect
701
 */
702
$emboss = getDefined('emboss', true, null);
703
704
verbose("emboss = $emboss");
705
706
707
708
/**
709
 * blur - Processing option, post filter for blur effect
710
 */
711
$blur = getDefined('blur', true, null);
712
713
verbose("blur = $blur");
714
715
716
717
/**
718
 * rotateBefore - Rotate the image with an angle, before processing
719
 */
720
$rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb'));
721
722
is_null($rotateBefore)
723
    or ($rotateBefore >= -360 and $rotateBefore <= 360)
724
    or errorPage('RotateBefore out of range', 404);
725
726
verbose("rotateBefore = $rotateBefore");
727
728
729
730
/**
731
 * rotateAfter - Rotate the image with an angle, before processing
732
 */
733
$rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r'));
734
735
is_null($rotateAfter)
736
    or ($rotateAfter >= -360 and $rotateAfter <= 360)
737
    or errorPage('RotateBefore out of range', 404);
738
739
verbose("rotateAfter = $rotateAfter");
740
741
742
743
/**
744
 * autoRotate - Auto rotate based on EXIF information
745
 */
746
$autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false);
747
748
verbose("autoRotate = $autoRotate");
749
750
751
752
/**
753
 * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter()
754
 */
755
$filters = array();
756
$filter = get(array('filter', 'f'));
757
if ($filter) {
758
    $filters[] = $filter;
759
}
760
761
for ($i = 0; $i < 10; $i++) {
762
    $filter = get(array("filter{$i}", "f{$i}"));
763
    if ($filter) {
764
        $filters[] = $filter;
765
    }
766
}
767
768
verbose("filters = " . print_r($filters, 1));
769
770
771
772
/**
773
* json -  output the image as a JSON object with details on the image.
774
* ascii - output the image as ASCII art.
775
 */
776
$outputFormat = getDefined('json', 'json', null);
777
$outputFormat = getDefined('ascii', 'ascii', $outputFormat);
778
779
verbose("outputformat = $outputFormat");
780
781
if ($outputFormat == 'ascii') {
782
    $defaultOptions = getConfig(
783
        'ascii-options',
784
        array(
785
            "characterSet" => 'two',
786
            "scale" => 14,
787
            "luminanceStrategy" => 3,
788
            "customCharacterSet" => null,
789
        )
790
    );
791
    $options = get('ascii');
792
    $options = explode(',', $options);
793
794
    if (isset($options[0]) && !empty($options[0])) {
795
        $defaultOptions['characterSet'] = $options[0];
796
    }
797
798
    if (isset($options[1]) && !empty($options[1])) {
799
        $defaultOptions['scale'] = $options[1];
800
    }
801
802
    if (isset($options[2]) && !empty($options[2])) {
803
        $defaultOptions['luminanceStrategy'] = $options[2];
804
    }
805
806
    if (count($options) > 3) {
807
        // Last option is custom character string
808
        unset($options[0]);
809
        unset($options[1]);
810
        unset($options[2]);
811
        $characterString = implode($options);
812
        $defaultOptions['customCharacterSet'] = $characterString;
813
    }
814
815
    $img->setAsciiOptions($defaultOptions);
816
}
817
818
819
820
821
/**
822
 * dpr - change to get larger image to easier support larger dpr, such as retina.
823
 */
824
$dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1);
825
826
verbose("dpr = $dpr");
827
828
829
830
/**
831
 * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php
832
 */
833
$convolve = get('convolve', null);
834
$convolutionConstant = getConfig('convolution_constant', array());
835
836
// Check if the convolve is matching an existing constant
837
if ($convolve && isset($convolutionConstant)) {
838
    $img->addConvolveExpressions($convolutionConstant);
839
    verbose("convolve constant = " . print_r($convolutionConstant, 1));
840
}
841
842
verbose("convolve = " . print_r($convolve, 1));
843
844
845
846
/**
847
 * no-upscale, nu - Do not upscale smaller image to larger dimension.
848
 */
849
$upscale = getDefined(array('no-upscale', 'nu'), false, true);
850
851
verbose("upscale = $upscale");
852
853
854
855
/**
856
 * Get details for post processing
857
 */
858
$postProcessing = getConfig('postprocessing', array(
859
    'png_lossy'        => false,
860
    'png_lossy_cmd'    => '/usr/local/bin/pngquant --force --output',
861
862
    'png_filter'        => false,
863
    'png_filter_cmd'    => '/usr/local/bin/optipng -q',
864
865
    'png_deflate'       => false,
866
    'png_deflate_cmd'   => '/usr/local/bin/pngout -q',
867
868
    'jpeg_optimize'     => false,
869
    'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize',
870
));
871
872
873
874
/**
875
 * lossy - Do lossy postprocessing, if available.
876
 */
877
$lossy = getDefined(array('lossy'), true, null);
878
879
verbose("lossy = $lossy");
880
881
882
883
/**
884
 * alias - Save resulting image to another alias name.
885
 * Password always apply, must be defined.
886
 */
887
$alias          = get('alias', null);
888
$aliasPath      = getConfig('alias_path', null);
889
$validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#');
890
$aliasTarget    = null;
891
892
if ($alias && $aliasPath && $passwordMatch) {
893
894
    $aliasTarget = $aliasPath . $alias;
895
    $useCache    = false;
896
897
    is_writable($aliasPath)
898
        or errorPage("Directory for alias is not writable.", 403);
899
900
    preg_match($validAliasname, $alias)
901
        or errorPage('Filename for alias contains invalid characters. Do not add extension.', 404);
902
903
} elseif ($alias) {
904
    errorPage('Alias is not enabled in the config file or password not matching.', 403);
905
}
906
907
verbose("alias = $alias");
908
909
910
911
/**
912
 * Add cache control HTTP header.
913
 */
914
$cacheControl = getConfig('cache_control', null);
915
916
if ($cacheControl) {
917
    verbose("cacheControl = $cacheControl");
918
    $img->addHTTPHeader("Cache-Control", $cacheControl);
919
}
920
921
922
923
/**
924
 * interlace - Enable configuration for interlaced progressive JPEG images.
925
 */
926
$interlaceConfig  = getConfig('interlace', null);
927
$interlaceValue   = getValue('interlace', null);
928
$interlaceDefined = getDefined('interlace', true, null);
929
$interlace = $interlaceValue ?? $interlaceDefined ?? $interlaceConfig;
930
verbose("interlace (configfile) = ", $interlaceConfig);
931
verbose("interlace = ", $interlace);
932
933
934
935
/**
936
 * Prepare a dummy image and use it as source image.
937
 */
938
if ($dummyImage === true) {
939
    $dummyDir = $cache->getPathToSubdir("dummy");
940
941
    $img->setSaveFolder($dummyDir)
942
        ->setSource($dummyFilename, $dummyDir)
943
        ->setOptions(
944
            array(
945
                'newWidth'  => $newWidth,
946
                'newHeight' => $newHeight,
947
                'bgColor'   => $bgColor,
948
            )
949
        )
950
        ->setJpegQuality($quality)
951
        ->setPngCompression($compress)
952
        ->createDummyImage()
953
        ->generateFilename(null, false)
954
        ->save(null, null, false);
955
956
    $srcImage = $img->getTarget();
957
    $imagePath = null;
958
959
    verbose("src (updated) = $srcImage");
960
}
961
962
963
964
/**
965
 * Prepare a sRGB version of the image and use it as source image.
966
 */
967
$srgbDefault = getConfig('srgb_default', false);
968
$srgbColorProfile = getConfig('srgb_colorprofile', __DIR__ . '/../icc/sRGB_IEC61966-2-1_black_scaled.icc');
969
$srgb = getDefined('srgb', true, null);
970
971
if ($srgb || $srgbDefault) {
972
973
    $filename = $img->convert2sRGBColorSpace(
974
        $srcImage,
975
        $imagePath,
976
        $cache->getPathToSubdir("srgb"),
977
        $srgbColorProfile,
978
        $useCache
979
    );
980
981
    if ($filename) {
982
        $srcImage = $img->getTarget();
983
        $imagePath = null;
984
        verbose("srgb conversion and saved to cache = $srcImage");
985
    } else {
986
        verbose("srgb not op");
987
    }
988
}
989
990
991
992
/**
993
 * Display status
994
 */
995
if ($status) {
996
    $text  = "img.php version = " . CIMAGE_VERSION . "\n";
997
    $text .= "PHP version = " . PHP_VERSION . "\n";
998
    $text .= "Running on: " . $_SERVER['SERVER_SOFTWARE'] . "\n";
999
    $text .= "Allow remote images = $allowRemote\n";
1000
1001
    $res = $cache->getStatusOfSubdir("");
1002
    $text .= "Cache $res\n";
1003
1004
    $res = $cache->getStatusOfSubdir("remote");
1005
    $text .= "Cache remote $res\n";
1006
1007
    $res = $cache->getStatusOfSubdir("dummy");
1008
    $text .= "Cache dummy $res\n";
1009
1010
    $res = $cache->getStatusOfSubdir("srgb");
1011
    $text .= "Cache srgb $res\n";
1012
1013
    $res = $cache->getStatusOfSubdir($fastTrackCache);
1014
    $text .= "Cache fasttrack $res\n";
1015
1016
    $text .= "Alias path writable = " . is_writable($aliasPath) . "\n";
1017
1018
    $no = extension_loaded('exif') ? null : 'NOT';
1019
    $text .= "Extension exif is $no loaded.<br>";
1020
1021
    $no = extension_loaded('curl') ? null : 'NOT';
1022
    $text .= "Extension curl is $no loaded.<br>";
1023
1024
    $no = extension_loaded('imagick') ? null : 'NOT';
1025
    $text .= "Extension imagick is $no loaded.<br>";
1026
1027
    $no = extension_loaded('gd') ? null : 'NOT';
1028
    $text .= "Extension gd is $no loaded.<br>";
1029
1030
    $text .= checkExternalCommand("PNG LOSSY", $postProcessing["png_lossy"], $postProcessing["png_lossy_cmd"]);
1031
    $text .= checkExternalCommand("PNG FILTER", $postProcessing["png_filter"], $postProcessing["png_filter_cmd"]);
1032
    $text .= checkExternalCommand("PNG DEFLATE", $postProcessing["png_deflate"], $postProcessing["png_deflate_cmd"]);
1033
    $text .= checkExternalCommand("JPEG OPTIMIZE", $postProcessing["jpeg_optimize"], $postProcessing["jpeg_optimize_cmd"]);
1034
1035
    if (!$no) {
1036
        $text .= print_r(gd_info(), 1);
1037
    }
1038
1039
    echo <<<EOD
1040
<!doctype html>
1041
<html lang=en>
1042
<meta charset=utf-8>
1043
<title>CImage status</title>
1044
<pre>$text</pre>
1045
EOD;
1046
    exit;
1047
}
1048
1049
1050
1051
/**
1052
 * Log verbose details to file
1053
 */
1054
if ($verboseFile) {
1055
    $img->setVerboseToFile("$cachePath/log.txt");
1056
}
1057
1058
1059
1060
/**
1061
 * Hook after img.php configuration and before processing with CImage
1062
 */
1063
$hookBeforeCImage = getConfig('hook_before_CImage', null);
1064
1065
if (is_callable($hookBeforeCImage)) {
1066
    verbose("hookBeforeCImage activated");
1067
1068
    $allConfig = $hookBeforeCImage($img, array(
1069
            // Options for calculate dimensions
1070
            'newWidth'  => $newWidth,
1071
            'newHeight' => $newHeight,
1072
            'aspectRatio' => $aspectRatio,
1073
            'keepRatio' => $keepRatio,
1074
            'cropToFit' => $cropToFit,
1075
            'fillToFit' => $fillToFit,
1076
            'crop'      => $crop,
1077
            'area'      => $area,
1078
            'upscale'   => $upscale,
1079
1080
            // Pre-processing, before resizing is done
1081
            'scale'        => $scale,
1082
            'rotateBefore' => $rotateBefore,
1083
            'autoRotate'   => $autoRotate,
1084
1085
            // General processing options
1086
            'bgColor'    => $bgColor,
1087
1088
            // Post-processing, after resizing is done
1089
            'palette'   => $palette,
1090
            'filters'   => $filters,
1091
            'sharpen'   => $sharpen,
1092
            'emboss'    => $emboss,
1093
            'blur'      => $blur,
1094
            'convolve'  => $convolve,
1095
            'rotateAfter' => $rotateAfter,
1096
            'interlace' => $interlace,
1097
1098
            // Output format
1099
            'outputFormat' => $outputFormat,
1100
            'dpr'          => $dpr,
1101
1102
            // Other
1103
            'postProcessing' => $postProcessing,
1104
            'lossy' => $lossy,
1105
    ));
1106
    verbose(print_r($allConfig, 1));
1107
    extract($allConfig);
1108
}
1109
1110
1111
1112
/**
1113
 * Display image if verbose mode
1114
 */
1115
if ($verbose) {
1116
    $query = array();
1117
    parse_str($_SERVER['QUERY_STRING'], $query);
1118
    unset($query['verbose']);
1119
    unset($query['v']);
1120
    unset($query['nocache']);
1121
    unset($query['nc']);
1122
    unset($query['json']);
1123
    $url1 = '?' . htmlentities(urldecode(http_build_query($query)));
1124
    $url2 = '?' . urldecode(http_build_query($query));
1125
    echo <<<EOD
1126
<!doctype html>
1127
<html lang=en>
1128
<meta charset=utf-8>
1129
<title>CImage verbose output</title>
1130
<style>body{background-color: #ddd}</style>
1131
<a href=$url1><code>$url1</code></a><br>
1132
<img src='{$url1}' />
1133
<pre id="json"></pre>
1134
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
1135
<script type="text/javascript">
1136
window.getDetails = function (url, id) {
1137
  $.getJSON(url, function(data) {
1138
    element = document.getElementById(id);
1139
    element.innerHTML = "filename: " + data.filename + "\\nmime type: " + data.mimeType + "\\ncolors: " + data.colors + "\\nsize: " + data.size + "\\nwidth: " + data.width + "\\nheigh: " + data.height + "\\naspect-ratio: " + data.aspectRatio + ( data.pngType ? "\\npng-type: " + data.pngType : '');
1140
  });
1141
}
1142
</script>
1143
<script type="text/javascript">window.getDetails("{$url2}&json", "json")</script>
1144
EOD;
1145
}
1146
1147
1148
1149
/**
1150
 * Load, process and output the image
1151
 */
1152
$img->log("Incoming arguments: " . print_r(verbose(), 1))
1153
    ->setSaveFolder($cachePath)
1154
    ->useCache($useCache)
1155
    ->setSource($srcImage, $imagePath)
1156
    ->setOptions(
1157
        array(
1158
            // Options for calculate dimensions
1159
            'newWidth'  => $newWidth,
1160
            'newHeight' => $newHeight,
1161
            'aspectRatio' => $aspectRatio,
1162
            'keepRatio' => $keepRatio,
1163
            'cropToFit' => $cropToFit,
1164
            'fillToFit' => $fillToFit,
1165
            'crop'      => $crop,
1166
            'area'      => $area,
1167
            'upscale'   => $upscale,
1168
1169
            // Pre-processing, before resizing is done
1170
            'scale'        => $scale,
1171
            'rotateBefore' => $rotateBefore,
1172
            'autoRotate'   => $autoRotate,
1173
1174
            // General processing options
1175
            'bgColor'    => $bgColor,
1176
1177
            // Post-processing, after resizing is done
1178
            'palette'   => $palette,
1179
            'filters'   => $filters,
1180
            'sharpen'   => $sharpen,
1181
            'emboss'    => $emboss,
1182
            'blur'      => $blur,
1183
            'convolve'  => $convolve,
1184
            'rotateAfter' => $rotateAfter,
1185
            'interlace' => $interlace,
1186
1187
            // Output format
1188
            'outputFormat' => $outputFormat,
1189
            'dpr'          => $dpr,
1190
1191
            // Postprocessing using external tools
1192
            'lossy' => $lossy,
1193
        )
1194
    )
1195
    ->loadImageDetails()
1196
    ->initDimensions()
1197
    ->calculateNewWidthAndHeight()
1198
    ->setSaveAsExtension($saveAs)
1199
    ->setJpegQuality($quality)
1200
    ->setPngCompression($compress)
1201
    ->useOriginalIfPossible($useOriginal)
1202
    ->generateFilename($cachePath)
1203
    ->useCacheIfPossible($useCache)
1204
    ->load()
1205
    ->preResize()
1206
    ->resize()
1207
    ->postResize()
1208
    ->setPostProcessingOptions($postProcessing)
1209
    ->save()
1210
    ->linkToCacheFile($aliasTarget)
1211
    ->output();
1212