]> git.mxchange.org Git - friendica.git/blob - src/Util/Proxy.php
Merge pull request #10362 from tobiasd/2021.06-CHANGELOG
[friendica.git] / src / Util / Proxy.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Util;
23
24 use Friendica\DI;
25
26 /**
27  * Proxy utilities class
28  */
29 class Proxy
30 {
31
32         /**
33          * Default time to keep images in proxy storage
34          */
35         const DEFAULT_TIME = 86400; // 1 Day
36
37         /**
38          * Sizes constants
39          */
40         const SIZE_MICRO  = 'micro'; // 48
41         const SIZE_THUMB  = 'thumb'; // 80
42         const SIZE_SMALL  = 'small'; // 300
43         const SIZE_MEDIUM = 'medium'; // 600
44         const SIZE_LARGE  = 'large'; // 1024
45
46         /**
47          * Pixel Sizes
48          */
49         const PIXEL_MICRO  = 48;
50         const PIXEL_THUMB  = 80;
51         const PIXEL_SMALL  = 300;
52         const PIXEL_MEDIUM = 600;
53         const PIXEL_LARGE  = 1024;
54
55         /**
56          * Accepted extensions
57          *
58          * @var array
59          * @todo Make this configurable?
60          */
61         private static $extensions = [
62                 'jpg',
63                 'jpeg',
64                 'gif',
65                 'png',
66         ];
67
68         /**
69          * Private constructor
70          */
71         private function __construct () {
72                 // No instances from utilities classes
73         }
74
75         /**
76          * Transform a remote URL into a local one.
77          *
78          * This function only performs the URL replacement on http URL and if the
79          * provided URL isn't local, "the isn't deactivated" (sic) and if the config
80          * system.proxy_disabled is set to false.
81          *
82          * @param string $url       The URL to proxyfy
83          * @param bool   $writemode Returns a local path the remote URL should be saved to
84          * @param string $size      One of the ProxyUtils::SIZE_* constants
85          *
86          * @return string The proxyfied URL or relative path
87          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
88          */
89         public static function proxifyUrl($url, $writemode = false, $size = '')
90         {
91                 // Get application instance
92                 $a = DI::app();
93
94                 // Trim URL first
95                 $url = trim($url);
96
97                 // Is no http in front of it?
98                 /// @TODO To weak test for being a valid URL
99                 if (substr($url, 0, 4) !== 'http') {
100                         return $url;
101                 }
102
103                 // Only continue if it isn't a local image and the isn't deactivated
104                 if (self::isLocalImage($url)) {
105                         $url = str_replace(Strings::normaliseLink(DI::baseUrl()) . '/', DI::baseUrl() . '/', $url);
106                         return $url;
107                 }
108
109                 // Is the proxy disabled?
110                 if (DI::config()->get('system', 'proxy_disabled')) {
111                         return $url;
112                 }
113
114                 // Image URL may have encoded ampersands for display which aren't desirable for proxy
115                 $url = html_entity_decode($url, ENT_NOQUOTES, 'utf-8');
116
117                 // Creating a sub directory to reduce the amount of files in the cache directory
118                 $basepath = $a->getBasePath() . '/proxy';
119
120                 $shortpath = hash('md5', $url);
121                 $longpath = substr($shortpath, 0, 2);
122
123                 if (is_dir($basepath) && $writemode && !is_dir($basepath . '/' . $longpath)) {
124                         mkdir($basepath . '/' . $longpath);
125                         chmod($basepath . '/' . $longpath, 0777);
126                 }
127
128                 $longpath .= '/' . strtr(base64_encode($url), '+/', '-_');
129
130                 // Extract the URL extension
131                 $extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION);
132
133                 if (in_array($extension, self::$extensions)) {
134                         $shortpath .= '.' . $extension;
135                         $longpath .= '.' . $extension;
136                 }
137
138                 $proxypath = DI::baseUrl() . '/proxy/' . $longpath;
139
140                 if ($size != '') {
141                         $size = ':' . $size;
142                 }
143
144                 // Too long files aren't supported by Apache
145                 // Writemode in combination with long files shouldn't be possible
146                 if ((strlen($proxypath) > 250) && $writemode) {
147                         return $shortpath;
148                 } elseif (strlen($proxypath) > 250) {
149                         return DI::baseUrl() . '/proxy/' . $shortpath . '?url=' . urlencode($url);
150                 } elseif ($writemode) {
151                         return $longpath;
152                 } else {
153                         return $proxypath . $size;
154                 }
155         }
156
157         /**
158          * "Proxifies" HTML code's image tags
159          *
160          * "Proxifies", means replaces image URLs in given HTML code with those from
161          * proxy storage directory.
162          *
163          * @param string $html Un-proxified HTML code
164          *
165          * @return string Proxified HTML code
166          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
167          */
168         public static function proxifyHtml($html)
169         {
170                 $html = str_replace(Strings::normaliseLink(DI::baseUrl()) . '/', DI::baseUrl() . '/', $html);
171
172                 return preg_replace_callback('/(<img [^>]*src *= *["\'])([^"\']+)(["\'][^>]*>)/siU', 'self::replaceUrl', $html);
173         }
174
175         /**
176          * Checks if the URL is a local URL.
177          *
178          * @param string $url
179          * @return boolean
180          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
181          */
182         public static function isLocalImage($url)
183         {
184                 if (substr($url, 0, 1) == '/') {
185                         return true;
186                 }
187
188                 if (strtolower(substr($url, 0, 5)) == 'data:') {
189                         return true;
190                 }
191
192                 // links normalised - bug #431
193                 $baseurl = Strings::normaliseLink(DI::baseUrl());
194                 $url = Strings::normaliseLink($url);
195
196                 return (substr($url, 0, strlen($baseurl)) == $baseurl);
197         }
198
199         /**
200          * Return the array of query string parameters from a URL
201          *
202          * @param string $url URL to parse
203          * @return array Associative array of query string parameters
204          */
205         private static function parseQuery($url)
206         {
207                 $query = parse_url($url, PHP_URL_QUERY);
208                 $query = html_entity_decode($query);
209
210                 parse_str($query, $arr);
211
212                 return $arr;
213         }
214
215         /**
216          * Call-back method to replace the UR
217          *
218          * @param array $matches Matches from preg_replace_callback()
219          * @return string Proxified HTML image tag
220          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
221          */
222         private static function replaceUrl(array $matches)
223         {
224                 // if the picture seems to be from another picture cache then take the original source
225                 $queryvar = self::parseQuery($matches[2]);
226
227                 if (!empty($queryvar['url']) && substr($queryvar['url'], 0, 4) == 'http') {
228                         $matches[2] = urldecode($queryvar['url']);
229                 }
230
231                 // Following line changed per bug #431
232                 if (self::isLocalImage($matches[2])) {
233                         return $matches[1] . $matches[2] . $matches[3];
234                 }
235
236                 // Return proxified HTML
237                 return $matches[1] . self::proxifyUrl(htmlspecialchars_decode($matches[2])) . $matches[3];
238         }
239
240 }