X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModule%2FProxy.php;h=6ca383f59b344a3100e19f425aadae627217ec58;hb=cb3f09ae4f344ff83fca9dc435f3cbad1972737f;hp=8c3493b2adaa6033027b78cf160dacaae6ca31a2;hpb=45e5a7f59ff9ee5f088d4b3eef6f989c9876d083;p=friendica.git diff --git a/src/Module/Proxy.php b/src/Module/Proxy.php index 8c3493b2ad..6ca383f59b 100644 --- a/src/Module/Proxy.php +++ b/src/Module/Proxy.php @@ -5,20 +5,20 @@ */ namespace Friendica\Module; -use Friendica\App; use Friendica\BaseModule; -use Friendica\Core\Config; use Friendica\Core\L10n; use Friendica\Core\System; -use Friendica\Database\DBA; use Friendica\Model\Photo; use Friendica\Object\Image; -use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use Friendica\Util\Proxy as ProxyUtils; /** * @brief Module Proxy + * + * urls: + * /proxy/[sub1/[sub2/]][.ext][:size] + * /proxy?url= */ class Proxy extends BaseModule { @@ -28,7 +28,6 @@ class Proxy extends BaseModule * * Sets application instance and checks if /proxy/ path is writable. * - * @param \Friendica\App $app Application instance */ public static function init() { @@ -66,21 +65,105 @@ class Proxy extends BaseModule header_remove('pragma'); } - $thumb = false; - $size = 1024; - $sizetype = ''; + $direct_cache = self::setupDirectCache(); + + $request = self::getRequestInfo(); + + if (empty($request['url'])) { + System::httpExit(400, ['title' => L10n::t('Bad Request.')]); + } + + // Webserver already tried direct cache... + + // Try to use filecache; + $cachefile = self::responseFromCache($request); + + // Try to use photo from db + self::responseFromDB($request); + + + // + // If script is here, the requested url has never cached before. + // Let's fetch it, scale it if required, then save it in cache. + // + + + // It shouldn't happen but it does - spaces in URL + $request['url'] = str_replace(' ', '+', $request['url']); + $redirects = 0; + $fetchResult = Network::fetchUrlFull($request['url'], true, $redirects, 10); + $img_str = $fetchResult->getBody(); + + $tempfile = tempnam(get_temppath(), 'cache'); + file_put_contents($tempfile, $img_str); + $mime = mime_content_type($tempfile); + unlink($tempfile); + + // If there is an error then return a blank image + if ((substr($fetchResult->getReturnCode(), 0, 1) == '4') || (!$img_str)) { + self::responseError($request); + // stop. + } + + $image = new Image($img_str, $mime); + if (!$image->isValid()) { + self::responseError($request); + // stop. + } + $basepath = $a->getBasePath(); + + // Store original image + if ($direct_cache) { + // direct cache , store under ./proxy/ + file_put_contents($basepath . '/proxy/' . ProxyUtils::proxifyUrl($request['url'], true), $image->asString()); + } elseif($cachefile !== '') { + // cache file + file_put_contents($cachefile, $image->asString()); + } else { + // database + Photo::store($image, 0, 0, $request['urlhash'], $request['url'], '', 100); + } - // If the cache path isn't there, try to create it - if (!is_dir($basepath . '/proxy') && is_writable($basepath)) { - mkdir($basepath . '/proxy'); + + // reduce quality - if it isn't a GIF + if ($image->getType() != 'image/gif') { + $image->scaleDown($request['size']); } - // Checking if caching into a folder in the webroot is activated and working - $direct_cache = (is_dir($basepath . '/proxy') && is_writable($basepath . '/proxy')); + // Store scaled image + if ($direct_cache && $request['sizetype'] != '') { + file_put_contents($basepath . '/proxy/' . ProxyUtils::proxifyUrl($request['url'], true) . $request['sizetype'], $image->asString()); + } + + self::responseImageHttpCache($image); + // stop. + } + + + /** + * @brief Build info about requested image to be proxied + * + * @return array + * [ + * 'url' => requested url, + * 'urlhash' => sha1 has of the url prefixed with 'pic:', + * 'size' => requested image size (int) + * 'sizetype' => requested image size (string): ':micro', ':thumb', ':small', ':medium', ':large' + * ] + * @throws \Exception + */ + private static function getRequestInfo() + { + $a = self::getApp(); + $url = ''; + $size = 1024; + $sizetype = ''; + + // Look for filename in the arguments - if ((isset($a->argv[1]) || isset($a->argv[2]) || isset($a->argv[3])) && !isset($_REQUEST['url'])) { + if (($a->argc > 1) && !isset($_REQUEST['url'])) { if (isset($a->argv[3])) { $url = $a->argv[3]; } elseif (isset($a->argv[2])) { @@ -89,6 +172,7 @@ class Proxy extends BaseModule $url = $a->argv[1]; } + /// @TODO: Why? And what about $url in this case? if (isset($a->argv[3]) && ($a->argv[3] == 'thumb')) { $size = 200; } @@ -103,7 +187,7 @@ class Proxy extends BaseModule $sizetype = ':thumb'; $url = substr($url, 0, -6); } elseif (substr($url, -6) == ':small') { - $size = 175; + $size = 300; $url = substr($url, 0, -6); $sizetype = ':small'; } elseif (substr($url, -7) == ':medium') { @@ -125,148 +209,116 @@ class Proxy extends BaseModule $url = base64_decode(strtr($url, '-_', '+/'), true); - if ($url) { - $_REQUEST['url'] = $url; - } - } else { - $direct_cache = false; - } - - if (empty($_REQUEST['url'])) { - System::httpExit(400, ["title" => L10n::t('Bad Request.')]); - } - - if (!$direct_cache) { - $urlhash = 'pic:' . sha1($_REQUEST['url']); - - $cachefile = get_cachefile(hash('md5', $_REQUEST['url'])); - if ($cachefile != '' && file_exists($cachefile)) { - $img_str = file_get_contents($cachefile); - $mime = mime_content_type($cachefile); - - header('Content-type: ' . $mime); - header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); - header('Etag: "' . md5($img_str) . '"'); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (31536000)) . ' GMT'); - header('Cache-Control: max-age=31536000'); - - // reduce quality - if it isn't a GIF - if ($mime != 'image/gif') { - $image = new Image($img_str, $mime); - - if ($image->isValid()) { - $img_str = $image->asString(); - } - } - - echo $img_str; - exit(); - } } else { - $cachefile = ''; + $url = defaults($_REQUEST, 'url', ''); } + + return [ + 'url' => $url, + 'urlhash' => 'pic:' . sha1($url), + 'size' => $size, + 'sizetype' => $sizetype, + ]; + } - $valid = true; - $photo = null; - - if (!$direct_cache && ($cachefile == '')) { - $photo = DBA::selectFirst('photo', ['data', 'desc'], ['resource-id' => $urlhash]); - - if (DBA::isResult($photo)) { - $img_str = $photo['data']; - $mime = $photo['desc']; - - if ($mime == '') { - $mime = 'image/jpeg'; - } - } - } - if (!DBA::isResult($photo)) { - // It shouldn't happen but it does - spaces in URL - $_REQUEST['url'] = str_replace(' ', '+', $_REQUEST['url']); - $redirects = 0; - $fetchResult = Network::fetchUrlFull($_REQUEST['url'], true, $redirects, 10); - $img_str = $fetchResult->getBody(); - - $tempfile = tempnam(get_temppath(), 'cache'); - file_put_contents($tempfile, $img_str); - $mime = mime_content_type($tempfile); - unlink($tempfile); - - // If there is an error then return a blank image - if ((substr($fetchResult->getReturnCode(), 0, 1) == '4') || (!$img_str)) { - $img_str = file_get_contents('images/blank.png'); - $mime = 'image/png'; - $cachefile = ''; // Clear the cachefile so that the dummy isn't stored - $valid = false; - $image = new Image($img_str, 'image/png'); - - if ($image->isValid()) { - $image->scaleDown(10); - $img_str = $image->asString(); - } - } elseif ($mime != 'image/jpeg' && !$direct_cache && $cachefile == '') { - $image = @imagecreatefromstring($img_str); - - if ($image === FALSE) { - die(); - } - - $fields = ['uid' => 0, 'contact-id' => 0, 'guid' => System::createGUID(), 'resource-id' => $urlhash, 'created' => DateTimeFormat::utcNow(), 'edited' => DateTimeFormat::utcNow(), - 'filename' => basename($_REQUEST['url']), 'type' => '', 'album' => '', 'height' => imagesy($image), 'width' => imagesx($image), - 'datasize' => 0, 'data' => $img_str, 'scale' => 100, 'profile' => 0, - 'allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '', 'desc' => $mime]; - DBA::insert('photo', $fields); - } else { - $image = new Image($img_str, $mime); + /** + * @brief setup ./proxy folder for direct cache + * + * @return bool False if direct cache can't be used. + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function setupDirectCache() + { + $a = self::getApp(); + $basepath = $a->getBasePath(); - if ($image->isValid() && !$direct_cache && ($cachefile == '')) { - Photo::store($image, 0, 0, $urlhash, $_REQUEST['url'], '', 100); - } - } + // If the cache path isn't there, try to create it + if (!is_dir($basepath . '/proxy') && is_writable($basepath)) { + mkdir($basepath . '/proxy'); } - $img_str_orig = $img_str; + // Checking if caching into a folder in the webroot is activated and working + $direct_cache = (is_dir($basepath . '/proxy') && is_writable($basepath . '/proxy')); + // we don't use direct cache if image url is passed in args and not in querystring + $direct_cache = $direct_cache && ($a->argc > 1) && !isset($_REQUEST['url']); + + return $direct_cache; + } - // reduce quality - if it isn't a GIF - if ($mime != 'image/gif') { - $image = new Image($img_str, $mime); - if ($image->isValid()) { - $image->scaleDown($size); - $img_str = $image->asString(); - } + /** + * @brief Try to reply with image in cachefile + * + * @param array $request Array from getRequestInfo + * + * @return string Cache file name, empty string if cache is not enabled. + * + * If cachefile exists, script ends here and this function will never returns + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + private static function responseFromCache(&$request) + { + $cachefile = get_cachefile(hash('md5', $request['url'])); + if ($cachefile != '' && file_exists($cachefile)) { + $img = new Image(file_get_contents($cachefile), mime_content_type($cachefile)); + self::responseImageHttpCache($img); + // stop. } + return $cachefile; + } - /* - * If there is a real existing directory then put the cache file there - * advantage: real file access is really fast - * Otherwise write in cachefile - */ - if ($valid && $direct_cache) { - file_put_contents($basepath . '/proxy/' . ProxyUtils::proxifyUrl($_REQUEST['url'], true), $img_str_orig); - - if ($sizetype != '') { - file_put_contents($basepath . '/proxy/' . ProxyUtils::proxifyUrl($_REQUEST['url'], true) . $sizetype, $img_str); - } - } elseif ($cachefile != '') { - file_put_contents($cachefile, $img_str_orig); + /** + * @brief Try to reply with image in database + * + * @param array $request Array from getRequestInfo + * + * If the image exists in database, then script ends here and this function will never returns + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + private static function responseFromDB(&$request) { + + $photo = Photo::getPhoto($request['urlhash']); + + if ($photo !== false) { + $img = Photo::getImageForPhoto($photo); + self::responseImageHttpCache($img); + // stop. } + } + + /** + * @brief Output a blank image, without cache headers, in case of errors + * + */ + private static function responseError() { + header('Content-type: ' . $img->getType()); + echo file_get_contents('images/blank.png'); + exit(); + } - header('Content-type: ' . $mime); - - // Only output the cache headers when the file is valid - if ($valid) { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); - header('Etag: "' . md5($img_str) . '"'); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (31536000)) . ' GMT'); - header('Cache-Control: max-age=31536000'); + /** + * @brief Output the image with cache headers + * + * @param Image $img + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function responseImageHttpCache(Image $img) + { + if (is_null($img) || !$img->isValid()) { + self::responseError(); + // stop. } - - echo $img_str; - + header('Content-type: ' . $img->getType()); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); + header('Etag: "' . md5($img->asString()) . '"'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (31536000)) . ' GMT'); + header('Cache-Control: max-age=31536000'); + echo $img->asString(); exit(); } - } + +