Completed
Pull Request — release-2.1 (#4166)
by 01
10:19
created

proxy.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This is a lightweight proxy for serving images, generally meant to be used alongside SSL
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
define('SMF', 'proxy');
17
18
/**
19
 * Class ProxyServer
20
 */
21
class ProxyServer
22
{
23
	/** @var bool $enabled Whether or not this is enabled */
24
	protected $enabled;
25
26
	/** @var int $maxSize The maximum size for files to cache */
27
	protected $maxSize;
28
29
	/** @var string $secret A secret code used for hashing */
30
	protected $secret;
31
32
	/** @var string The cache directory */
33
	protected $cache;
34
35
	/**
36
	 * Constructor, loads up the Settings for the proxy
37
	 *
38
	 * @access public
39
	 */
40
	public function __construct()
41
	{
42
		global $image_proxy_enabled, $image_proxy_maxsize, $image_proxy_secret, $cachedir, $sourcedir;
43
44
		require_once(dirname(__FILE__) . '/Settings.php');
45
		require_once($sourcedir . '/Class-CurlFetchWeb.php');
46
		require_once($sourcedir . '/Subs.php');
47
48
49
		// Turn off all error reporting; any extra junk makes for an invalid image.
50
		error_reporting(0);
51
52
		$this->enabled = (bool) $image_proxy_enabled;
53
		$this->maxSize = (int) $image_proxy_maxsize;
54
		$this->secret = (string) $image_proxy_secret;
55
		$this->cache = $cachedir . '/images';
56
	}
57
58
	/**
59
	 * Checks whether the request is valid or not
60
	 *
61
	 * @access public
62
	 * @return bool Whether the request is valid
63
	 */
64
	public function checkRequest()
65
	{
66
		if (!$this->enabled)
67
			return false;
68
69
		// Try to create the image cache directory if it doesn't exist
70
		if (!file_exists($this->cache))
71
			if (!mkdir($this->cache) || !copy(dirname($this->cache) . '/index.php', $this->cache . '/index.php'))
72
				return false;
73
74
		if (empty($_GET['hash']) || empty($_GET['request']))
75
			return false;
76
77
		$hash = $_GET['hash'];
78
		$request = $_GET['request'];
79
80
		if (md5($request . $this->secret) != $hash)
81
			return false;
82
83
		// Attempt to cache the request if it doesn't exist
84
		if (!$this->isCached($request))
85
			return $this->cacheImage($request);
86
87
		return true;
88
	}
89
90
	/**
91
	 * Serves the request
92
	 *
93
	 * @access public
94
	 * @return void
95
	 */
96
	public function serve()
97
	{
98
		$request = $_GET['request'];
99
		$cached_file = $this->getCachedPath($request);
100
		$cached = json_decode(file_get_contents($cached_file), true);
101
102
		// Did we get an error when trying to fetch the image
103
		$response = $this->checkRequest();
104
		if (is_int($response)) {
105
			// Throw a 404
106
			header('HTTP/1.0 404 Not Found');
107
			exit;
108
		}
109
110
		// Is the cache expired?
111
		if (!$cached || time() - $cached['time'] > (5 * 86400))
112
		{
113
			@unlink($cached_file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
114
			if ($this->checkRequest())
115
				$this->serve();
116
			redirectexit($request);
117
		}
118
119
		// Right, image not cached? Simply redirect, then.
120
		if (!$response)
121
		    redirectexit($request);
122
123
		// Make sure we're serving an image
124
		$contentParts = explode('/', !empty($cached['content_type']) ? $cached['content_type'] : '');
125
		if ($contentParts[0] != 'image')
126
			exit;
127
128
		header('Content-type: ' . $cached['content_type']);
129
		header('Content-length: ' . $cached['size']);
130
		echo base64_decode($cached['body']);
131
	}
132
133
	/**
134
	 * Returns the request's hashed filepath
135
	 *
136
	 * @access public
137
	 * @param string $request The request to get the path for
138
	 * @return string The hashed filepath for the specified request
139
	 */
140
	protected function getCachedPath($request)
141
	{
142
		return $this->cache . '/' . sha1($request . $this->secret);
143
	}
144
145
	/**
146
	 * Check whether the image exists in local cache or not
147
	 *
148
	 * @access protected
149
	 * @param string $request The image to check for in the cache
150
	 * @return bool Whether or not the requested image is cached
151
	 */
152
	protected function isCached($request)
153
	{
154
		return file_exists($this->getCachedPath($request));
155
	}
156
157
	/**
158
	 * Attempts to cache the image while validating it
159
	 *
160
	 * @access protected
161
	 * @param string $request The image to cache/validate
162
	 * @return bool|int Whether the specified image was cached or error code when accessing
163
	 */
164
	protected function cacheImage($request)
165
	{
166
		$dest = $this->getCachedPath($request);
167
168
		$curl = new curl_fetch_web_data(array(CURLOPT_BINARYTRANSFER => 1));
169
		$request = $curl->get_url_data($request);
170
		$responseCode = $request->result('code');
171
		$response = $request->result();
172
173
		if (empty($response))
174
			return false;
175
176
		if ($responseCode != 200) {
177
			return $request->result('code');
178
		}
179
180
		$headers = $response['headers'];
181
182
		// Make sure the url is returning an image
183
		$contentParts = explode('/', !empty($headers['content-type']) ? $headers['content-type'] : '');
184
		if ($contentParts[0] != 'image')
185
			return false;
186
187
		// Validate the filesize
188
		if ($response['size'] > ($this->maxSize * 1024))
189
			return false;
190
191
		return file_put_contents($dest, json_encode(array(
192
			'content_type' => $headers['content-type'],
193
			'size' => $response['size'],
194
			'time' => time(),
195
			'body' => base64_encode($response['body']),
196
		)));
197
	}
198
}
199
200
$proxy = new ProxyServer();
201
$proxy->serve();
202
203
?>
0 ignored issues
show
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...