Skip to content

Instantly share code, notes, and snippets.

@searsia
Created January 6, 2018 08:45
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save searsia/e141c4aca4ca1f3bd8a2c04877f4b26e to your computer and use it in GitHub Desktop.
Save searsia/e141c4aca4ca1f3bd8a2c04877f4b26e to your computer and use it in GitHub Desktop.
Simple PHP image proxy
<?php
$PROXY = 'https://yourdomain.top/proxy.php?url=';
# This script forwards the call
# Check for url parameter, and prevent file transfer
if (isset($_GET['url']) and preg_match('#^https?://#', $_GET['url']) === 1) {
$url .= $_GET['url'];
} else {
header('HTTP/1.1 404 Not Found');
exit;
}
# Check if the client already has the requested item
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) or
isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
header('HTTP/1.1 304 Not Modified');
exit;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Searsia/1.0');
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 4);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 12800);
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($DownloadSize, $Downloaded, $UploadSize, $Uploaded) { return ($Downloaded > 1024 * 512) ? 1 : 0; } ); # max 500kb
$out = curl_exec ($ch);
curl_close ($ch);
$file_array = explode("\r\n\r\n", $out, 2);
$header_array = explode("\r\n", $file_array[0]);
foreach($header_array as $header_value) {
$header_pieces = explode(': ', $header_value);
if(count($header_pieces) == 2) {
$headers[$header_pieces[0]] = trim($header_pieces[1]);
}
}
if (array_key_exists('Location', $headers)) {
$newurl = urlencode($headers['Location']);
header("HTTP/1.1 301 Moved Permanently");
header('Location: ' . $PROXY . $newurl);
} else {
if (array_key_exists('Content-Type', $headers)) {
$ct = $headers['Content-Type'];
if (preg_match('#image/png|image/.*icon|image/jpe?g|image/gif#', $ct) !== 1) {
header('HTTP/1.1 404 Not Found');
exit;
}
header('Content-Type: ' . $ct);
}
if (array_key_exists('Content-Length', $headers))
header('Content-Length: ' . $headers['Content-Length']);
if (array_key_exists('Expires', $headers))
header('Expires: ' . $headers['Expires']);
if (array_key_exists('Cache-Control', $headers))
header('Cache-Control: ' . $headers['Cache-Control']);
if (array_key_exists('Last-Modified', $headers))
header('Last-Modified: ' . $headers['Last-Modified']);
echo $file_array[1];
}
?>
@dansleboby
Copy link

dansleboby commented Jun 16, 2021

Improved version

# Check for url parameter, and prevent file transfer
if (isset($_GET['url']) and preg_match('#^https?://#', $_GET['url']) === 1) {
	$url = $_GET['url'];
} else {
	header('HTTP/1.1 404 Not Found');
	exit;
}

# Check if the client already has the requested item
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) or
	isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
	header('HTTP/1.1 304 Not Modified');
	exit;
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 4);
curl_setopt($ch, CURLOPT_BUFFERSIZE, 12800);
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36');
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, function($DownloadSize, $Downloaded, $UploadSize, $Uploaded) { return ($Downloaded > 1024 * 4096) ? 1 : 0; } ); # max 4096kb

$version = curl_version();
if ($version !==FALSE && ($version['features'] & CURL_VERSION_SSL)) { // Curl do support SSL
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
}

$response = curl_exec ($ch);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);



curl_close ($ch);

$header_blocks =  array_filter(preg_split('#\n\s*\n#Uis' , substr($response, 0, $header_size)));
$header_array = explode("\n", array_pop($header_blocks));

$body = substr($response, $header_size);

$headers = [];
foreach($header_array as $header_value) {
	$header_pieces = explode(':', $header_value);
	if(count($header_pieces) == 2) {
		$headers[strtolower($header_pieces[0])] = trim($header_pieces[1]);
	}
}

if (array_key_exists('content-type', $headers)) {
	$ct = $headers['content-type'];
	if (preg_match('#image/png|image/.*icon|image/jpe?g|image/gif|image/webp|image/svg\+xml#', $ct) !== 1) {
		header('HTTP/1.1 404 Not Found');
		exit;
	}
	header('Content-Type: ' . $ct);
} else {
	header('HTTP/1.1 404 Not Found');
	exit;
}

if (array_key_exists('content-length', $headers))
	header('Content-Length: ' . $headers['content-length']);
if (array_key_exists('expires', $headers))
	header('Expires: ' . $headers['expires']);
if (array_key_exists('cache-control', $headers))
	header('Cache-Control: ' . $headers['cache-control']);
if (array_key_exists('last-modified', $headers))
	header('Last-Modified: ' . $headers['last-modified']);
echo $body;
exit;

@searsia
Copy link
Author

searsia commented Jun 16, 2021

Thanks! What is exactly improved in this version?

@egranty
Copy link

egranty commented Jan 29, 2022

Many thanks for script!

I have some suggestions to improve script:

  • $header_array = explode("\n", $header_blocks[array_key_last($header_blocks)]); change to $header_array = explode("\n", array_pop($header_blocks)); since array_key_last() is only supported in PHP 7 and higher.

  • you did strtolower($header_pieces[0]) and then uses if (array_key_exists('Expires', $headers)) in upper case. Its need to be changed to lowercase.

  • add the WEBP and SVG image types: if (preg_match('#image/png|image/.*icon|image/jpe?g|image/gif|image/webp|image/svg\+xml#', strtolower($ct)) !== 1) {, and you don't need strtolower there since all $headers[] already have lowercase.

  • the $header_pieces = explode(': ', $header_value); change ': ' to ':' (without trailing space). It can help to handle some wrong headers. Anyway you do trim() after.

  • add the curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'); to set useragent otherwise something like curl/7.29.0 will be used by default and a lot of hostings treat it as bot (an ban it).

  • its heplful to add CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST options to avoid SSL problems with some hostings:

    $version = curl_version();
    if ($version !==FALSE && ($version['features'] & CURL_VERSION_SSL)) { // Curl do support SSL
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    }

@dansleboby
Copy link

Thanks @egranty I have updated my comment :)

@searsia
Copy link
Author

searsia commented Feb 2, 2022

That's really helpful, thanks both!

@cngodles
Copy link

This works!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment