| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  | <?php | 
					
						
							| 
									
										
										
										
											2021-04-19 18:51:05 +00:00
										 |  |  | // {{{ License
 | 
					
						
							| 
									
										
										
										
											2021-02-19 10:34:21 +00:00
										 |  |  | // This file is part of GNU social - https://www.gnu.org/software/social
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // GNU social is distributed in the hope that it will be useful,
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with GNU social.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2021-04-19 18:51:05 +00:00
										 |  |  | // }}}
 | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-14 15:27:37 +00:00
										 |  |  | namespace Plugin\StoreRemoteMedia; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  | use App\Core\DB\DB; | 
					
						
							|  |  |  | use App\Core\Event; | 
					
						
							|  |  |  | use App\Core\GSFile; | 
					
						
							|  |  |  | use App\Core\HTTPClient; | 
					
						
							| 
									
										
										
										
											2021-08-18 14:15:30 +01:00
										 |  |  | use function App\Core\I18n\_m; | 
					
						
							|  |  |  | use App\Core\Log; | 
					
						
							| 
									
										
										
										
											2021-04-19 18:51:05 +00:00
										 |  |  | use App\Core\Modules\Plugin; | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  | use App\Entity\AttachmentThumbnail; | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  | use App\Entity\AttachmentToLink; | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  | use App\Entity\AttachmentToNote; | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  | use App\Entity\Link; | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  | use App\Entity\Note; | 
					
						
							|  |  |  | use App\Util\Common; | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  | use App\Util\Exception\DuplicateFoundException; | 
					
						
							|  |  |  | use App\Util\Exception\ServerException; | 
					
						
							|  |  |  | use App\Util\Exception\TemporaryFileException; | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  | use App\Util\TemporaryFile; | 
					
						
							| 
									
										
										
										
											2021-04-14 15:27:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 10:34:21 +00:00
										 |  |  | /** | 
					
						
							|  |  |  |  * The StoreRemoteMedia plugin downloads remotely attached files to local server. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @package   GNUsocial | 
					
						
							| 
									
										
										
										
											2021-04-14 15:27:37 +00:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-02-19 10:34:21 +00:00
										 |  |  |  * @author    Mikael Nordfeldth | 
					
						
							|  |  |  |  * @author    Stephen Paul Weber | 
					
						
							|  |  |  |  * @author    Mikael Nordfeldth | 
					
						
							|  |  |  |  * @author    Miguel Dantas | 
					
						
							|  |  |  |  * @author    Diogo Peralta Cordeiro | 
					
						
							|  |  |  |  * @copyright 2015-2016, 2019-2021 Free Software Foundation, Inc http://www.fsf.org | 
					
						
							|  |  |  |  * @license   https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-04-19 18:51:05 +00:00
										 |  |  | class StoreRemoteMedia extends Plugin | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |     public function version(): string | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |         return '3.0.0'; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-18 14:15:30 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      *  Settings which can be set in social.local.yaml | 
					
						
							|  |  |  |      *  WARNING, these are _regexps_ (slashes added later). Always escape your dots and end ('$') your strings | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public bool $check_whitelist   = false; | 
					
						
							|  |  |  |     public bool $check_blacklist   = false; | 
					
						
							|  |  |  |     public array $domain_whitelist = [ | 
					
						
							|  |  |  |         // hostname
 | 
					
						
							|  |  |  |         '.*', // Default to allowing any host
 | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |     public array $domain_blacklist = []; | 
					
						
							|  |  |  |     // Whether to maintain a copy of the original media or only a thumbnail of it
 | 
					
						
							|  |  |  |     public bool $store_original = false; | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |     public ?int $thumbnail_width; | 
					
						
							|  |  |  |     public ?int $thumbnail_height; | 
					
						
							|  |  |  |     public ?int $max_size; | 
					
						
							|  |  |  |     public ?bool $smart_crop; | 
					
						
							| 
									
										
										
										
											2021-02-21 10:35:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |     private function getStoreOriginal(): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->store_original; | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-08-18 14:15:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |     private function getThumbnailWidth(): int | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |         return $this->thumbnail_width ?? Common::config('thumbnail', 'width'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-06-30 13:36:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |     private function getThumbnailHeight(): int | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return $this->thumbnail_height ?? Common::config('thumbnail', 'height'); | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |     private function getMaxSize(): int | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |         return $this->max_size ?? Common::config('attachments', 'file_quota'); | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |     private function getSmartCrop(): bool | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |         return $this->smart_crop ?? Common::config('thumbnail', 'smart_crop'); | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-06-24 15:56:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |      * @param Link $link | 
					
						
							|  |  |  |      * @param Note $note | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @throws ServerException | 
					
						
							|  |  |  |      * @throws TemporaryFileException | 
					
						
							| 
									
										
										
										
											2021-08-18 14:15:30 +01:00
										 |  |  |      * @throws DuplicateFoundException | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |      * @return bool | 
					
						
							| 
									
										
										
										
											2021-08-18 14:15:30 +01:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |     public function onNewLinkFromNote(Link $link, Note $note): bool | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |         // Embed is the plugin to handle these
 | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |         if ($link->getMimetypeMajor() === 'text') { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |             return Event::next; | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-18 14:15:30 +01:00
										 |  |  |         // Is this URL trusted?
 | 
					
						
							|  |  |  |         if (!$this->allowedLink($link->getUrl())) { | 
					
						
							|  |  |  |             Log::info("Blocked URL ({$link->getUrl()}) in StoreRemoteMedia->onNewLinkFromNote."); | 
					
						
							|  |  |  |             return Event::next; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |         // Have we handled it already?
 | 
					
						
							| 
									
										
										
										
											2021-09-06 23:47:28 +01:00
										 |  |  |         /** @var AttachmentToLink */ | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |         $attachment_to_link = DB::find('attachment_to_link', | 
					
						
							|  |  |  |             ['link_id' => $link->getId()]); | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // If it was handled already
 | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |         if (!is_null($attachment_to_link)) { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |             // Relate the note with the existing attachment
 | 
					
						
							|  |  |  |             DB::persist(AttachmentToNote::create([ | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |                 'attachment_id' => $attachment_to_link->getAttachmentId(), | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |                 'note_id'       => $note->getId(), | 
					
						
							|  |  |  |             ])); | 
					
						
							|  |  |  |             DB::flush(); | 
					
						
							|  |  |  |             return Event::stop; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             // Retrieve media
 | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |             $get_response = HTTPClient::get($link->getUrl()); | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |             $media        = $get_response->getContent(); | 
					
						
							|  |  |  |             $mimetype     = $get_response->getHeaders()['content-type'][0]; | 
					
						
							|  |  |  |             unset($get_response); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Ensure we still want to handle it
 | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |             if ($mimetype != $link->getMimetype()) { | 
					
						
							|  |  |  |                 $link->setMimetype($mimetype); | 
					
						
							|  |  |  |                 DB::persist($link); | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |                 DB::flush(); | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |                 if ($link->getMimetypeMajor() === 'text') { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |                     return Event::next; | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |             // Create an attachment for this
 | 
					
						
							|  |  |  |             $temp_file = new TemporaryFile(); | 
					
						
							|  |  |  |             $temp_file->write($media); | 
					
						
							| 
									
										
										
										
											2021-09-22 15:08:30 +01:00
										 |  |  |             $attachment = GSFile::storeFileAsAttachment($temp_file); | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |             // Relate the link with the attachment
 | 
					
						
							| 
									
										
										
										
											2021-09-23 16:19:34 +01:00
										 |  |  |             // TODO: Create a function that gets the title from content disposition or URL when such header isn't available
 | 
					
						
							| 
									
										
										
										
											2021-08-13 20:09:20 +01:00
										 |  |  |             DB::persist(AttachmentToLink::create([ | 
					
						
							|  |  |  |                 'link_id'       => $link->getId(), | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |                 'attachment_id' => $attachment->getId(), | 
					
						
							|  |  |  |             ])); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Relate the note with the attachment
 | 
					
						
							|  |  |  |             DB::persist(AttachmentToNote::create([ | 
					
						
							|  |  |  |                 'attachment_id' => $attachment->getId(), | 
					
						
							|  |  |  |                 'note_id'       => $note->getId(), | 
					
						
							|  |  |  |             ])); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             DB::flush(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Should we create a thumb and delete the original file?
 | 
					
						
							|  |  |  |             if (!$this->getStoreOriginal()) { | 
					
						
							|  |  |  |                 $thumbnail = AttachmentThumbnail::getOrCreate( | 
					
						
							|  |  |  |                     attachment: $attachment, | 
					
						
							| 
									
										
										
										
											2021-09-25 13:12:32 +01:00
										 |  |  |                     size: 'medium', | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |                     crop: $this->getSmartCrop() | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 $attachment->deleteStorage(); | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |             return Event::stop; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-18 14:15:30 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * @param string $url | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return bool true if allowed by the lists, false otherwise | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function allowedLink(string $url): bool | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $passed_whitelist = !$this->check_whitelist; | 
					
						
							|  |  |  |         $passed_blacklist = !$this->check_blacklist; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($this->check_whitelist) { | 
					
						
							|  |  |  |             $passed_whitelist = false; // don't trust be default
 | 
					
						
							|  |  |  |             $host             = parse_url($url, PHP_URL_HOST); | 
					
						
							|  |  |  |             foreach ($this->domain_whitelist as $regex => $provider) { | 
					
						
							|  |  |  |                 if (preg_match("/{$regex}/", $host)) { | 
					
						
							|  |  |  |                     $passed_whitelist = true; // we trust this source
 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($this->check_blacklist) { | 
					
						
							|  |  |  |             // assume it passed by default
 | 
					
						
							|  |  |  |             $host = parse_url($url, PHP_URL_HOST); | 
					
						
							|  |  |  |             foreach ($this->domain_blacklist as $regex => $provider) { | 
					
						
							|  |  |  |                 if (preg_match("/{$regex}/", $host)) { | 
					
						
							|  |  |  |                     $passed_blacklist = false; // we blocked this source
 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $passed_whitelist && $passed_blacklist; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Event raised when GNU social polls the plugin for information about it. | 
					
						
							|  |  |  |      * Adds this plugin's version information to $versions array | 
					
						
							|  |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-09-06 23:47:28 +01:00
										 |  |  |      * @param array $versions inherited from parent | 
					
						
							| 
									
										
										
										
											2021-04-14 15:27:37 +00:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2021-02-16 18:30:21 +00:00
										 |  |  |      * @return bool true hook value | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2019-08-12 15:03:30 +01:00
										 |  |  |     public function onPluginVersion(array &$versions): bool | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-08-12 00:39:36 +01:00
										 |  |  |         $versions[] = [ | 
					
						
							|  |  |  |             'name'        => 'StoreRemoteMedia', | 
					
						
							|  |  |  |             'version'     => $this->version(), | 
					
						
							|  |  |  |             'author'      => 'Mikael Nordfeldth, Diogo Peralta Cordeiro', | 
					
						
							|  |  |  |             'homepage'    => GNUSOCIAL_PROJECT_URL, | 
					
						
							|  |  |  |             'description' => // TRANS: Plugin description.
 | 
					
						
							|  |  |  |                 _m('Plugin for downloading remotely attached files to local server.'), | 
					
						
							|  |  |  |         ]; | 
					
						
							|  |  |  |         return Event::next; | 
					
						
							| 
									
										
										
										
											2015-10-01 22:18:47 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-04-14 15:27:37 +00:00
										 |  |  | } |