Location: PHPKode > projects > TightURL > tighturl-0.1.3.3/tighturl.php
<?php
/**
 * TightURL :: A blind redirection service
 *
 * Copyright (c) 2004-2008, Ron Guerin <hide@address.com>
 * portions Copyright (c) 2002,2003 Free Software Foundation
 *
 * This file implements a blind redirection service named TightURL.
 * TightURL is Free Software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * TightURL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * If you are not able to view the LICENSE, which should
 * always be possible within a valid and working TightURL release,
 * please write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * to get a copy of the GNU General Public License or to report a
 * possible license violation.
 *
 * @package TightURL
 * @author Ron Guerin <hide@address.com>
 * @license http://www.fsf.org/licenses/gpl.html GNU Public License
 * @copyright Copyright &copy; 2004 Ron Guerin
 * @filesource
 * @link http://tighturl.com TightURL
 * @version 0.1
 * 
 */

define("VERSION", "0.1.3.3");
define("REQUIRED_PHP_VERSION", "4.3.0");

// System defaults,  DO NOT EDIT THIS FILE
// Edit tighturl.config.inc.php instead!

global $copyright, $conn, $db, $os, $svcname;

$dbhost = "localhost";
$dbuser = "dbuser";
$dbpass = "dbpass";
$dbname = "tighturl";
$dbtable = "urls";
$FOFMethod=FALSE; //0=Full URL path or mod_rewrite, 1=404-Method compressed URLs
$os="";

// URIBL variables
$uribl = array("multi.surbl.org", "black.uribl.com");
$uriblurl = array("www.surbl.org", "www.uribl.com");

// Bad Behavior variables
$BB2 = true;         
$BBstats = true;
$BBstrict = false;
$BBverbose = true;
$BBLogging = true;
$bb2_settings_defaults = "";

// Require submitted URLs to exist?
$mustexist = true;

// Text strings and style variables
$svcname = "URLSquisher";
$verbtext = "Squish";
$pasttext = "Squished";
$tagline = "Squish long URLs to make short ones";
$headcolor = "#006600";
$tablecolor = "#00CC99";
$copystart = date("Y");
$copyrightholder = "SquishURL Enterprises";

// Reserved URLs
$ReservedURL = array("x", "rest", "xmlrpc", "soap", "xml", "atom", "rss", "blog",
                     "faq", "help", "about", "api", "code", "source", "docs",
                     "cvs", "arch", "url", "admin", "setup", "svn", "project", "abuse", "cgi-sys", "exploited");

// You REALLY don't want to edit below here unless you know what you're doing.

// *************************************************************************

  if (version_compare(phpversion(), REQUIRED_PHP_VERSION)<0) {
    die_HTML($svcname, "Error: TightURL ".VERSION." needs PHP >= ".REQUIRED_PHP_VERSION." (you are using ".phpversion().")");
  }

  if (file_exists("tighturl-install.php")) die_HTML($svcname, "Error: You must remove tighturl-install.php before using $svcname."); 

  $os=strpos(strtolower(PHP_OS), "win")===false?"nix":"win";

  $validurlpattern  = "\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)"
   . "*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])"
   . "\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)"
   . "\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)"
   . "\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])"
   . "|((([0-9A-F]{1,4}(((:[0-9A-F]{1,4}){5}::[0-9A-F]{1,4})|((:[0-9A-F]{1,4}){4}"
   . "::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,1})|((:[0-9A-F]{1,4}){3}::[0-9A-F]{1,4}"
   . "(:[0-9A-F]{1,4}){0,2})|((:[0-9A-F]{1,4}){2}::[0-9A-F]{1,4}(:[0-9A-F]{1,4})"
   . "{0,3})|(:[0-9A-F]{1,4}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,4})|(::[0-9A-F]{1,4}"
   . "(:[0-9A-F]{1,4}){0,5})|(:[0-9A-F]{1,4}){7}))|(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}"
   . "){0,6}))|::)|((([0-9A-F]{1,4}(((:[0-9A-F]{1,4}){3}::([0-9A-F]{1,4}){1})"
   . "|((:[0-9A-F]{1,4}){2}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,1})|((:[0-9A-F]{1,4})"
   . "{1}::[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,2})|(::[0-9A-F]{1,4}(:[0-9A-F]{1,4}"
   . "){0,3})|((:[0-9A-F]{1,4}){0,5})))|([:]{2}[0-9A-F]{1,4}(:[0-9A-F]{1,4}){0,4}))"
   . ":|::)((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{0,2})\.){3}(25[0-5]|2[0-4][0-9]|"
   . "[0-1]?[0-9]{0,2})"
   . "|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org"
   . "|mobi|biz|arpa|info|name|pro|aero|coop|museum"
   . "|[a-zA-Z]{2}))(\:[0-9]+)*(\/.($|[a-zA-Z0-9\.\:\,\?\'\(\)\\\*\+&%\$;|#\=~_\-\s@]*))*\/*";

  $validipv4pattern  = ":\/\/(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\."
   . "(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])"
   . "\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\/*";

  $forbid = "\.(cmd|bat|exe|scr|pif|vbs|js|pif|msi|cdr)";

// ****** !All overridable configuration variables must go above this line! ******

  if (! isset($antiabuse)) $antiabuse = true;
  if (! isset($netchecks)) $netchecks = true;
  if (! isset($mustexist)) $mustexist = true;

  // Status: 0=Ok, 1=Warn, 2=Black, 3=Policy, 4=Complaints

  if (file_exists("tighturl.urlpattern.inc.php")) include("tighturl.urlpattern.inc.php");
  if (file_exists("tighturl.tltpattern.inc.php")) include("tighturl.tltpattern.inc.php");
  if (file_exists("tighturl.redirpattern.inc.php")) include("tighturl.redirpattern.inc.php");
  if (file_exists("tighturl.ptcpattern.inc.php")) include("tighturl.ptcpattern.inc.php");
  if (file_exists("tighturl.blpattern.inc.php")) include("tighturl.blpattern.inc.php");
  if (file_exists("tighturl.config.inc.php")) include("tighturl.config.inc.php");

  // Figure out our copyright string
  $thisyear = date("Y");
  $copyright = $copystart;
  if ($copystart != $thisyear) $copyright .= "-" . $thisyear;
  $uribls = "";
  for ($i=0; $i<count($uribl); $i++) {
    if ($i > 0) $uribls .= ", ";
    $uribls .= "<a href='http://" . $uriblurl[$i] . "'>" . $uribl[$i] . "</a>";
  }

 if (! $FOFMethod) $parm = "?i=";  // We need the parameter tag

 // Figure out correct self
  if (strncmp($_SERVER['PHP_SELF'], $_SERVER['REQUEST_URI'], strlen($_SERVER['PHP_SELF'])) != 0) {
    if (preg_match("|(.*)/.*$|",$_SERVER['PHP_SELF'],$matches)) $self = $matches[1];
    if (! preg_match("|.*/$|", $self)) $self .= "/";
  }
  else {
    $self = $_SERVER['PHP_SELF'];  // We need the script name
    if (! preg_match("|.*/$|", $self)) $self .= "/";
  }
  // Connect to MySQL, open database.
  $conn = @mysql_connect($dbhost, $dbuser, $dbpass) or die_HTML($svcname, "Error: Cannot connect to database.");
  $db = mysql_select_db($dbname, $conn) or die_HTML($svcname, "Error: Cannot select database. ". mysql_error());

  // When in doubt, turn Bad Behavior on, set it to FALSE in the config to turn it off.
  if (! isset($BB2)) $BB2 = true;

  // If user has not turned off Bad Behavior in the config, use BB2 (highly recommended)
  if ($BB2 && file_exists("bad-behavior/bad-behavior-tighturl.php")) require_once("bad-behavior/bad-behavior-tighturl.php");

  // Figure out what kind of request this is and service it.
  
  // This is klugey.  Clean up later.
  // also I think data should be sanitized immediately
  if ((isset($_REQUEST['save']) && $_REQUEST['save'] == 'y')
   && (isset($_REQUEST['url']) && ! empty($_REQUEST['url']) && trim($_REQUEST['url']) != ""
   && (preg_match("/^.*url=(.*)$/", $_SERVER['QUERY_STRING'], $matches) != 0))) {
    $url = trim($matches[1]);
    if (preg_match("/^(.*)&tighturlaction.*$/", $matches[1], $matches)) $url = $matches[1];
    save_URL(urldecode($url));
  }
  elseif (isset($_REQUEST['i']) && !empty($_REQUEST['i'])) {
    lookup_ID($_REQUEST['i']);
  }
  elseif ($FOFMethod && preg_match("/^\/+([a-zA-Z0-9]+)\/*(.*)\/*$/", $_SERVER['REQUEST_URI'], $matches)) {
    lookup_ID($matches[1]);
  }
  elseif ($FOFMethod && $_SERVER['REQUEST_URI'] != "/") {
    display_HTML("", "", "Error: Couldn't find a valid " . $svcname . " URI.");
  }
  else {
    display_HTML("", "main");
  }
  exit;

// *************************************************************************


/**
 * sanitize a string for SQL input
 */
function sanitize_sql_string($string) {
  return(mysql_real_escape_string($string));
}

/**
 * Counts the number of times a substring is contained in a given string.
 */
function countSubstrs($haystack, $needle) {
  return (($p = strpos($haystack, $needle)) === false) ? 0 : (1 + countSubstrs(substr($haystack, $p+1), $needle));
}

/**
 * Checks to see if a given URI is on a URI blacklist.
 * Currently this means SURBL (http://www.surbl.org) and URIBL (http://www.uribl.com)
 * 
 * Returns TRUE if the domain is listed on any configured URIBLs, returns FALSE if
 * anything goes wrong or the anti-abuse system is turned off.
 * 
 * A companion URI extractor must be written for the below issues
 * Must be changed to do full resolution of redirections on URI, simulating a browser
 * Must be changed to do IPv6 lookups
 * Must be changed to check multiple URIs (maybe a wrapper instead)
 * Must be changed to optionally check HTML entity encoded versions of URIs
 * Must be changed to handle URIBL's inclusion of some third-level domains.
 *
 */
function URI_on_URIBL($uri) {

// This code does not yet properly implement a correct and efficient querying
// of URI BL data.

  global $uribl, $uribluri, $validschemes, $validurlpattern, $validipv4pattern,
         $antiabuse, $netchecks, $tltlds;

  // Everything gets a pass if antiabuse or network tests are off.
  if ((! $antiabuse) || (! $netchecks)) return(false);
        
  $uribls = "";

  if ($uri) {
    // Test for IPv4 address, reverse the quads if found
    if (preg_match("/^".$validschemes.$validipv4pattern."/", $uri, $matches)) {
      $domain=$matches[5] . "." . $matches[4] . "." . $matches[3] . "." . $matches[2];
    }
    else {
      // strip out second-level domain name, *unless* on exception list,
      // in which case, strip out third level also and test that instead.
      // FIX: when testing uribl.com lists, also test additional level.  First hit wins.

      preg_match("/^".$validschemes.$validurlpattern."$/", $uri, $matches);
      $domain = $matches[4];
      if (preg_match("/".$tltlds."$/", $domain, $matches)) {$levels = 2;} else {$levels = 1;}

      // klugey stripping routine to reduce domain to base domain name
      // expect regex wojuld be better

      $ss = countSubstrs($domain, ".");
      while ($ss > $levels) {
        $chop = strpos($domain, ".");
        $domain = substr($domain, $chop + 1);
        $ss = countSubstrs($domain, ".");
      }
    }

    // Query URI blacklists to see if domain/IP appears as target in known spam
    // or something involved in a malware/phishing attack.
    for ($i=0; $i<count($uribl); $i++) {
      $fqdn = $domain . "." . $uribl[$i];
      $recexists = gethostbyname($fqdn); // ghbn weirdly returns the name on failure
      if (($recexists != $fqdn) && preg_match("/^127\.", $recexists)) {
        if ($i > 0) $uribls .= ", ";                
        $uribls .= $uribl[$i];      
      }
    }
    return ($uribls); // change to return an array of indexes into the URIBL array
  }
}

/**
 * Checks to see if a given URL is a Reserved URL.
 * 
 * Returns TRUE if the ID is listed as a Reserved URL.
 */
function on_Reserve($decimal) {
  global $ReservedURL;

  $res=FALSE;

  if ($decimal) {
    $sexatrigesimal = base_convert($decimal, 10, 36);
    for ($i=0; $i<count($ReservedURL); $i++) {
      if ($sexatrigesimal == strtolower($ReservedURL[$i])) return TRUE;
    }
    return FALSE;
  }
} 

/**
 * Save the given URL in the database if unique and return the ID or return an existing ID for given URL.
 * The ID returned will be a sexatrigesimal (Base-36) number.
 *
 * Saves the URL in the database, converts the decimal ID value returned by the database to
 * a sexatrigesimal value, and displays the generated TightURL.
 */
function save_URL($url) {
  global $dbtable, $svcname, $FOFMethod, $validschemes, $validurlpattern, $pasttext, $self, $redir, $bl,
         $antiabuse, $netchecks, $ptc;
 
  $selfref = "|^(http://" . $_SERVER['HTTP_HOST'] . $self . ")|i";
  $aliasref = "|^(http://www." . $_SERVER['HTTP_HOST'] . $self . ")|i";
  $remote = $_SERVER['REMOTE_ADDR'];

  if (preg_match($selfref, $url) || preg_match($aliasref, $url)) {
    display_HTML("", "", "Error: A " . $svcname . " URL cannot point to another URL within " . $_SERVER['HTTP_HOST'] . $self . " .", $url, "", $url);
  }
  elseif (! preg_match("/^".$validschemes.$validurlpattern."$/", $url)) {
    display_HTML("", "", "Error: That URL (".htmlspecialchars(strip_tags($url)).") is not valid.", $url, "", $url);
  }
  elseif (($forbid != "") && (preg_match("!.*".$forbid."$!i", $url))) {
    display_HTML("", "", "Error: Executable URIs are not accepted here due to phishing/malware abuse.", $url, "", $url);
  }
// This code was never meant to be in a release. oops
//  elseif (preg_match("!.*\.gif$!", $url)) { // replace this with image comparison
//    display_HTML("", "", "Error: URL rejected for service abuse.", $url, "", $url);
//  }
  elseif (isset($mustexist) && $mustexist && isset($netchecks) && $netchecks) {
    if (! Resolve_URL($decodedurl)) display_HTML("", "", "Error: Submitted URL does not exist on the public Internet.", $url, "", $url);
  }
  elseif (isset($bl) && preg_match("/.*".$bl.".*/i", $url)) { // delete this crap
//    echo "url: $url  bl: $bl\n";
    display_HTML("", "", "Error: URL rejected for violating our terms of use.", $url, "", $url);
  }
  elseif (preg_match("/.*".$redir.".*/i", $url)) {
    display_HTML("", "", "Error: ".$svcname." is not a URL obfuscation service, and does not accept redirection links.", $url, "", $url);
  }
  elseif (isset($ptc) && preg_match("/.*".$ptc.".*/i", $url)) {
    display_HTML("", "", "Error: ".$svcname." does not accept PTC (Pay To Click) links due to spamming abuse.", $url, "", $url);
  }
  else {
    $safeurl = sanitize_sql_string($url);
    $result = mysql_query("SELECT MAX(id) FROM $dbtable") or display_HTML("", "", "Error: $svcname system error.", $url, "", $url);
    $lastid = mysql_result($result, 0) + 1;
    $guesssexatrigesimal = base_convert($lastid, 10, 36);
    $guessurl = "http://" . $_SERVER['HTTP_HOST'] . $self;
    if (! $FOFMethod) $guessurl .= "?i=";  // We need the parameter tag
    $guessurl .= $guesssexatrigesimal; // Append the Base-36 ID to the URL
    if ( strlen($guessurl) >= strlen($url) ) {
      display_HTML("", "", "Fail: That URL cannot be shortened by $svcname. Sorry!", $url, "", $url);
    }
    else {
      if ($antiabuse && $netchecks) {$lists = URI_on_URIBL($url);} else {$lists = false;}
      if (! $lists) {
        $rows=0; $srows=0; $testurl=$safeurl;
        if (preg_match("/\/$/", $testurl)) $testurl = rtrim($testurl,"/");
        $req = "SELECT * FROM $dbtable WHERE url = '$testurl/';";
        $res = mysql_query($req);
        $srows = @mysql_num_rows($res) or $srows = 0;
        if ($srows == 0) {
          $req = "SELECT * FROM $dbtable WHERE url = '$testurl';";
          $res = mysql_query($req);
          $rows = @mysql_num_rows($res) or $rows = 0;
        }
        if ($rows == 0 && $srows == 0) {
          do {
            $req ="INSERT INTO $dbtable (id, url, adddate, addip) ";
            $req .= "VALUES ('', '$safeurl', NOW(), '$remote');";
            if (mysql_query($req)) {
              $decimal = mysql_insert_id();
            }
            else {
              die_HTML($svcname, "Error: Database failure.");
            }
            $reserved_id = on_Reserve($decimal);
            if ($reserved_id) {
              // Delete this record so it doesn't override the reserved ID. (?)
              $req = "DELETE FROM $dbtable WHERE id = '$decimal';";
              $res = mysql_query($req) or die_HTML($svcname, "Error: Database failure.");
            }
          } while ($reserved_id);
        }
        else {
          // Return existing ID for this duplicate request
          $decimal = mysql_result($res, 0, "id");
        }
        $sexatrigesimal = base_convert($decimal, 10, 36);
        $address = "http://" . $_SERVER['HTTP_HOST'] . $self;
        if (! $FOFMethod) $address .= "?i=";  // We need the parameter tag
        $address .= $sexatrigesimal; // Append the Base-36 ID to the URL
        display_HTML("", "save", "", $url, $address);
      }
      else {
        display_HTML("HTTP/1.0 403 Forbidden", "", "Error: Submitted URL (" . $url . ") is listed in " . $lists . ". You may not create a " . $svcname . " link for it.");
      }
    }
  }
}

/**
 * Looks up given ID in the database and redirects, displays template, or
 * displays error page. Expects the ID to be a sexatrigesimal (Base-36) number,
 * which is the format used by TightURLs.
 *
 * We convert the ID to decimal before looking it up in the database, as the
 * ID field is a MySQL autoincrement decimal value.
 */
function lookup_ID($sexatrigesimal) {
  global $dbtable, $svcname;
  
  // First, convert unsafe user input sexatrigesimal to decimal, which will be safe.
  $decimal = base_convert ($sexatrigesimal, 36, 10);

  $req = "SELECT * FROM $dbtable WHERE id = '$decimal';";
  $res = mysql_query($req) or die_HTML($svcname, "Error: Query failed");
 
  $rows = mysql_num_rows($res);
  if (($rows != 0) && (mysql_result($res, 0, "url") != "")) {
    // Change this logic to display a templated page instead?
    switch (mysql_result($res, 0, "status")) {
    case 5:
      display_HTML("HTTP/1.0 403 Forbidden", "complaints");
      return;
      break;
    case 4:
      display_HTML("HTTP/1.0 403 Forbidden", "policy");
      return;
      break;
    case 3:
      display_HTML("HTTP/1.0 403 Forbidden", "blacklist");
      return;
      break;
    default:
      $url = stripslashes(mysql_result($res, 0, "url"));
      break;
    }
    $req ="update $dbtable set lasthit=NOW(), hits=hits+1 where id='$decimal';";
    $res = mysql_query($req);
    header("HTTP/1.0 301 Moved Permanently");
    header("Location: $url");
  }
  elseif (! on_Reserve($decimal)) {  // Not found, Not on reserve
    display_HTML("HTTP/1.0 404 Not Found", "", "Error: That " . $svcname . " ID is not in our database.");
  }
  else {  // It's a(n implied) Reserved URL
    // Is this a template or an API?
    $sexatrigesimal = strtolower($sexatrigesimal);
    switch ($sexatrigesimal) {
    case "rest":
      api_REST();
      break;
    case "xmlrpc":
      api_XMLRPC();
      break;
    case "soap":
      api_SOAP();
      break;
    default:
      display_HTML("", $sexatrigesimal);
    }
  }
}

function api_REST() {
  die_HTML($svcname, "Error: REST API not implemented yet.", "HTTP/1.0 501 Not Implemented");
}

function api_XMLRPC() {
  die_HTML($svcname, "Error: XML-RPC API not implemented yet.", "HTTP/1.0 501 Not Implemented");
}

function api_SOAP() {
  die_HTML($svcname, "Error: SOAP API not implemented yet.", "HTTP/1.0 501 Not Implemented");
}

/**
 * Display HTML page using template and template variables.
 *
 * Reads in the main system template file (tighturl.tmpl) into $html .
 *
 * $code
 *   HTTP 1.0 status code and message.
 *
 * $template
 *   Checks for the existence of a subtemplate named tighturl.$template.tmpl
 *   and replaces template variable $HTML in the main template tighturl.tmpl
 *   with the contents of tighturl.$template.tmpl if any.
 *
 *   Then any remaining $HTML from the only or inner template is replaced by $content,
 *   along with $url, $tighturl, and $input.  A variety of other replacements are
 *   made using various global variables.
 *
 * $content
 *   HTML content to be replace template variable $HTML
 *
 * $url
 *   URL submitted to TightURL
 * 
 * $tighturl
 *   TightURL generated for $url
 * 
 * $input
 *   When submitted URL does not validate it is passed back as $input
 * 
 * Template variables are words in all capital letters that start with a
 * $ symbol, such as $TEMPLATEVARIABLE.  TightURL now supports at least
 * 20 template variables.  At runtime, these template variables are replaced
 * by program variables.
 * - $HTML : HTML passed into the function as $input by the program or an inner template 
 * - $PARM : Parameter tag when not using 404-Method
 * - $URL : URL submitted to TightURL
 * - $URLLEN : Length of the submitted URL
 * - $TIGHTURL : TightURL generated for the submitted URL
 * - $TIGHTURLLEN : Length of generated TightURL
 * - $DIFF : Difference in length between submitted and TightURLs
 * - $INPUT : Bad input URL being passed back to output form
 * - $SVCNAME : Name of the TightURL service
 * - $HEADCOLOR : Color of the H1 Header tag
 * - $TABLECOLOR : Color of the table containing URL input field
 * - $TAGLINE : Tagline of the TightURL service
 * - $CPASTTEXT : Capitalized past-tense word for tightening URLs
 * - $PASTTEXT : Non-Capitalized past-tense word for tightening URLs
 * - $CVERBTEXT : Capitalized action word for tightening URLs
 * - $VERBTEXT : Non-Capitalized action word for tightening URLs
 * - $COPYRIGHT : Copyright duration string generated from $copystart global variable,
 *                will be current 4-digit year if $copystart not defined.
 * - $COPYRIGHTHOLDER : Name of copyright holder
 * - $URLBLS : HTML string of URIBLs TightURL is checking
 * - $HOST : Hostname TightURL is running on
 * - $SELF : Name TightURL is invoked as
 */
function display_HTML ($code, $template, $content="", $url="", $tighturl="", $input="") {
  global $svcname, $verbtext, $pasttext, $tagline, $uribls, $parm,
       $headcolor, $tablecolor, $copyright, $copyrightholder, $self, $BB2;
  
//  $url = htmlspecialchars($url);
  if ($code="") $code = "HTTP/1.0 200 OK";
  if (preg_match("/\/$/", $template)) $template = rtrim($template,"/");
  if (file_exists("tighturl.tmpl")) {
    $html = file_get_contents("tighturl.tmpl");
    if (($template != "") && file_exists("tighturl." . $template . ".tmpl")) {
      $template = file_get_contents("tighturl." . $template . ".tmpl");
      $html = preg_replace("/\\\$HTML/", $template, $html);
    }
    elseif ($template != "") {
      die_HTML($svcname, "Error: Template file tighturl." . $template . ".tmpl cannot be found.");
    }
    if (substr($content, 0, 6) == "Error:") {
      $content = preg_replace("/Error:/", "<big><font color='red'>Error:", $content)."</font></big>";
    }
    if ($content) $content .= "<br />\n";
    // Always replace longer similar tokens before shorter ones. Things won't work the
    // way you expect if you replace $URL first, and then replace $URLMORELETTERS.
    $html = preg_replace("/\\\$HTML/", $content, $html);
    $html = preg_replace("/\\\$PARM/", $parm, $html);
    $html = preg_replace("/\\\$URLLEN/", strlen($url), $html);
    $html = preg_replace("/\\\$URL/", htmlspecialchars(strip_tags($url), ENT_QUOTES), $html);
    $html = preg_replace("/\\\$INPUT/", $input, $html);
    $html = preg_replace("/\\\$TIGHTURLVER/", VERSION, $html);
    $html = preg_replace("/\\\$TIGHTURLLEN/", strlen($tighturl), $html);
    $html = preg_replace("/\\\$TIGHTURL/", $tighturl, $html);
    $html = preg_replace("/\\\$DIFF/", strlen($url)-strlen($tighturl), $html);
    $html = preg_replace("/\\\$SVCNAME/", $svcname, $html);
    $html = preg_replace("/\\\$HEADCOLOR/", $headcolor, $html);
    $html = preg_replace("/\\\$TABLECOLOR/", $tablecolor, $html);
    $html = preg_replace("/\\\$TAGLINE/", $tagline, $html);
    $html = preg_replace("/\\\$CPASTTEXT/", $pasttext, $html);
    $html = preg_replace("/\\\$PASTTEXT/", strtolower($pasttext), $html);
    $html = preg_replace("/\\\$VERBTEXT/", strtolower($verbtext), $html);
    $html = preg_replace("/\\\$CVERBTEXT/", $verbtext, $html);
    $html = preg_replace("/\\\$COPYRIGHTHOLDER/", $copyrightholder, $html);
    $html = preg_replace("/\\\$COPYRIGHT/", $copyright, $html);
    $html = preg_replace("/\\\$URIBLS/", $uribls, $html);
    $html = preg_replace("/\\\$HOST/", $_SERVER['HTTP_HOST'], $html);
    $html = preg_replace("/\\\$SELF/", $self, $html);
    $html = preg_replace("/\\\$__/", "$", $html); // Template Variables shown as text instead of substituted
    if (preg_match("|<title>(.*)</title>|is", $html, $matches))
      $html = preg_replace("|<title>(.*)</title>|is", "<title>" . strip_tags($matches[1]) . "</title>", $html);
    if ($BB2) {
      $html = preg_replace("/\\\$BBSTATS/",  bb2_insert_stats(), $html);
      $bb2code = bb2_insert_head();
      if (preg_match("|<head>(.*)</head>|is", $html, $matches))
        $html = preg_replace("|<head>(.*)</head>|is", "<head>\n" . $bb2code . $matches[1] . "</head>", $html);
    }
    else {
      $html = preg_replace("/\\\$BBSTATS/", "", $html);
    }
    header($code);
    echo $html;
  }
  else {
    die_HTML($svcname, "Error: <big><font color='red'>Error: TightURL Redirection service (" . $svcname . ") site template not found.</font></big>");
  }
}

/**
 * Die in an HTML-friendly way, without the benefit of a template.
 * Use display_HTML to "die" using the TightURL site template.
 */
function die_HTML($svcname, $errmsg, $code="HTTP/1.0 500 Internal Server Error") {

  header($code);
  echo "<html>\n  <head>\n    <title>" . $svcname . "</title>\n  </head>\n  <body>\n";
  echo "   " . $errmsg . "<br />\n";
  echo "  </body>\n</html>";
  die;
}

/**
 * Verifies the existence and accessibility of a resource in a given URL.
 * 
 * Returns FALSE if the resource does not exist or cannot be accessed using
 * supplied authentication information, else returns the resolved and verified
 * URL.  Given URL is returned as resolved to itself if $netchecks are off.
 *
 * Will recurse through redirection chains up to 12 times by default.  This
 * value is preferably selectable by the user in a configuration screen
 * somewhere, but probably should not be lower than 12, as attempts are made
 * to detect HTML and JavaScript redirects in addition to HTTP redirects, and
 * a dozen redirects to find the end is quite possible.
 *
 * Unfortunately Google's GFE server erroneously returns 404 errors when
 * they should be returning something like a 405, making it impossible to
 * use HTTP HEAD to verify the existence of resources front-ended by GFE.
 * Additionally Amazon throws a 405 attempting to HEAD some of their resources
 * so this function does not attempt to use HEAD at all.
 *
 * BUG: Presently only does HTTP
 *
 */
//function Resolve_URL ($url, $chain=12, &$resolvedchain=array("")) {
function Resolve_URL ($url, $chain=12, &$resolvedchain) {
//  $resolvedchain = array($url);

  if (! isset($resolvedchain)) $resolvedchain=array("");

  global $netchecks;

  if (! $netchecks) {
    // If network checks off, accept submitted URL as resolved.
    return($url);
  }

  $parsed = parse_url($url);

  $pre  = isset($parsed['scheme']) ? $parsed['scheme'].':'.((strtolower($parsed['scheme']) == 'mailto') ? '' : '//') : '';
  $pre .= isset($parsed['user']) ? $parsed['user'].(isset($parsed['pass']) ? ':'.$parsed['pass'] : '').'@' : '';
  $pre .= isset($parsed['host']) ? $parsed['host'] : '';
  $pre .= isset($parsed['port']) ? ':'.$parsed['port'] : '';
  if(isset($parsed['path']))
    $post = (substr($parsed['path'], 0, 1) == '/') ? $parsed['path'] : ('/'.$parsed['path']);
  $post .= isset($parsed['query']) ? '?'.$parsed['query'] : '';
  $post .= isset($parsed['fragment']) ? '#'.$parsed['fragment'] : '';

  $resolved = false;

  // Change this to support all protocols TightURL supports, not just HTTP
  if (! isset($parsed['port']) || $parsed['port'] == 0) $parsed['port'] = 80;;
  if($connection = @fsockopen ($parsed['host'], $parsed['port'], $errno, $errstr, 5)) {
    stream_set_timeout($connection, 5);
    // HTTP send Connection: Close so we don't have to wait
    // Google's GFE handling of HEAD is broken, and Amazon returns 405 on HEAD so had to use GET
    fwrite($connection, "GET ".$post." HTTP/1.0\r\nHost: ".$parsed['host']."\r\nConnection: Close\r\n\r\n");
    while (!feof($connection)) {
      $line_read=fgets($connection);
      if ($line_read == "") break; //blank line is header delimiter, if you see it you're done here
                                   //Fix: change this and start parsing the body for HTML-based redirections.

      if (preg_match("/HTTP\/\S* +(\S*) /", $line_read, $matches)) { // Look for certain HTTP status codes
        switch ($matches[1]) {
        case 200: // Ok, we have a final destination (as far as HTTP is concerned)
        case 201: // Created, we have a final destination
        case 202: // Accepted, we have a final destination
        case 203: // Non-authoritative reply, we have a final destination
        case 204: // No content, we have a final destination
        case 205: // Reset content, we have a final destination
        case 206: // Partial content, we have a final destination
        case 207: // Multi-status, we have a final destination
        case 304: // Not Modified (this is ok)
        case 401: // Authorization required (this is ok)
        case 402: // Payment required (this is ok)
        case 403: // Forbidden (but also ok)
        case 405: // Method not allowed (but also ok)
        case 406: // Not acceptable (acceptable here unless someone tells us otherwise)
        case 409: // Conflict (acceptable unless someone tells us otherwise)
        case 421: // Too many connections (fail ok)
        case 426: // Use TLS (fail ok)
        case 500: // Internal server error (fail ok)
        case 502: // Bad gateway (fail ok)
        case 503: // Service unavailable (fail ok)
        case 504: // Gateway timeout (fail ok)
        case 505: // HTTP version not supported (fail ok)
        case 509: // Bandwidth exceeded pseudo code (fail ok)
          $resolved = $url;
          $resolvedchain[] = $url;
          break 2;
        case 300:
        case 301:
        case 302:
        case 307:
          break;
        case 404: // Not found
        case 408: // Request timeout (this URL will never work again)
        case 410: // Gone (and not coming back)
          break 2;
        default:
          $resolved = $url;
          $resolvedchain[] = $url;
          break 2;          
        }
      }

      // If this is a redirect (300, 301, 302, 307), follow it if the chain isn't too long
      if (preg_match("/Location: (.*)\r\n/", $line_read, $matches)) {
        fclose($connection);
        $connection = false;
        $resolvedchain[] = $url;
        if ($chain > 0 ) $resolved = Resolve_URL($matches[1], $chain - 1, $resolvedchain);
        break;
      }

    }
    // Parse body here?
    if ($connection) fclose($connection);
  }
  return($resolved);
}

?>
Return current item: TightURL