StoreRemoteMediaPlugin.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. <?php
  2. if (!defined('GNUSOCIAL')) { exit(1); }
  3. // FIXME: To support remote video/whatever files, this plugin needs reworking.
  4. class StoreRemoteMediaPlugin extends Plugin
  5. {
  6. // settings which can be set in config.php with addPlugin('Oembed', array('param'=>'value', ...));
  7. // WARNING, these are _regexps_ (slashes added later). Always escape your dots and end your strings
  8. public $domain_whitelist = array( // hostname => service provider
  9. '^i\d*\.ytimg\.com$' => 'YouTube',
  10. '^i\d*\.vimeocdn\.com$' => 'Vimeo',
  11. );
  12. public $append_whitelist = array(); // fill this array as domain_whitelist to add more trusted sources
  13. public $check_whitelist = false; // security/abuse precaution
  14. protected $imgData = array();
  15. // these should be declared protected everywhere
  16. public function initialize()
  17. {
  18. parent::initialize();
  19. $this->domain_whitelist = array_merge($this->domain_whitelist, $this->append_whitelist);
  20. }
  21. /**
  22. * Save embedding information for a File, if applicable.
  23. *
  24. * Normally this event is called through File::saveNew()
  25. *
  26. * @param File $file The abount-to-be-inserted File object.
  27. *
  28. * @return boolean success
  29. */
  30. public function onStartFileSaveNew(File &$file)
  31. {
  32. // save given URL as title if it's a media file this plugin understands
  33. // which will make it shown in the AttachmentList widgets
  34. if (isset($file->title) && strlen($file->title)>0) {
  35. // Title is already set
  36. return true;
  37. }
  38. if (!isset($file->mimetype)) {
  39. // Unknown mimetype, it's not our job to figure out what it is.
  40. return true;
  41. }
  42. switch (common_get_mime_media($file->mimetype)) {
  43. case 'image':
  44. // Just to set something for now at least...
  45. $file->title = $file->mimetype;
  46. break;
  47. }
  48. return true;
  49. }
  50. public function onCreateFileImageThumbnailSource(File $file, &$imgPath, $media=null)
  51. {
  52. // If we are on a private node, we won't do any remote calls (just as a precaution until
  53. // we can configure this from config.php for the private nodes)
  54. if (common_config('site', 'private')) {
  55. return true;
  56. }
  57. if ($media !== 'image') {
  58. return true;
  59. }
  60. // If there is a local filename, it is either a local file already or has already been downloaded.
  61. if (!empty($file->filename)) {
  62. return true;
  63. }
  64. $this->checkWhitelist($file->getUrl());
  65. // First we download the file to memory and test whether it's actually an image file
  66. common_debug(sprintf('Downloading remote file id==%u with URL: %s', $file->getID(), _ve($file->getUrl())));
  67. try {
  68. $imgData = HTTPClient::quickGet($file->getUrl());
  69. } catch (HTTP_Request2_ConnectionException $e) {
  70. common_log(LOG_ERR, __CLASS__.': quickGet on URL: '._ve($file->getUrl()).' threw exception: '.$e->getMessage());
  71. return true;
  72. }
  73. $info = @getimagesizefromstring($imgData);
  74. if ($info === false) {
  75. throw new UnsupportedMediaException(_('Remote file format was not identified as an image.'), $file->getUrl());
  76. } elseif (!$info[0] || !$info[1]) {
  77. throw new UnsupportedMediaException(_('Image file had impossible geometry (0 width or height)'));
  78. }
  79. $filehash = hash(File::FILEHASH_ALG, $imgData);
  80. try {
  81. // Exception will be thrown before $file is set to anything, so old $file value will be kept
  82. $file = File::getByHash($filehash);
  83. //FIXME: Add some code so we don't have to store duplicate File rows for same hash files.
  84. } catch (NoResultException $e) {
  85. $filename = $filehash . '.' . common_supported_mime_to_ext($info['mime']);
  86. $fullpath = File::path($filename);
  87. // Write the file to disk if it doesn't exist yet. Throw Exception on failure.
  88. if (!file_exists($fullpath) && file_put_contents($fullpath, $imgData) === false) {
  89. throw new ServerException(_('Could not write downloaded file to disk.'));
  90. }
  91. // Updated our database for the file record
  92. $orig = clone($file);
  93. $file->filehash = $filehash;
  94. $file->filename = $filename;
  95. $file->width = $info[0]; // array indexes documented on php.net:
  96. $file->height = $info[1]; // https://php.net/manual/en/function.getimagesize.php
  97. // Throws exception on failure.
  98. $file->updateWithKeys($orig);
  99. }
  100. // Get rid of the file from memory
  101. unset($imgData);
  102. $imgPath = $file->getPath();
  103. return false;
  104. }
  105. /**
  106. * @return boolean false on no check made, provider name on success
  107. * @throws ServerException if check is made but fails
  108. */
  109. protected function checkWhitelist($url)
  110. {
  111. if (!$this->check_whitelist) {
  112. return false; // indicates "no check made"
  113. }
  114. $host = parse_url($url, PHP_URL_HOST);
  115. foreach ($this->domain_whitelist as $regex => $provider) {
  116. if (preg_match("/$regex/", $host)) {
  117. return $provider; // we trust this source, return provider name
  118. }
  119. }
  120. throw new ServerException(sprintf(_('Domain not in remote source whitelist: %s'), $host));
  121. }
  122. public function onPluginVersion(array &$versions)
  123. {
  124. $versions[] = array('name' => 'StoreRemoteMedia',
  125. 'version' => GNUSOCIAL_VERSION,
  126. 'author' => 'Mikael Nordfeldth',
  127. 'homepage' => 'https://gnu.io/',
  128. 'description' =>
  129. // TRANS: Plugin description.
  130. _m('Plugin for downloading remotely attached files to local server.'));
  131. return true;
  132. }
  133. }