Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
5.4 KiB

  1. <?php
  2. namespace Friendica\Util;
  3. use Friendica\Core\Config;
  4. use Friendica\DI;
  5. /**
  6. * @brief Proxy utilities class
  7. */
  8. class Proxy
  9. {
  10. /**
  11. * Default time to keep images in proxy storage
  12. */
  13. const DEFAULT_TIME = 86400; // 1 Day
  14. /**
  15. * Sizes constants
  16. */
  17. const SIZE_MICRO = 'micro';
  18. const SIZE_THUMB = 'thumb';
  19. const SIZE_SMALL = 'small';
  20. const SIZE_MEDIUM = 'medium';
  21. const SIZE_LARGE = 'large';
  22. /**
  23. * Accepted extensions
  24. *
  25. * @var array
  26. * @todo Make this configurable?
  27. */
  28. private static $extensions = [
  29. 'jpg',
  30. 'jpeg',
  31. 'gif',
  32. 'png',
  33. ];
  34. /**
  35. * @brief Private constructor
  36. */
  37. private function __construct () {
  38. // No instances from utilities classes
  39. }
  40. /**
  41. * @brief Transform a remote URL into a local one.
  42. *
  43. * This function only performs the URL replacement on http URL and if the
  44. * provided URL isn't local, "the isn't deactivated" (sic) and if the config
  45. * system.proxy_disabled is set to false.
  46. *
  47. * @param string $url The URL to proxyfy
  48. * @param bool $writemode Returns a local path the remote URL should be saved to
  49. * @param string $size One of the ProxyUtils::SIZE_* constants
  50. *
  51. * @return string The proxyfied URL or relative path
  52. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  53. */
  54. public static function proxifyUrl($url, $writemode = false, $size = '')
  55. {
  56. // Get application instance
  57. $a = DI::app();
  58. // Trim URL first
  59. $url = trim($url);
  60. // Is no http in front of it?
  61. /// @TODO To weak test for being a valid URL
  62. if (substr($url, 0, 4) !== 'http') {
  63. return $url;
  64. }
  65. // Only continue if it isn't a local image and the isn't deactivated
  66. if (self::isLocalImage($url)) {
  67. $url = str_replace(Strings::normaliseLink(DI::baseUrl()) . '/', DI::baseUrl() . '/', $url);
  68. return $url;
  69. }
  70. // Is the proxy disabled?
  71. if (Config::get('system', 'proxy_disabled')) {
  72. return $url;
  73. }
  74. // Image URL may have encoded ampersands for display which aren't desirable for proxy
  75. $url = html_entity_decode($url, ENT_NOQUOTES, 'utf-8');
  76. // Creating a sub directory to reduce the amount of files in the cache directory
  77. $basepath = $a->getBasePath() . '/proxy';
  78. $shortpath = hash('md5', $url);
  79. $longpath = substr($shortpath, 0, 2);
  80. if (is_dir($basepath) && $writemode && !is_dir($basepath . '/' . $longpath)) {
  81. mkdir($basepath . '/' . $longpath);
  82. chmod($basepath . '/' . $longpath, 0777);
  83. }
  84. $longpath .= '/' . strtr(base64_encode($url), '+/', '-_');
  85. // Extract the URL extension
  86. $extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION);
  87. if (in_array($extension, self::$extensions)) {
  88. $shortpath .= '.' . $extension;
  89. $longpath .= '.' . $extension;
  90. }
  91. $proxypath = DI::baseUrl() . '/proxy/' . $longpath;
  92. if ($size != '') {
  93. $size = ':' . $size;
  94. }
  95. // Too long files aren't supported by Apache
  96. // Writemode in combination with long files shouldn't be possible
  97. if ((strlen($proxypath) > 250) && $writemode) {
  98. return $shortpath;
  99. } elseif (strlen($proxypath) > 250) {
  100. return DI::baseUrl() . '/proxy/' . $shortpath . '?url=' . urlencode($url);
  101. } elseif ($writemode) {
  102. return $longpath;
  103. } else {
  104. return $proxypath . $size;
  105. }
  106. }
  107. /**
  108. * @brief "Proxifies" HTML code's image tags
  109. *
  110. * "Proxifies", means replaces image URLs in given HTML code with those from
  111. * proxy storage directory.
  112. *
  113. * @param string $html Un-proxified HTML code
  114. *
  115. * @return string Proxified HTML code
  116. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  117. */
  118. public static function proxifyHtml($html)
  119. {
  120. $html = str_replace(Strings::normaliseLink(DI::baseUrl()) . '/', DI::baseUrl() . '/', $html);
  121. return preg_replace_callback('/(<img [^>]*src *= *["\'])([^"\']+)(["\'][^>]*>)/siU', 'self::replaceUrl', $html);
  122. }
  123. /**
  124. * @brief Checks if the URL is a local URL.
  125. *
  126. * @param string $url
  127. * @return boolean
  128. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  129. */
  130. private static function isLocalImage($url)
  131. {
  132. if (substr($url, 0, 1) == '/') {
  133. return true;
  134. }
  135. if (strtolower(substr($url, 0, 5)) == 'data:') {
  136. return true;
  137. }
  138. // links normalised - bug #431
  139. $baseurl = Strings::normaliseLink(DI::baseUrl());
  140. $url = Strings::normaliseLink($url);
  141. return (substr($url, 0, strlen($baseurl)) == $baseurl);
  142. }
  143. /**
  144. * @brief Return the array of query string parameters from a URL
  145. *
  146. * @param string $url URL to parse
  147. * @return array Associative array of query string parameters
  148. */
  149. private static function parseQuery($url)
  150. {
  151. $query = parse_url($url, PHP_URL_QUERY);
  152. $query = html_entity_decode($query);
  153. parse_str($query, $arr);
  154. return $arr;
  155. }
  156. /**
  157. * @brief Call-back method to replace the UR
  158. *
  159. * @param array $matches Matches from preg_replace_callback()
  160. * @return string Proxified HTML image tag
  161. * @throws \Friendica\Network\HTTPException\InternalServerErrorException
  162. */
  163. private static function replaceUrl(array $matches)
  164. {
  165. // if the picture seems to be from another picture cache then take the original source
  166. $queryvar = self::parseQuery($matches[2]);
  167. if (!empty($queryvar['url']) && substr($queryvar['url'], 0, 4) == 'http') {
  168. $matches[2] = urldecode($queryvar['url']);
  169. }
  170. // Following line changed per bug #431
  171. if (self::isLocalImage($matches[2])) {
  172. return $matches[1] . $matches[2] . $matches[3];
  173. }
  174. // Return proxified HTML
  175. return $matches[1] . self::proxifyUrl(htmlspecialchars_decode($matches[2])) . $matches[3];
  176. }
  177. }