%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;
   
}