Add image proxy
authorMatthias Schmidt <gravatronics@live.com>
Thu, 6 Aug 2015 16:18:53 +0000 (18:18 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Thu, 6 Aug 2015 16:19:46 +0000 (18:19 +0200)
CHANGELOG.md
com.woltlab.wcf/option.xml
wcfsetup/install/files/acp/install.php
wcfsetup/install/files/js/WCF.ImageViewer.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Dialog.js
wcfsetup/install/files/lib/action/ImageProxyAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/bbcode/ImageBBCode.class.php
wcfsetup/install/files/lib/system/cronjob/DailyCleanUpCronjob.class.php

index 95c9806abfb0b283835fd5e4f002efb129ce89b2..f5c767e1ab0c6c53f22b7071a2ca00f2188425fd 100644 (file)
@@ -27,6 +27,7 @@
 * Replaced old user bulk processing with new implementation using the abstract bulk processing system.
 * `conditionContainers` template event in template `noticeAdd.tpl` added.
 * Use condition system for user search.
+* Image proxy for images included with the image BBCode.
 * Overhauled Redactor integration
        * Linebreaks mode instead of using paragraphs, works better with the PHP-side parser which works with linebreaks
        * Ported the PHP-BBCode parser, massively improves accuracy and ensures validity
\ No newline at end of file
index a08960ffadaee805b2bd7d256715a4f82d9e017b..2d908677a48bb2867c78cf3aeeb882cb0b72c980 100644 (file)
                                        <category name="message.general.edit">
                                                <parent>message.general</parent>
                                        </category>
+                                       <category name="message.general.image">
+                                               <parent>message.general</parent>
+                                       </category>
                                
                                <category name="message.attachment">
                                        <parent>message</parent>
@@ -971,6 +974,27 @@ redis:cache_source_redis_host,!cache_source_memcached_host]]></enableoptions>
                        </option>
                        <!-- /message.general.share -->
                        
+                       <!-- message.general.image -->
+                       <option name="module_image_proxy">
+                               <categoryname>message.general.image</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <enableoptions><![CDATA[image_proxy_secret]]></enableoptions>
+                       </option>
+                       <option name="image_proxy_secret">
+                               <categoryname>message.general.image</categoryname>
+                               <optiontype>text</optiontype>
+                               <allowemptyvalue>0</allowemptyvalue>
+                       </option>
+                       <option name="image_proxy_expiration">
+                               <categoryname>message.general.image</categoryname>
+                               <optiontype>integer</optiontype>
+                               <defaultvalue>14</defaultvalue>
+                               <maxvalue>30</maxvalue>
+                               <minvalue>7</minvalue>
+                       </option>
+                       <!-- /message.general.image -->
+                       
                        <!-- message.censorship -->
                        <option name="enable_censorship">
                                <categoryname>message.censorship</categoryname>
index 0f6fe42d62b1d2e310991ac81c9ede8dfde19e1b..6f3b17dd4b3a6249771e67a469003dfdd9bde1fa 100644 (file)
@@ -2,6 +2,7 @@
 use wcf\system\session\SessionHandler;
 use wcf\system\WCF;
 use wcf\util\DateUtil;
+use wcf\util\StringUtil;
 
 /**
  * @author     Marcel Werk
@@ -64,3 +65,14 @@ if ($timezone = @date_default_timezone_get()) {
                $statement->execute(array($timezone, 'timezone'));
        }
 }
+
+// set image proxy secret
+$sql = "UPDATE wcf".WCF_N."_option
+       SET     optionValue = ?
+       WHERE   optionName = ?";
+$statement = WCF::getDB()->prepareStatement($sql);
+$statement->execute([
+       StringUtil::getRandomID(),
+       'image_proxy_secret'
+]);
+
index d251716aba31420173764a09d8691e13e15bd02d..0ee764c9ac7034983294bd03d5d00d62e2a1831a 100644 (file)
@@ -1217,7 +1217,7 @@ $.widget('ui.wcfImageViewer', {
                        
                        $images.push({
                                image: {
-                                       fullURL: $link.prop('href'),
+                                       fullURL: $thumbnail.data('source') ? $thumbnail.data('source').replace(/\\\//g, '/') : $link.prop('href'),
                                        link: '',
                                        title: $link.prop('title'),
                                        url: $link.prop('href'),
index db2821e5dba48d6ce997c7cf799d6cc806aa88b0..b2e3a42b14d369cbc3ce5e086176b35a715c711f 100644 (file)
@@ -295,12 +295,7 @@ define(
                                        content.innerHTML = html;
                                }
                                else if (html instanceof DocumentFragment) {
-                                       if (html.children[0].nodeName !== 'div' || html.childElementCount > 1) {
-                                               content.appendChild(html);
-                                       }
-                                       else {
-                                               content = html.children[0];
-                                       }
+                                       content.appendChild(html);
                                }
                                
                                content.id = id;
diff --git a/wcfsetup/install/files/lib/action/ImageProxyAction.class.php b/wcfsetup/install/files/lib/action/ImageProxyAction.class.php
new file mode 100644 (file)
index 0000000..ce138aa
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+namespace wcf\action;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\SystemException;
+use wcf\util\FileUtil;
+use wcf\util\HTTPRequest;
+use wcf\util\StringUtil;
+
+/**
+ * Proxies requests for embedded images.
+ * 
+ * @author     Matthias Schmidt
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage action
+ * @category   Community Framework
+ */
+class ImageProxyAction extends AbstractAction {
+       /**
+        * hashed image proxy secret and image url
+        * @var string
+        */
+       public $hash = '';
+       
+       /**
+        * image url
+        * @var string
+        */
+       public $url = '';
+       
+       /**
+        * @see \wcf\action\IAction::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['url'])) $this->url = urldecode(StringUtil::trim($_REQUEST['url']));
+               if (isset($_REQUEST['hash'])) $this->hash = StringUtil::trim($_REQUEST['hash']);
+       }
+       
+       /**
+        * @see \wcf\action\IAction::execute()
+        */
+       public function execute() {
+               parent::execute();
+               
+               $hash = sha1(IMAGE_PROXY_SECRET.$this->url);
+               if ($this->hash != $hash) {
+                       throw new IllegalLinkException();
+               }
+               
+               try {
+                       $request = new HTTPRequest($this->url);
+                       $request->execute();
+                       $reply = $request->getReply();
+                       
+                       $fileExtension = '';
+                       if (($position = mb_strrpos($this->url, '.')) !== false) {
+                               $fileExtension = mb_strtolower(mb_substr($this->url, $position + 1));
+                       }
+                       
+                       // check if requested content is image
+                       if (!isset($reply['headers']['Content-Type']) || !StringUtil::startsWith($reply['headers']['Content-Type'], 'image/')) {
+                               throw new IllegalLinkException();
+                       }
+                       
+                       // save image
+                       $fileLocation = WCF_DIR.'images/proxy/'.substr($hash, 0, 2).'/'.$hash.($fileExtension ? '.'.$fileExtension : '');
+                       $dir = dirname($fileLocation);
+                       if (!@file_exists($dir)) {
+                               FileUtil::makePath($dir, 0777);
+                       }
+                       file_put_contents($fileLocation, $reply['body']);
+                       
+                       // update mtime for correct expiration calculation
+                       @touch($fileLocation);
+                       
+                       $this->executed();
+                       
+                       @header('Content-Type: '.$reply['headers']['Content-Type']);
+                       @readfile($fileLocation);
+                       exit;
+               }
+               catch (SystemException $e) {
+                       throw new IllegalLinkException();
+               }
+       }
+}
index 0b6c05123fd8b2e173b5c4262bcfa1de8fada1bd..44172ebad81391a086709416af6add2023ab6b61 100644 (file)
@@ -1,6 +1,8 @@
 <?php
 namespace wcf\system\bbcode;
 use wcf\util\StringUtil;
+use wcf\system\WCF;
+use wcf\system\request\LinkHandler;
 
 /**
  * Parses the [img] bbcode tag.
@@ -23,6 +25,12 @@ class ImageBBCode extends AbstractBBCode {
                }
                
                if ($parser->getOutputType() == 'text/html') {
+                       $dataSource = '';
+                       if (MODULE_IMAGE_PROXY) {
+                               $dataSource = $src;
+                               $src = $this->getProxyLink($src);
+                       }
+                       
                        $float = '';
                        if (isset($openingTag['attributes'][1])) {
                                $float = $openingTag['attributes'][1];
@@ -40,7 +48,7 @@ class ImageBBCode extends AbstractBBCode {
                                $style .= 'width: ' . $width . 'px;';
                        }
                        
-                       return '<img src="'.$src.'" class="jsResizeImage" alt=""'.($style ? ' style="' . $style . '"' : '').' />';
+                       return '<img src="'.$src.'" class="jsResizeImage" alt=""'.($style ? ' style="' . $style . '"' : '').($dataSource ? ' data-source="'.StringUtil::encodeJS($dataSource).'"' : '').' />';
                }
                else if ($parser->getOutputType() == 'text/simplified-html') {
                        $src = StringUtil::decodeHTML($src);
@@ -52,4 +60,31 @@ class ImageBBCode extends AbstractBBCode {
                        return '';
                }
        }
+       
+       /**
+        * Returns the link to the cached image (or the link to fetch the image
+        * using the image proxy).
+        * 
+        * @param       string          $link
+        * @return      string
+        */
+       protected function getProxyLink($link) {
+               $hash = sha1(IMAGE_PROXY_SECRET.$link);
+               $fileExtension = '';
+               if (($position = mb_strrpos($link, '.')) !== false) {
+                       $fileExtension = mb_strtolower(mb_substr($link, $position + 1));
+               }
+               
+               $path = 'images/proxy/'.substr($hash, 0, 2).'/'.$hash.($fileExtension ? '.'.$fileExtension : '');
+               
+               $fileLocation = WCF_DIR.$path;
+               if (file_exists($fileLocation)) {
+                       return WCF::getPath().$path;
+               }
+               
+               return LinkHandler::getInstance()->getLink('ImageProxy', [
+                       'hash' => $hash,
+                       'url' => urlencode($link)
+               ]);
+       }
 }
index 2778be1ac799aa3a458cacfa5504b211670700ea..f4bfb8718e7ff2c7c885f0ca26a94806dfdb1cf1 100644 (file)
@@ -181,5 +181,14 @@ class DailyCleanUpCronjob extends AbstractCronjob {
                                }
                        }
                }));
+               
+               // clean up proxy images
+               if (MODULE_IMAGE_PROXY) {
+                       DirectoryUtil::getInstance(WCF_DIR.'images/proxy/')->executeCallback(new Callback(function($filename, $object) {
+                               if ($object->isFile() && $object->getMTime() < TIME_NOW - 86400 * IMAGE_PROXY_EXPIRATION) {
+                                       @unlink($filename);
+                               }
+                       }));
+               }
        }
 }