%s) to be viewed.';
$phrases['file_too_large'] = 'The requested file is too large. The maximum permitted filesize is %s MB.';
$phrases['server_busy'] = 'The server is currently busy and unable to process your request. Please try again in a few minutes. We apologise for any inconvenience.';
$phrases['http_error'] = 'The requested resource could not be loaded because the server returned an error:
%s %s (?).';
$phrases['curl_error'] = 'The requested resource could not be loaded. libcurl returned the error:
%s';
$phrases['unknown_error'] = 'The script encountered an unknown error. Error id: %s.';
// If an HTTP error (status code >= 400) is encountered, the script will look here
// for an additional "friendly" explanation of the problem.
$httpErrors = array('404' => 'A 404 error occurs when the requested resource does not exist.');
/*****************************************************************
* Load theme config
******************************************************************/
// Current version - no need to change this!
$themeReplace['version'] = 'v1.0 Final';
// Look for a config.php in the /themes/themeName/ folder
if ( ! defined('MULTIGLYPE') && file_exists($tmp = GLYPE_ROOT . '/themes/' . $CONFIG['theme'] . '/config.php') ) {
// Load it
include $tmp;
}
// NB if running multiple proxies off the same source files - with glype
// manager or any other product - set the MULTIGLYPE constant to stop the
// script automatically loading theme config files.
/*****************************************************************
* Start session
******************************************************************/
// Set name to the configured value - change if running multiple proxies in same
// folder and experiencing session conflicts.
session_name('s');
// Allow caching. We don't want PHP to send any cache-related headers automatically
// (and by default it tries to stop all caching). Using this limiter sends the fewest
// headers, which we override later.
session_cache_limiter('private_no_expire');
// Don't call _start() if session.auto_start = 1
if ( session_id() == '' ) {
session_start();
}
// Copy certain session items (needed after session [may be] closed)
$jsFlags = isset($_SESSION['js_flags']) ? $_SESSION['js_flags'] : false;
/*****************************************************************
* Check IP bans
******************************************************************/
// Only check once per session or if the IP address changes
if ( empty($_SESSION['ip_verified']) || $_SESSION['ip_verified'] != $_SERVER['REMOTE_ADDR'] ) {
// Current IP matches a banned IP? true/false
$banned = false;
// Examine all IP bans
foreach ( $CONFIG['ip_bans'] as $ip ) {
// Is this a range or single?
if ( ($pos = strspn($ip, '0123456789.')) == strlen($ip) ) {
// Just a single IP so check for a match
if ( $_SERVER['REMOTE_ADDR'] == $ip ) {
// Flag the match and break out the loop
$banned = true;
break;
}
// And try next IP
continue;
}
// Must be some form of IP range if still here. Convert our own
// IP address to int and binary.
$ownLong = ip2long($_SERVER['REMOTE_ADDR']);
$ownBin = decbin($ownLong);
// What kind of range?
if ( $ip[$pos] == '/' ) {
// Slash notation - split by slash
list($net, $mask) = explode('/', $ip);
// Fill IP with .0 if shortened form
if ( ( $tmp = substr_count($net, '.') ) < 3 ) {
$net .= str_repeat('.0', 3-$tmp);
}
// Note: there MUST be a better way of doing the rest of this section
// but couldn't understand and/or get anything else to work...
// To do: improve!
// Convert a subnet mask to a prefix length
if ( strpos($mask, '.') ) {
$mask = substr_count(decbin(ip2long($mask)), '1');
}
// Produce a binary string of the network address of prefix length
// and compare to the equivalent for own address
if ( substr(decbin(ip2long($net)), 0, $mask) === substr($ownBin, 0, $mask) ) {
// They match so must be banned
$banned = true;
break;
}
} else {
// No slash so it should just be a pair of dotted quads
$from = ip2long(substr($ip, 0, $pos));
$to = ip2long(substr($ip, $pos+1));
// Did we get valid ranges?
if ( $from && $to ) {
// Are we in the range?
if ( $ownLong >= $from && $ownLong <= $to ) {
// We're banned. Don't bother checking the rest of the bans.
$banned = true;
break;
}
}
}
}
// Is the IP address banned?
if ( $banned ) {
// Send a Forbidden header
header('HTTP/1.1 403 Forbidden', true, 403);
// Print the banned page and exit!
echo loadTemplate('banned.page');
exit;
}
// Still here? Must be OK so save IP in session to prevent rechecking next time
$_SESSION['ip_verified'] = $_SERVER['REMOTE_ADDR'];
}
/*****************************************************************
* Find bitfield to determine options from
******************************************************************/
// First, find the bitfield!
if ( $CONFIG['path_info_urls'] && ! empty($_SERVER['PATH_INFO']) && preg_match('#/b([0-9]{1,5})(?:/f([a-z]{1,10}))?/?$#', $_SERVER['PATH_INFO'], $tmp) ) {
// Found a /bXX/ value at end of path info
$bitfield = $tmp[1];
// (And while we're here, grab the flag too)
$flag = isset($tmp[2]) ? $tmp[2] : '';
} else if ( ! empty($_GET['b']) ) {
// Found a b= value in the query string
$bitfield = intval($_GET['b']);
} else if ( ! empty($_SESSION['bitfield']) ) {
// Use stored session bitfield - mid-browsing but somehow lost the bitfield
$bitfield = $_SESSION['bitfield'];
} else {
// Could not find any bitfield, regenerate (later)
$regenerate = true;
$bitfield = 0;
}
// Get flag from query string while we're here
if ( ! isset($flag) ) {
$flag = isset($_GET['f']) ? $_GET['f'] : '';
}
/*****************************************************************
* Determine options / use defaults
******************************************************************/
$i = 0;
// Loop through the possible options
foreach ( $CONFIG['options'] as $name => $details ) {
// Is the option forced?
if ( ! empty($details['force']) ) {
// Use default
$options[$name] = $details['default'];
// And move onto next option
continue;
}
// Which bit does this option occupy in the bitfield?
$bit = pow(2, $i);
// Use value from bitfield if possible,
if ( ! isset($regenerate) ) {
// Use value from bitfield
$options[$name] = checkBit($bitfield, $bit);
}
// No bitfield available - use defaults and regenerate
else {
// Use default value
$options[$name] = $details['default'];
// Set bit
if ( $details['default'] ) {
setBit($bitfield, $bit);
}
}
// Increase index
++$i;
}
// Save new session value
$_SESSION['bitfield'] = $bitfield;
/*****************************************************************
* Unique URLs
******************************************************************/
if ( $CONFIG['unique_urls'] ) {
// First visit? Ensure we have a unique salt
if ( ! isset($_SESSION['unique_salt']) ) {
// Generate random string
$_SESSION['unique_salt'] = substr(md5(uniqid(true)),rand(0,10),rand(11,20));
}
// Session gets closed before all parsing complete so copy unique to globals
$GLOBALS['unique_salt'] = $_SESSION['unique_salt'];
}
/*****************************************************************
* Custom browser - set up defaults
******************************************************************/
if ( ! isset($_SESSION['custom_browser']) ) {
$_SESSION['custom_browser'] = array(
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'referrer' => 'real',
'tunnel' => '',
'tunnel_port' => '',
'tunnel_type' => '',
);
}
/*****************************************************************
* Global functions
* NB: Some of these (e.g. templating) could make up a whole new class
* that could be easily swapped out to completely change how it works.
* In the interests of speed - but at the cost of convenience - all this
* is stuck together in here as functions.
******************************************************************/
/*****************************************************************
* URL encoding
* There are 3 options that affect URL encodings - the path info setting,
* the unique URLs setting and the users choice of to encode or not.
******************************************************************/
// Takes a normal URL and converts it to a URL that, when requested,
// will load the resource through our proxy
function proxifyURL($url, $flag='', $absolute=true) {
global $CONFIG, $options, $bitfield;
// First, validate the input
if ( empty($url) ) {
return '';
}
// Is it an anchor?
if ( $url[0] == '#' ) {
return $url;
}
// Is it javascript?
if ( stripos($url, 'javascript:') === 0 ) {
return $url;
}
// Is it already proxified?
if ( strpos($url, GLYPE_BROWSE) === 0 ) {
return $url;
}
// URLs occur as values of HTML attributes so decode it
$url = $url;
// Convert to absolute url (if not already)
$url = absoluteURL($url);
// Extract an #anchor
$jumpTo = '';
if ( $tmp = strpos($url, '#') ) {
// Split into jumpTo (append it after proxifying) and $url
$jumpTo = substr($url, $tmp);
$url = substr($url, 0, $tmp);
}
// Add encoding
if ( $options['encodeURL'] ) {
// Part of our encoding is to remove HTTP (saves space and helps avoid detection)
$url = substr($url, 4);
// Add base64
$url = base64_encode($url);
// Are we using unique URLs?
if ( $CONFIG['unique_urls'] ) {
// Add the salt
$url = $GLOBALS['unique_salt'] . $url;
}
}
// Protect chars that have other meaning in URLs
$url = rawurlencode($url);
// If generating absolute URL, add prefix
$prefix = $absolute ? GLYPE_BROWSE : '';
// Return in path info format (only when encoding is on)
if ( $CONFIG['path_info_urls'] && $options['encodeURL'] ) {
return $prefix . '/' . str_replace('%', '_', chunk_split($url, 8, '/')) . 'b' . $bitfield . '/' . ( $flag ? 'f' . $flag : '') . $jumpTo;
}
// Otherwise, return in 'normal' (query string) format
return $prefix . '?u=' . $url . '&b=' . $bitfield . ( $flag ? '&f=' . $flag : '' ) . $jumpTo;
}
// Takes a URL that has been "proxified" by the proxifyURL() function
// and returns it to a normal, direct URL
function deproxifyURL($url, $verifyUnique=false) {
// Check we have URL to deproxify
if ( empty($url) ) {
return $url;
}
// Remove our prefix
$url = str_replace(GLYPE_BROWSE, '', $url);
// Take off flags and bitfield
if ( $url[0] == '/' ) {
// First char is slash, must be path info format
$url = preg_replace('#/b[0-9]{1,5}(?:/f[a-z]{1,10})?/?$#', '', $url);
// Return % and strip /
$url = str_replace('_', '%', $url);
$url = str_replace('/', '', $url);
} else {
// First char not / so must be the standard query string format
if ( preg_match('#\bu=([^&]+)#', $url, $tmp) ) {
$url = $tmp[1];
}
}
// Remove URL encoding (returns special chars such as /)
$url = rawurldecode($url);
// Is it encoded? Presence of :// means unencoded.
if ( ! strpos($url, '://') ) {
// Check for unique salt to remove
if ( isset($GLOBALS['unique_salt']) ) {
// Verify the salt
if ( $verifyUnique && substr($url, 0, strlen($GLOBALS['unique_salt'])) != $GLOBALS['unique_salt'] ) {
error('unique_mismatch', false);
}
// Remove the salt
$url = substr($url, strlen($GLOBALS['unique_salt']));
}
// Remove base64
$url = base64_decode($url);
// Add http back
$url = 'http' . $url;
}
// URLs were originally HTML attributes so *should* have had all
// entities encoded. Decode it.
$url = htmlspecialchars_decode($url);
// Check for successful decoding
if ( strpos($url, '://') === false ) {
return false;
}
// Return decoded URL
return $url;
}
// Take any type of URL (relative, absolute, with base, from root, etc.)
// and return an absolute URL.
function absoluteURL($input) {
global $base, $URL;
// No input?
if ( ! $input ) {
return $input;
}
// Ensure a complete URL
if ( stripos($input, 'http://') !== 0 && stripos($input, 'https://') !== 0 ) {
// No change if .
if ( $input == '.' ) {
$input = '';
}
// Relative from root
if ( $input && $input[0] == '/' ) {
// "//domain.com" is also acceptable so check next char as well
if ( isset($input[1]) && $input[1] == '/' ) {
// Prefix the HTTP and we're done
$input = 'http:' . $input;
} else {
// Relative path from root so add scheme+host as prefix and we're done
$input = $URL['scheme_host'] . $input;
}
} else if ( isset($base) ) {
// Relative path from base href
$input = $base . $input;
} else {
// Relative from document
$input = $URL['scheme_host'] . $URL['path'] . $input;
}
}
// Simplify path
// Strip ./ (refers to current directory)
$input = str_replace('/./', '/', $input);
// Strip double slash //
if ( strlen($input) > 8 && strpos($input, '//', 8) ) {
$input = preg_replace('#(? $level ) {
// If this is the .., remove it and the previous directory
// so in effect we've travelled up a directory
if ( $level == '..' ) {
unset($parts[$previous]);
unset($parts[$key]);
break;
}
// If we remove more than one .. part, keys will no
// longer be consecutively ordered
$previous = $key;
}
}
// Return joined URI
$input = implode('/',$parts);
}
return $input;
}
/*****************************************************************
* Templating System
******************************************************************/
// Load a template
function loadTemplate($file, $vars=array()) {
// Extract passed vars
extract($vars);
// Start output buffer
ob_start();
// Ensure file exists
if ( $path = getTemplatePath($file) ) {
// Load template into buffer
include $path;
// Get buffer into variable
$template = ob_get_contents();
}
// Dispose of output buffer
ob_end_clean();
// Ensure template loaded properly
if ( empty($template) ) {
// Return an error message
return 'ERROR: template failed to load. Please ensure you have correctly installed any custom themes and check you have not removed any files from the default theme.';
}
// Apply theme replacements to template
$template = replaceThemeTags($template);
// Return HTML
return $template;
}
// Take a template name and return absolute path
function getTemplatePath($file) {
global $CONFIG;
// First look in custom theme folder
if ( ! file_exists($return = GLYPE_ROOT . '/themes/' . $CONFIG['theme'] . '/' . $file . '.php') ) {
// Then look in default folder (if different)
if ( $CONFIG['theme'] == 'default' || ! file_exists($return = GLYPE_ROOT . '/themes/default/' . $file . '.php') ) {
// Still not found? Fail.
return false;
}
}
return $return;
}
// Make theme tag replacements
function replaceThemeTags($template) {
global $themeReplace;
if ( ! empty($themeReplace) ) {
foreach ( $themeReplace as $tag => $value ) {
// Make the replacement
$template = str_replace('', $value, $template);
// And for backwards compatability - will be removed at next major release
if ( COMPATABILITY_MODE ) {
$template = str_replace('', $value, $template);
}
}
}
// Return updated
return $template;
}
function render($buffer) { /*This is just to remind users of the terms of the license - removing the credit link is still illegal and enforced by copyright law.*/global$CONFIG;if(defined('LCNSE_KEY'))$CONFIG['license_key']=LCNSE_KEY;if($buffer&&(empty($CONFIG['license_key'])||strlen($CONFIG['license_key'])!=17)&&!preg_match('#]+href\s*=\s*(["\\\']?)http://(?:www\.)?glype\.com#i', $buffer)){$buffer.=base64_decode('PHNwYW4gc3R5bGU9XCJmb250LXNpemU6eC1sYXJnZTtcIj5Qb3dlcmVkIGJ5IDxhIGhyZWY9XCJodHRwOi8vd3d3LmdseXBlLmNvbS9cIj5nbHlwZTwvYT4uIDxicj5UaGlzIGNvcHkgb2YgZ2x5cGUgcHJveHkgaXMgcnVubmluZyB3aXRob3V0IGEgY3JlZGl0IGxpbmsgb3IgbGljZW5zZSB0byByZW1vdmUgdGhlIGNyZWRpdCBsaW5rLjxicj4gUGxlYXNlIGJ1eSBhIGxpY2Vuc2UgZnJvbSA8YSBocmVmPVwiaHR0cDovL3d3dy5nbHlwZS5jb20vbGljZW5zZVwiPmdseXBlLmNvbTwvYT4gb3IgcmV0dXJuIHRoZSBjcmVkaXQgbGluayB0byB0aGUgdGVtcGxhdGUuPC9zcGFuPg==');}header('Content-Length: '.strlen($buffer));return $buffer;}
// Replace content of main.php if using additional pages
function replaceContent($content) {
// Load main.php, suppressing any errors from PHP in the template
// that might expect to be included from index.php.
ob_start();
include getTemplatePath('main');
$output = ob_get_contents();
ob_end_clean();
// Return with theme tags replaced
return replaceThemeTags(preg_replace('#.*#s', $content, $output));
}
/*****************************************************************
* Input encoding / decoding
* PHP converts a number of characters to underscores in incoming
* variable names in an attempt to be compatible with register globals.
* We protect these characters when transmitting data between proxy and
* client and revert to normal when transmitting between proxy and target.
******************************************************************/
// Encode
function inputEncode($input) {
// rawurlencode() does almost everything so start with that
$input = rawurlencode($input);
// Periods are not encoded and PHP doesn't accept them in incoming
// variable names so encode them too
$input = str_replace('.', '%2E', $input);
// [] can be used to create an array so preserve them
$input = str_replace('%5B', '[', $input);
$input = str_replace('%5D', ']', $input);
// And return changed
return $input;
}
// And the complementary decode
function inputDecode($input) {
return rawurldecode($input);
}
// Encode a URL once
function urlencode_once($input) {
return str_replace('%2B', '+', rawurlencode(rawurldecode($input)));
}
/*****************************************************************
* Bitfield operations
******************************************************************/
function checkBit($value, $bit) {
return ($value & $bit) ? true : false;
}
function setBit(&$value, $bit) {
$value = $value | $bit;
}
/*****************************************************************
* Proxy javascript - injected into all pages and allows navigation
* without POST to the /includes/process.php page.
******************************************************************/
function injectionJS() {
global $CONFIG, $URL, $options, $base, $bitfield, $jsFlags;
// Prepare options to make available for our javascript
// Constants
$siteURL = GLYPE_URL;
$scriptName = SCRIPT_NAME;
// URL parts
$targetHost = isset($URL['scheme_host']) ? $URL['scheme_host'] : '';
$targetPath = isset($URL['path']) ? $URL['path'] : '';
// Optional values (may not be set):
$base = isset($base) ? $base : '';
$unique = $CONFIG['unique_urls'] ? $GLOBALS['unique_salt'] : '';
// On proxified page?
$optional = isset($URL) ? ",override:1" : '';
$optional .= is_array($jsFlags) ? '' : ',first:1';
// Path to our javascript file
$jsFile = GLYPE_URL . '/includes/main.js';
return <<ginf={url:'{$siteURL}',script:'{$scriptName}',target:{h:'{$targetHost}',p:'{$targetPath}',b:'{$base}'},enc:{u:'{$unique}',e:'{$options['encodeURL']}',p:'{$CONFIG['path_info_urls']}'},b:'{$bitfield}'{$optional}}
OUT;
}
/*****************************************************************
* Compatability
******************************************************************/
// Requirements are only PHP5 but this function was introduced in PHP 5.1.3
if ( ! function_exists('curl_setopt_array') ) {
// Takes an array of options and sets all at once
function curl_setopt_array($ch, $options) {
foreach ( $options as $option => $value ) {
curl_setopt($ch, $option, $value);
}
}
}
if ( COMPATABILITY_MODE ) {
// Function renamed at 1.0, here for backwards compatability
function render_injectionJS() {
return injectionJS();
}
}
/*****************************************************************
* Miscelleanous
******************************************************************/
// Send no-cache headers.
function sendNoCache() {
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Cache-Control: post-check=0, pre-check=0', false );
header( 'Pragma: no-cache' );
}
// Trim and stripslashes
function clean($value) {
// Static $magic saves us recalling get_magic_quotes_gpc() every time
static $magic;
// Recurse if array
if ( is_array($value) ) {
return array_map($value);
}
// Trim extra spaces
$value = trim($value);
// Check magic quotes status
if ( ! isset($magic) ) {
$magic = get_magic_quotes_gpc();
}
// Stripslashes if magic
if ( $magic && is_string($value) ) {
$value = stripslashes($value);
}
// Return cleaned
return $value;
}
// Redirect
function redirect($to = 'index.php') {
// Did we have an absolute URL?
if ( strpos($to, 'http') !== 0 ) {
// If not, prefix our current URL
$to = GLYPE_URL . '/' . $to;
}
// Send redirect
header('Location: ' . $to);
exit;
}
// Error message
function error($type, $allowReload=false) {
global $phrases, $flag;
// Get extra arguments
$args = func_get_args();
// Remove first argument (we have that as $type)
array_shift($args);
// Check error exists
if ( ! isset($phrases[$type]) ) {
// Force to the "unknown" error message
$args = array($type);
$type = 'unknown_error';
}
// If in frame, don't redirect back to index
if ( isset($flag) && $flag == 'frame' ) {
// Extra arguments to take care of?
if ( $args ) {
// Error text must be generated by calling sprintf - we only have
// the extra args as an array so we have to use call_user_func_array
$errorText = call_user_func_array('sprintf', array_merge((array) $phrases[$type], $args));
} else {
// Error text can be fetched simply from the $phrases array
$errorText = $phrases[$type];
}
die($errorText . ' Return to index.');
}
// Still here? Not frame so serialize to pass in query string
$pass = $args ? '&p=' . base64_encode(serialize($args)) : '';
// Don't cache the error
sendNoCache();
// Do we want to allow refresh?
$return = $allowReload ? '&return=' . rawurlencode(currentURL()) : '';
// And go to error page
redirect('index.php?e=' . $type . $return . $pass);
exit;
}
// Return current URL (absolute URL to proxified page)
function currentURL() {
// Which method are we using
$method = empty($_SERVER['PATH_INFO']) ? 'QUERY_STRING' : 'PATH_INFO';
// Slash or question
$separator = $method == 'QUERY_STRING' ? '?' : '';
// Return full URL
return GLYPE_BROWSE . $separator . ( isset($_SERVER[$method]) ? $_SERVER[$method] : '');
}
// Check tmp directory and create it if necessary
function checkTmpDir($path, $htaccess=false) {
global $CONFIG;
// Does it already exist?
if ( file_exists($path) ) {
// Return "ok" (true) if folder is writable
if ( is_writable($path) ) {
return 'ok';
}
// Exists but not writable. Nothing else we can do.
return false;
} else {
// Does not exist, can we create it? (No if the desired dir is not
// inside the temp dir)
if ( is_writable($CONFIG['tmp_dir']) && realpath($CONFIG['tmp_dir']) == realpath(dirname($path) . '/') && mkdir($path, 0755, true) ) {
// New dir, protect it with .htaccess
if ( $htaccess ) {
file_put_contents($path . '/.htaccess', $htaccess);
}
// Return (true) "made"
return 'made';
}
}
return false;
}