summaryrefslogtreecommitdiffstats
path: root/function
diff options
context:
space:
mode:
Diffstat (limited to 'function')
-rw-r--r--function/.htaccess1
-rw-r--r--function/JWT.php326
-rw-r--r--function/ProfileClass.php707
-rw-r--r--function/RFC822.php872
-rw-r--r--function/mimePart.php333
-rw-r--r--function/smtp.php361
-rw-r--r--function/thumb.php1244
7 files changed, 3844 insertions, 0 deletions
diff --git a/function/.htaccess b/function/.htaccess
new file mode 100644
index 0000000..b0e37d7
--- /dev/null
+++ b/function/.htaccess
@@ -0,0 +1 @@
+RewriteEngine Off \ No newline at end of file
diff --git a/function/JWT.php b/function/JWT.php
new file mode 100644
index 0000000..f194893
--- /dev/null
+++ b/function/JWT.php
@@ -0,0 +1,326 @@
+<?php
+
+/**
+ * JSON Web Token implementation, based on this spec:
+ * http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06
+ *
+ * PHP version 5
+ *
+ * @category Authentication
+ * @package Authentication_JWT
+ * @author Neuman Vong <neuman@twilio.com>
+ * @author Anant Narayanan <anant@php.net>
+ * @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
+ * @link https://github.com/firebase/php-jwt
+ */
+class JWT
+{
+ public static $supported_algs = array(
+ 'HS256' => array('hash_hmac', 'SHA256'),
+ 'HS512' => array('hash_hmac', 'SHA512'),
+ 'HS384' => array('hash_hmac', 'SHA384'),
+ 'RS256' => array('openssl', 'SHA256'),
+ );
+
+ /**
+ * Decodes a JWT string into a PHP object.
+ *
+ * @param string $jwt The JWT
+ * @param string|Array|null $key The secret key, or map of keys
+ * @param Array $allowed_algs List of supported verification algorithms
+ *
+ * @return object The JWT's payload as a PHP object
+ *
+ * @throws DomainException Algorithm was not provided
+ * @throws UnexpectedValueException Provided JWT was invalid
+ * @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
+ * @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
+ * @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
+ * @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
+ *
+ * @uses jsonDecode
+ * @uses urlsafeB64Decode
+ */
+ public static function decode($jwt, $key = null, $allowed_algs = array())
+ {
+ $tks = explode('.', $jwt);
+ if (count($tks) != 3) {
+ throw new UnexpectedValueException('Wrong number of segments');
+ }
+ list($headb64, $bodyb64, $cryptob64) = $tks;
+ if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) {
+ throw new UnexpectedValueException('Invalid header encoding');
+ }
+ if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) {
+ throw new UnexpectedValueException('Invalid claims encoding');
+ }
+ $sig = JWT::urlsafeB64Decode($cryptob64);
+ if (isset($key)) {
+ if (empty($header->alg)) {
+ throw new DomainException('Empty algorithm');
+ }
+ if (empty(self::$supported_algs[$header->alg])) {
+ throw new DomainException('Algorithm not supported');
+ }
+ if (!is_array($allowed_algs) || !in_array($header->alg, $allowed_algs)) {
+ throw new DomainException('Algorithm not allowed');
+ }
+ if (is_array($key)) {
+ if (isset($header->kid)) {
+ $key = $key[$header->kid];
+ } else {
+ throw new DomainException('"kid" empty, unable to lookup correct key');
+ }
+ }
+
+ // Check the signature
+ if (!JWT::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
+ throw new SignatureInvalidException('Signature verification failed');
+ }
+
+ // Check if the nbf if it is defined. This is the time that the
+ // token can actually be used. If it's not yet that time, abort.
+ if (isset($payload->nbf) && $payload->nbf > time()) {
+ throw new BeforeValidException(
+ 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
+ );
+ }
+
+ // Check that this token has been created before 'now'. This prevents
+ // using tokens that have been created for later use (and haven't
+ // correctly used the nbf claim).
+ if (isset($payload->iat) && $payload->iat > time()) {
+ throw new BeforeValidException(
+ 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
+ );
+ }
+
+ // Check if this token has expired.
+ if (isset($payload->exp) && time() >= $payload->exp) {
+ throw new ExpiredException('Expired token');
+ }
+ }
+
+ return $payload;
+ }
+
+ /**
+ * Converts and signs a PHP object or array into a JWT string.
+ *
+ * @param object|array $payload PHP object or array
+ * @param string $key The secret key
+ * @param string $alg The signing algorithm. Supported
+ * algorithms are 'HS256', 'HS384' and 'HS512'
+ *
+ * @return string A signed JWT
+ * @uses jsonEncode
+ * @uses urlsafeB64Encode
+ */
+ public static function encode($payload, $key, $alg = 'HS256', $keyId = null)
+ {
+ $header = array('typ' => 'JWT', 'alg' => $alg);
+ if ($keyId !== null) {
+ $header['kid'] = $keyId;
+ }
+ $segments = array();
+ $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($header));
+ $segments[] = JWT::urlsafeB64Encode(JWT::jsonEncode($payload));
+ $signing_input = implode('.', $segments);
+
+ $signature = JWT::sign($signing_input, $key, $alg);
+ $segments[] = JWT::urlsafeB64Encode($signature);
+
+ return implode('.', $segments);
+ }
+
+ /**
+ * Sign a string with a given key and algorithm.
+ *
+ * @param string $msg The message to sign
+ * @param string|resource $key The secret key
+ * @param string $alg The signing algorithm. Supported algorithms
+ * are 'HS256', 'HS384', 'HS512' and 'RS256'
+ *
+ * @return string An encrypted message
+ * @throws DomainException Unsupported algorithm was specified
+ */
+ public static function sign($msg, $key, $alg = 'HS256')
+ {
+ if (empty(self::$supported_algs[$alg])) {
+ throw new DomainException('Algorithm not supported');
+ }
+ list($function, $algorithm) = self::$supported_algs[$alg];
+ switch($function) {
+ case 'hash_hmac':
+ return hash_hmac($algorithm, $msg, $key, true);
+ case 'openssl':
+ $signature = '';
+ $success = openssl_sign($msg, $signature, $key, $algorithm);
+ if (!$success) {
+ throw new DomainException("OpenSSL unable to sign data");
+ } else {
+ return $signature;
+ }
+ }
+ }
+
+ /**
+ * Verify a signature with the mesage, key and method. Not all methods
+ * are symmetric, so we must have a separate verify and sign method.
+ * @param string $msg the original message
+ * @param string $signature
+ * @param string|resource $key for HS*, a string key works. for RS*, must be a resource of an openssl public key
+ * @param string $alg
+ * @return bool
+ * @throws DomainException Invalid Algorithm or OpenSSL failure
+ */
+ private static function verify($msg, $signature, $key, $alg)
+ {
+ if (empty(self::$supported_algs[$alg])) {
+ throw new DomainException('Algorithm not supported');
+ }
+
+ list($function, $algorithm) = self::$supported_algs[$alg];
+ switch($function) {
+ case 'openssl':
+ $success = openssl_verify($msg, $signature, $key, $algorithm);
+ if (!$success) {
+ throw new DomainException("OpenSSL unable to verify data: " . openssl_error_string());
+ } else {
+ return $signature;
+ }
+ case 'hash_hmac':
+ default:
+ $hash = hash_hmac($algorithm, $msg, $key, true);
+ if (function_exists('hash_equals')) {
+ return hash_equals($signature, $hash);
+ }
+ $len = min(self::safeStrlen($signature), self::safeStrlen($hash));
+
+ $status = 0;
+ for ($i = 0; $i < $len; $i++) {
+ $status |= (ord($signature[$i]) ^ ord($hash[$i]));
+ }
+ $status |= (self::safeStrlen($signature) ^ self::safeStrlen($hash));
+
+ return ($status === 0);
+ }
+ }
+
+ /**
+ * Decode a JSON string into a PHP object.
+ *
+ * @param string $input JSON string
+ *
+ * @return object Object representation of JSON string
+ * @throws DomainException Provided string was invalid JSON
+ */
+ public static function jsonDecode($input)
+ {
+ if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
+ /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
+ * to specify that large ints (like Steam Transaction IDs) should be treated as
+ * strings, rather than the PHP default behaviour of converting them to floats.
+ */
+ $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
+ } else {
+ /** Not all servers will support that, however, so for older versions we must
+ * manually detect large ints in the JSON string and quote them (thus converting
+ *them to strings) before decoding, hence the preg_replace() call.
+ */
+ $max_int_length = strlen((string) PHP_INT_MAX) - 1;
+ $json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
+ $obj = json_decode($json_without_bigints);
+ }
+
+ if (function_exists('json_last_error') && $errno = json_last_error()) {
+ JWT::handleJsonError($errno);
+ } elseif ($obj === null && $input !== 'null') {
+ throw new DomainException('Null result with non-null input');
+ }
+ return $obj;
+ }
+
+ /**
+ * Encode a PHP object into a JSON string.
+ *
+ * @param object|array $input A PHP object or array
+ *
+ * @return string JSON representation of the PHP object or array
+ * @throws DomainException Provided object could not be encoded to valid JSON
+ */
+ public static function jsonEncode($input)
+ {
+ $json = json_encode($input);
+ if (function_exists('json_last_error') && $errno = json_last_error()) {
+ JWT::handleJsonError($errno);
+ } elseif ($json === 'null' && $input !== null) {
+ throw new DomainException('Null result with non-null input');
+ }
+ return $json;
+ }
+
+ /**
+ * Decode a string with URL-safe Base64.
+ *
+ * @param string $input A Base64 encoded string
+ *
+ * @return string A decoded string
+ */
+ public static function urlsafeB64Decode($input)
+ {
+ $remainder = strlen($input) % 4;
+ if ($remainder) {
+ $padlen = 4 - $remainder;
+ $input .= str_repeat('=', $padlen);
+ }
+ return base64_decode(strtr($input, '-_', '+/'));
+ }
+
+ /**
+ * Encode a string with URL-safe Base64.
+ *
+ * @param string $input The string you want encoded
+ *
+ * @return string The base64 encode of what you passed in
+ */
+ public static function urlsafeB64Encode($input)
+ {
+ return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
+ }
+
+ /**
+ * Helper method to create a JSON error.
+ *
+ * @param int $errno An error number from json_last_error()
+ *
+ * @return void
+ */
+ private static function handleJsonError($errno)
+ {
+ $messages = array(
+ JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+ JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
+ JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON'
+ );
+ throw new DomainException(
+ isset($messages[$errno])
+ ? $messages[$errno]
+ : 'Unknown JSON error: ' . $errno
+ );
+ }
+
+ /**
+ * Get the number of bytes in cryptographic strings.
+ *
+ * @param string
+ * @return int
+ */
+ private static function safeStrlen($str)
+ {
+ if (function_exists('mb_strlen')) {
+ return mb_strlen($str, '8bit');
+ }
+ return strlen($str);
+ }
+}
diff --git a/function/ProfileClass.php b/function/ProfileClass.php
new file mode 100644
index 0000000..7f5df21
--- /dev/null
+++ b/function/ProfileClass.php
@@ -0,0 +1,707 @@
+<?php
+
+class Profile {
+
+ var $ZePrijavljen;
+ var $ime;
+ var $priimek;
+ var $email;
+ var $geslo;
+ var $EncPass;
+
+ var $LoggingIn;
+
+ function __construct() {
+ global $admin_type;
+ global $lang;
+ global $site_path;
+
+ global $mysql_username;
+ global $mysql_password;
+ global $mysql_server;
+ global $mysql_database_name;
+
+ global $cookie_domain;
+
+ // AAI prijava na vrh.
+ if ($admin_type==-1 && isset ($_SERVER['Shib-Session-Index']) && isset ($_SERVER['eduPersonPrincipalName']) && isset ($_SERVER['mail']) && isset ($_SERVER['givenName']) && isset ($_SERVER['sn']) && $_SERVER['mail']!='') {
+ // se prijavljam preko eduroam!
+ $this->eduroamLogin();
+ }
+
+ if ($admin_type > -1) {
+
+ $this->ZePrijavljen = true;
+ $this->LoggingIn = false;
+
+ $sql = sisplet_query ("SELECT name, surname, lang FROM users WHERE email='" .base64_decode ($_COOKIE['uid']) ."'");
+ if ($r = mysqli_fetch_row ($sql)) {
+ $this->ime = $r[0];
+ $this->priimek = $r[1];
+
+ if (is_numeric ($r[2]) && $r[2] != "0" && $r[2]!=$lang['id']) {
+ unset ($lang);
+ include ($site_path .'lang/' .$r[2] .'.php');
+ }
+
+ $this->ime = CleanXSS ($this->ime);
+ $this->priimek = CleanXSS ($this->priimek);
+ } else {
+ mysqli_select_db($GLOBALS['connect_db'],"meta");
+
+ $sql = sisplet_query ("SELECT ime, priimek FROM administratorji WHERE email='" .base64_decode ($_COOKIE['uid']) ."'");
+ $r = mysqli_fetch_row ($sql);
+ $this->ime = $r[0];
+ $this->priimek = $r[0];
+
+ mysqli_select_db($GLOBALS['connect_db'],$mysql_database_name);
+ }
+ }
+
+ else {
+ if (isset ($_POST['mail'])) $this->email = strtolower ($_POST['mail']);
+ if (isset ($_GET['mail'])) $this->email = strtolower ($_GET['mail']);
+ if (isset ($_POST['pass'])) $this->pass = $_POST['pass'];
+
+ $this->email = CleanXSS ($this->email);
+ $this->pass = CleanXSS ($this->pass);
+
+ $this->LoggingIn = true;
+ }
+ }
+
+
+ function eduroamAnotherServerLogin() {
+ global $pass_salt;
+ global $cookie_domain;
+ global $originating_domain;
+ global $keep_domain;
+
+ // Popravimo string iz geta, ker ima nekje + namesto space
+ $repaired_string = str_replace(' ', '+', $_GET['s']);
+
+ // malo manj varno, ampak bo OK.
+ $klobasa = base64_decode($repaired_string);
+
+
+ // Dobimo array parametrov iz get-a
+ $data = explode ("|", $klobasa);
+
+ // Pridobimo maile - mozno da jih je vec, potem vzamemo prvega
+ $mails = explode(";", $data[0]);
+ sort($mails);
+ $mail = $mails[0];
+
+ $ime = $data[1];
+ $priimek = $data[2];
+
+ $njegova = $data[3];
+ $moja = $data[4];
+
+
+ // Preverimo ce ima veljaven token (najprej pobrisemo stare)
+ sisplet_query ("DELETE FROM aai_prenosi WHERE timestamp < (UNIX_TIMESTAMP() - 600);");
+ $res = sisplet_query ("SELECT * FROM aai_prenosi WHERE moja='" .$moja ."' AND njegova='" .$njegova ."'");
+
+ if (mysqli_num_rows ($res) > 0) {
+
+ $pass = base64_encode((hash('SHA256', "e5zhbWRTEGW&u375ejsznrtztjhdtz%WZ&" .$pass_salt)));
+
+ // Preverimo ce obstaja user v bazi
+ $result = sisplet_query ("SELECT pass, id FROM users WHERE email='" .$mail ."'");
+ if (mysqli_num_rows ($result) == 0) {
+
+ // dodaj ga v bazo
+ $pass = base64_encode(hash('SHA256', "e5zhbWRTEGW&u375ejsznrtztjhdtz%WZ&" .$pass_salt));
+ sisplet_query ("INSERT INTO users (email, name, surname, type, pass, eduroam, when_reg) VALUES ('$mail', '$ime', '$priimek', '3', '" .$pass ."', '1', NOW())");
+
+ // Pridobimo id dodanega userja
+ $user_id = mysqli_insert_id($GLOBALS['connect_db']);
+ }
+ else {
+ // potegni geslo in mu daj kuki
+ $r = mysqli_fetch_row ($result);
+
+ $pass = $r[0];
+ $user_id = $r[1];
+ }
+
+ $result = sisplet_query ("SELECT value FROM misc WHERE what='CookieLife'");
+ $row = mysqli_fetch_row ($result);
+ $LifeTime = $row[0];
+
+ // Zlogiramo login
+ sisplet_query ("UPDATE users SET last_login=NOW() WHERE id='".$user_id."'");
+
+ // določi še, od kje se je prijavil
+ $hostname="";
+ $headers = apache_request_headers();
+ if (array_key_exists('X-Forwarded-For', $headers)){
+ $hostname=$headers['X-Forwarded-For'];
+ } else {
+ $hostname=$_SERVER["REMOTE_ADDR"];
+ }
+
+ sisplet_query ("INSERT INTO user_login_tracker (uid, IP, kdaj) VALUES ('".$user_id."', '" .$hostname ."', NOW())");
+
+ setcookie ("uid", base64_encode($mail), time()+$LifeTime, '/', $cookie_domain);
+ setcookie ("secret", $pass, time()+$LifeTime, '/', $cookie_domain);
+ // moram vedeti, da je AAI!
+ setcookie ("aai", '1', '/', $cookie_domain);
+
+ $this->ZePrijavljen = true;
+
+ // Preveri ce moramo po registraciji vrec na kak URL
+ $rt = sisplet_query ("SELECT value FROM misc WHERE what='AfterReg'");
+ $rxx = mysqli_fetch_row ($rt);
+
+ if (strlen ($rxx[0]) > 3){
+ $rxx[0] = str_replace ($originating_domain, $keep_domain, $rxx[0]);
+ header ('location: ' .$rxx[0] .'?&l=1');
+ }
+ else
+ header ('location: /index.php');
+ }
+ else
+ header ('location: /index.php');
+ }
+
+ function eduroamLogin() {
+ global $pass_salt;
+ global $cookie_domain;
+ global $originating_domain;
+ global $keep_domain;
+
+ $mail = $_SERVER['mail'];
+ $ime = $_SERVER['givenName'];
+ $priimek = $_SERVER['sn'];
+ $pass = base64_encode((hash('SHA256', "e5zhbWRTEGW&u375ejsznrtztjhdtz%WZ&" .$pass_salt)));
+
+ $result = sisplet_query ("SELECT pass, id FROM users WHERE email='" .$mail ."'");
+ if (mysqli_num_rows ($result) == 0) {
+ // dodaj ga v bazo
+ $pass = base64_encode((hash('SHA256', "e5zhbWRTEGW&u375ejsznrtztjhdtz%WZ&" .$pass_salt)));
+ sisplet_query ("INSERT INTO users (email, name, surname, type, pass, eduroam) VALUES ('$mail', '$ime', '$priimek', '3', '" .$pass ."', '1')");
+
+ // Pridobimo id dodanega userja
+ $user_id = mysqli_insert_id($GLOBALS['connect_db']);
+ }
+ else {
+ // potegni geslo in mu daj kuki
+ $r = mysqli_fetch_row ($result);
+
+ $pass = $r[0];
+ $user_id = $r[1];
+ }
+
+ $result = sisplet_query ("SELECT value FROM misc WHERE what='CookieLife'");
+ $row = mysqli_fetch_row ($result);
+ $LifeTime = $row[0];
+
+ sisplet_query ("UPDATE users SET last_login=NOW() WHERE id='" .$user_id ."'");
+ // določi še, od kje se je prijavil
+
+ $hostname="";
+ $headers = apache_request_headers();
+ if (array_key_exists('X-Forwarded-For', $headers)){
+ $hostname=$headers['X-Forwarded-For'];
+ } else {
+ $hostname=$_SERVER["REMOTE_ADDR"];
+ }
+
+ sisplet_query ("INSERT INTO user_login_tracker (uid, IP, kdaj) VALUES ('" .$user_id ."', '" .$hostname ."', NOW())");
+
+ setcookie ("uid", base64_encode($mail), time()+$LifeTime, '/', $cookie_domain);
+ setcookie ("secret", $pass, time()+$LifeTime, '/', $cookie_domain);
+ setcookie("unam", base64_encode($ime.' '.$priimek),time() + $LifeTime, '/', $cookie_domain);
+
+ // moram vedeti, da je AAI!
+ setcookie("aai", '1', time() + $LifeTime, '/', $cookie_domain);
+
+ // Piškotek za cca. 10 let, da mu naslednjić ponudimo prijavno
+ setcookie('external-login', '1', time()+280000000, '/', $cookie_domain);
+
+ $this->ZePrijavljen = true;
+
+ // Preveri ce moramo po registraciji vrec na kak URL
+ $rt = sisplet_query ("SELECT value FROM misc WHERE what='AfterReg'");
+ $rxx = mysqli_fetch_row ($rt);
+
+ if (strlen ($rxx[0]) > 3){
+ $rxx[0] = str_replace ($originating_domain, $keep_domain, $rxx[0]);
+ header ('location: ' .$rxx[0] .'?&l=1');
+ }
+ else
+ header ('location: /index.php');
+ }
+
+ function GoogleLogin () {
+
+ require_once ('../function/JWT.php');
+
+ global $google_login_client_id;
+ global $google_login_client_secret;
+ global $site_url;
+ global $lang;
+ global $proxy;
+
+ $oauth2_code = $_GET['code'];
+ $discovery = json_decode(file_get_contents('https://accounts.google.com/.well-known/openid-configuration'));
+
+ if ($proxy != "") {
+ $ctx = stream_context_create(array(
+ 'http' => array(
+ 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
+ 'method' => 'POST',
+ 'content' => http_build_query(array(
+ 'client_id' => $google_login_client_id,
+ 'client_secret' => $google_login_client_secret,
+ 'code' => $oauth2_code,
+ 'grant_type' => 'authorization_code',
+ 'redirect_uri' => $site_url .'utils/google-oauth2.php',
+ 'openid.realm' => $site_url,
+ )),
+ 'proxy' => 'tcp://' .$proxy,
+ ),
+ ));
+
+ }
+ else {
+ $ctx = stream_context_create(array(
+ 'http' => array(
+ 'header' => "Content-type: application/x-www-form-urlencoded\r\n",
+ 'method' => 'POST',
+ 'content' => http_build_query(array(
+ 'client_id' => $google_login_client_id,
+ 'client_secret' => $google_login_client_secret,
+ 'code' => $oauth2_code,
+ 'grant_type' => 'authorization_code',
+ 'redirect_uri' => $site_url .'utils/google-oauth2.php',
+ 'openid.realm' => $site_url,
+ )),
+ ),
+ ));
+
+ }
+
+ $resp = file_get_contents($discovery->token_endpoint, false, $ctx);
+ if (!$resp) {
+ // $http_response_header here got magically populated by file_get_contents(), surprise
+ echo '<h1>' .$lang['oid_auth_rejected'] .'</h1>';
+ echo '<p>' .$lang['google_auth_rejected'] .'</p>';
+
+ echo '<ul><li>' .$lang['oid_maybe_you_rejected'] .'<a href="' .$site_url .'index.php?fl=8&lact=40">' .$lang['try_again'] .'</a></li><li>' .$lang['oid_maybe_local1'] .'<a href="' .$site_url .'index.php?fl=8&lact=20">' .$lang['oid_maybe_local2'] .'</a></li></ul>';
+ }
+ $resp = json_decode($resp);
+ $access_token = $resp->access_token;
+ $id_token = $resp->id_token;
+
+ // Skip JWT verification: we got it directly from Google via https, nothing could go wrong.
+ $id_payload = JWT::decode($resp->id_token, null, false);
+ if (!$id_payload->sub) {
+ echo '<h1>' .$lang['oid_auth_rejected'] .'</h1>';
+ echo '<p>' .$lang['google_auth_rejected'] .'</p>';
+
+ echo '<ul><li>' .$lang['oid_maybe_you_rejected'] .'<a href="' .$site_url .'index.php?fl=8&lact=40">' .$lang['try_again'] .'</a></li><li>' .$lang['oid_maybe_local1'] .'<a href="' .$site_url .'index.php?fl=8&lact=20">' .$lang['oid_maybe_local2'] .'</a></li></ul>';
+ }
+
+ $user_id = 'google+' . $id_payload->sub;
+ $user_email = $id_payload->email;
+
+ if ($user_email != '' && $user_id != '') {
+ $this->email = $user_email;
+
+ $res = sisplet_query ("SELECT pass FROM users WHERE email='" .$user_email ."'");
+
+ // Je noter, ga samo prijavim...
+ if (mysqli_num_rows ($res) > 0) {
+ $r = mysqli_fetch_row ($res);
+
+ $this->EncPass = $r[0];
+
+ $this->Login();
+ }
+ // Ni se registriran, ga je potrebno dodati na prijavno formo
+ else {
+ // geslo med 00000 in zzzzz
+ $this->pass = base_convert(mt_rand(0x19A100, 0x39AA3FF), 10, 36);
+ $this->EncPass = base64_encode((hash('SHA256', $this->pass .$pass_salt)));
+ $this->email = $user_email;
+ $fn = explode ("@", $user_email);
+
+ sisplet_query ("INSERT INTO users
+ (name, surname, email, pass, lang, when_reg)
+ VALUES
+ ('".$fn[0]."', '', '".$user_email."', '".$this->EncPass."', '".(isset ($_GET['regFromEnglish']) && $_GET['regFromEnglish']=="1"?'2':'1')."', NOW())");
+ $uid = mysqli_insert_id($GLOBALS['connect_db']);
+
+ sisplet_query ("INSERT INTO oid_users (uid) VALUES ('$uid')");
+
+ // prijavi
+ $this->Login();
+ }
+ }
+ }
+
+
+ function Login () {
+ global $mysql_database_name;
+ global $site_url;
+ global $lang;
+ global $pass_salt;
+
+ global $cookie_domain;
+
+ global $originating_domain;
+ global $keep_domain;
+
+
+ // if ($this->LoggingIn == true) {
+ $mini = $this->email .$this->pass;
+
+ for ($Stevec=0; $Stevec<strlen ($mini); $Stevec++)
+ {
+ $mini = str_replace ("'", "", $mini);
+ }
+
+ $result = sisplet_query ("SELECT value FROM misc WHERE what='CookieLife'");
+ $row = mysqli_fetch_row ($result);
+ $LifeTime = $row[0];
+
+ if ((isset($_POST['remember']) && $_POST['remember'] == "1") || (isset($_COOKIE['remember-me']) && $_COOKIE['remember-me'] == 1)) {
+ $LifeTime = 3600 * 24 * 365;
+ } else {
+ $LifeTime = $LifeTime;
+ }
+
+ $sql = sisplet_query("SELECT type, pass, status, id, name, surname, email FROM users WHERE email='" .$this->email ."'");
+ if (mysqli_num_rows($sql) > 0)
+ {
+ $r = mysqli_fetch_row ($sql);
+
+ // BAN
+ if ($r[2] == 0)
+ {
+ header('Location: ' .$site_url .'index.php?fl=8&lact=12&ban=1&em=' .$this->email);
+ die();
+ }
+
+ if (base64_encode((hash('SHA256', $this->pass .$pass_salt))) == $r[1] || $this->EncPass == $r[1])
+ {
+
+ sisplet_query ("UPDATE users SET last_login=NOW() WHERE id='" .$r[3] ."'");
+
+
+ // določi še, od kje se je prijavil
+
+ $hostname="";
+ $headers = apache_request_headers();
+ if (array_key_exists('X-Forwarded-For', $headers)){
+ $hostname=$headers['X-Forwarded-For'];
+ } else {
+ $hostname=$_SERVER["REMOTE_ADDR"];
+ }
+
+ sisplet_query ("INSERT INTO user_login_tracker (uid, IP, kdaj) VALUES ('" .$r[3] ."', '" .$hostname ."', NOW())");
+
+
+ setcookie ("uid", base64_encode($this->email), time()+$LifeTime, '/', $cookie_domain);
+ setcookie("unam", base64_encode($r[4].' '.$r[5]),time() + $LifeTime, '/', $cookie_domain);
+ setcookie ("secret", $r[1], time()+$LifeTime, '/', $cookie_domain);
+
+ if ($r[2] == "2" || $r[2] == "6")
+ {
+ setcookie ("P", time(), time()+$LifeTime, '/', $cookie_domain);
+ }
+
+ $this->ZePrijavljen = true;
+
+ if (isset ($_POST['l']) && $_POST['l']!='')
+ {
+ header ('location: ' .$site_url .str_replace (base64_decode($_POST['l']), $site_url, ""));
+ }
+
+
+ // Preveri ce moramo po registraciji vrec na kak URL
+ $rt = sisplet_query ("SELECT value FROM misc WHERE what='AfterReg'");
+ $rxx = mysqli_fetch_row ($rt);
+
+ if (strlen ($rxx[0]) > 3)
+ {
+ $rxx[0] = str_replace ($originating_domain, $keep_domain, $rxx[0]);
+ header ('location: ' .$rxx[0] .'?&l=1');
+ } else {
+ $CheckCasovnice = sisplet_query ("SELECT * FROM misc WHERE what='TimeTables' AND value='1'");
+ if (mysqli_num_rows ($CheckCasovnice) != 0)
+ {
+ if (!isset ($_GET['l'])) header('Location: ' .$site_url .'index.php?fl=13');
+ else header('Location: ' .base64_decode($_GET['l']) .'&l=1');
+ }
+ else
+ {
+ if (!isset ($_GET['l'])) header('Location: ' .$site_url .'?l=1');
+ else header('Location: ' .base64_decode($_GET['l']) .'?&l=1');
+ }
+ }
+ }
+ else
+ {
+ // Password prompt
+ header ('location: ' .$site_url .'index.php?fl=8&lact=20&em=' .$this->email .(isset ($_GET['l'])?'&l=' .$_GET['l']:''));
+ die();
+ }
+ }
+ else
+ {
+ // Ni kul mail
+ header ('location: ' .$site_url .'index.php?fl=8&lact=10&em=' .$this->email .(isset ($_GET['l'])?'&l=' .$_GET['l']:''));
+ die();
+ }
+ }
+
+ function Logout ($gohome='1') {
+ global $site_url;
+ global $cookie_domain;
+ global $global_user_id;
+
+ setcookie ('uid', '', time()-3600, '/', $cookie_domain);
+ setcookie ('secret', '', time()-3600, '/', $cookie_domain);
+ setcookie ('ME', '', time()-3600, '/', $cookie_domain);
+ setcookie ('P', '', time()-3600, '/', $cookie_domain);
+ setcookie ("AN", '', time()-3600, '/', $cookie_domain);
+ setcookie ("AS", '', time()-3600, '/', $cookie_domain);
+ setcookie ("AT", '', time()-3600, '/', $cookie_domain);
+
+ setcookie("DP", $p, time()-3600*24*365, "/", $cookie_domain);
+ setcookie("DC", $p, time()-3600*24*365, "/", $cookie_domain);
+ setcookie("DI", $p, time()-3600*24*365, "/", $cookie_domain);
+ setcookie("SO", $p, time()-3600*24*365, "/", $cookie_domain);
+ setcookie("SPO", $p, time()-3600*24*365, "/", $cookie_domain);
+ setcookie("SL", $p, time()-3600*24*365, "/", $cookie_domain);
+
+
+ // pobrisi se naddomeno! (www.1ka.si naj pobrise se 1ka.si)
+ if (substr_count ($cookie_domain, ".") > 1) {
+ $nd = substr ($cookie_domain, strpos ($cookie_domain, ".")+1);
+
+ setcookie ('uid', '', time()-3600, '/', $nd);
+ setcookie ('secret', '', time()-3600, '/', $nd);
+ setcookie ('ME', '', time()-3600, '/', $nd);
+ setcookie ('P', '', time()-3600, '/', $nd);
+ setcookie ("AN", '', time()-3600, '/', $nd);
+ setcookie ("AS", '', time()-3600, '/', $nd);
+ setcookie ("AT", '', time()-3600, '/', $nd);
+
+ setcookie("DP", $p, time()-3600*24*365, "/", $nd);
+ setcookie("DC", $p, time()-3600*24*365, "/", $nd);
+ setcookie("DI", $p, time()-3600*24*365, "/", $nd);
+ setcookie("SO", $p, time()-3600*24*365, "/", $nd);
+ setcookie("SPO", $p, time()-3600*24*365, "/", $nd);
+ setcookie("SL", $p, time()-3600*24*365, "/", $nd);
+ }
+
+ if (isset($_COOKIE['aai']) && !empty ($_COOKIE['aai']) && $_COOKIE['aai']=="1") {
+ setcookie ("aai", '', time()-3600, '/', $cookie_domain);
+ header ('location: https://aai.1ka.si/Shibboleth.sso/Logout?return=https://www.1ka.si');
+ die();
+ }
+
+ // Preusmerimo na domaco stran
+ if ($gohome=='1')
+ header('Location:' .$site_url);
+ }
+
+
+ function FBLogin() {
+ global $facebook_appid;
+ global $facebook_appsecret;
+ global $cookie_path;
+
+ if ($r = file_get_contents ("https://graph.facebook.com/v2.9/oauth/access_token?client_id=" .$facebook_appid ."&redirect_uri=https://www.1ka.si/fb_login.php&client_secret=" .$facebook_appsecret ."&code=" .$_GET['code'])) {
+ $at = json_decode ($r);
+
+ $user = json_decode(file_get_contents('https://graph.facebook.com/me?fields=email,first_name,last_name&access_token=' .$at->{'access_token'}));
+
+ if (!isset ($user->email) && isset ($user->name)) {
+ $user->email = str_replace(" ", ".", $user->first_name ."." .$user->last_name) ."@facebook.com";
+ }
+
+ $old_email = str_replace(" ", ".", $user->first_name ."." .$user->last_name) ."@facebook.com";
+ $old_email = str_replace (array(" ","č","ć","Č","Ć","ž","Ž","š","Š","đ","Đ"), array(".","c","c","C","C","z","Z","s","S","d","D"), $old_email);
+
+ // preveri email, ce ga imas v bazi:
+ if (isset ($user->email) && $user->email!='') {
+ $result = sisplet_query ("select u.name, u.surname, f.id, u.id, u.pass FROM users u, fb_users f WHERE u.id=f.uid AND u.email='" .str_replace ("'", '', $user->email) ."'");
+ if (mysqli_num_rows ($result)==0) {
+ $result2 = sisplet_query ("select u.id FROM users u LEFT JOIN fb_users f on (u.id=f.uid) where u.email='" .str_replace ("'", '', $old_email) ."'");
+ if (mysqli_num_rows ($result2)>0) {
+ $r2 = mysqli_fetch_row ($result2);
+
+ $result3 = sisplet_query ("SELECT id FROM users WHERE email='" .$user->email ."'");
+ if (mysqli_num_rows ($result3) > 0) {
+ $real_id = mysqli_fetch_row ($result3);
+
+ // moramo popravljati IDje in jebat ježa
+ // iz "pravega" skopiram geslo na "fb", "fb" popravim v pravega in pravega dizejblam. In iz pravega vse srv_dpstop popravim na "fb"
+ sisplet_query ("UPDATE users a, users b SET a.pass=b.pass WHERE a.email='" .str_replace ("'", '', $old_email) ."' AND b.email='" .str_replace ("'", '', $user->email) ."'");
+ sisplet_query ("UPDATE users SET email=CONCAT('D3LMD-' , email) WHERE email='" .str_replace ("'", '', $user->email) ."'");
+
+ if ($real_id[0] > 0 && $r2[0] > 0) {
+ sisplet_query ("UPDATE srv_dostop SET uid=" .$r2[0] ." WHERE uid=" .$real_id[0]);
+ }
+ }
+ sisplet_query ("UPDATE users SET email='" .str_replace ("'", '', $user->email) ."' WHERE id='" .$r2[0] ."'");
+
+ }
+
+ }
+ $result = sisplet_query ("select u.name, u.surname, IF(ISNULL(f.id),'0',f.id), u.id, u.pass FROM users u LEFT JOIN fb_users f on (u.id=f.uid) where u.email='" .str_replace ("'", '', $user->email) ."'");
+
+
+ // je noter, preveri ce je v FB (podatki, podatki!)
+ if (mysqli_num_rows ($result)>0) {
+
+ $r = mysqli_fetch_row ($result);
+
+ if ($r[2]!='0') {
+ // samo prijavi
+ $this->EncPass = $r[4];
+ $this->email = str_replace (" ", ".", $user->email);
+
+ $this->Login();
+ }
+ else {
+ // dodaj FB podatke in prijavi
+ if (isset ($user->first_name)) $fn = $user->first_name;
+ else $fn = $r[0];
+
+ if (isset ($user->last_name)) $ln = $user->last_name;
+ else $ln = $r[1];
+
+ if (isset ($user->gender)) $gn = $user->gender;
+ else $gn = '';
+
+ if (isset ($user->profile_link)) $pl = $user->profile_link;
+ else $pl = '';
+
+ if (isset ($user->timezone)) $tz = $user->timezone;
+ else $tz = '';
+
+ sisplet_query ("INSERT INTO fb_users (uid, first_name, last_name, gender, timezone, profile_link) VALUES ('" .$r[3] ."', '" .$fn ."', '" .$ln ."', '" .$gn ."', '" .$tz ."', '" .$pl ."')");
+
+ // Prijaviga :)
+ $this->EncPass = $r[4];
+ $this->email = $user->email;
+
+ $this->Login();
+
+ }
+ }
+ else {
+ // registriraj, dodaj FB podatke in prijavi
+ // dodaj FB podatke in prijavi
+ if (isset ($user->first_name)) $fn = $user->first_name;
+ else $fn = str_replace (" ", ".", $r[0]);
+
+ if (isset ($user->last_name)) $ln = $user->last_name;
+ else $ln = $r[1];
+
+ if (isset ($user->gender)) $gn = $user->gender;
+ else $gn = '';
+
+ if (isset ($user->profile_link)) $pl = $user->profile_link;
+ else $pl = '';
+
+ if (isset ($user->timezone)) $tz = $user->timezone;
+ else $tz = '';
+
+ // geslo med 00000 in zzzzz
+ $this->pass = base_convert(mt_rand(0x19A100, 0x39AA3FF), 10, 36);
+ $this->EncPass = base64_encode((hash('SHA256', $this->pass .$pass_salt)));
+ $this->email = str_replace (array(" ","č","ć","Č","Ć","ž","Ž","š","Š","đ","Đ"), array(".","c","c","C","C","z","Z","s","S","d","D"), $user->email);
+
+ //sisplet_query ("INSERT INTO users (name, surname, email, pass, when_reg) VALUES ('" .iconv('utf-8', 'iso-8859-2//TRANSLIT', $fn) ."', '" .iconv('utf-8', 'iso-8859-2//TRANSLIT',$ln) ."', '" .iconv('utf-8', 'iso-8859-2//TRANSLIT',$this->email) ."', '" .$this->EncPass ."', NOW())");
+ sisplet_query ("INSERT INTO users (name, surname, email, pass, when_reg) VALUES ('" . $fn ."', '" . $ln ."', '" .iconv('utf-8', 'iso-8859-2//TRANSLIT',$this->email) ."', '" .$this->EncPass ."', NOW())");
+ $uid = mysqli_insert_id($GLOBALS['connect_db']);
+
+ //sisplet_query ("INSERT INTO fb_users (uid, first_name, last_name, gender, timezone, profile_link) VALUES ('" .$uid ."', '" .iconv('utf-8', 'iso-8859-2//TRANSLIT',$fn) ."', '" .iconv('utf-8', 'iso-8859-2//TRANSLIT',$ln) ."', '" .$gn ."', '" .$tz ."', '" .$pl ."')");
+ sisplet_query ("INSERT INTO fb_users (uid, first_name, last_name, gender, timezone, profile_link) VALUES ('" .$uid ."', '" . $fn ."', '" . $ln ."', '" .$gn ."', '" .$tz ."', '" .$pl ."')");
+
+ // prijavi
+ $this->Login();
+ }
+ }
+ }
+ }
+}
+
+
+// popravek, FB sprememba...
+function get_facebook_cookie($app_id, $app_secret) {
+ if ($_COOKIE['fbsr_' . $app_id] != '') {
+ return get_new_facebook_cookie($app_id, $app_secret);
+ } else {
+ return get_old_facebook_cookie($app_id, $app_secret);
+ }
+}
+
+function get_old_facebook_cookie($app_id, $app_secret) {
+ $args = array();
+ parse_str(trim($_COOKIE['fbs_' . $app_id], '\\"'), $args);
+ ksort($args);
+ $payload = '';
+ foreach ($args as $key => $value) {
+ if ($key != 'sig') {
+ $payload .= $key . '=' . $value;
+ }
+ }
+ if (md5($payload . $app_secret) != $args['sig']) {
+ return array();
+ }
+ return $args;
+}
+
+function get_new_facebook_cookie($app_id, $app_secret) {
+ $signed_request = parse_signed_request($_COOKIE['fbsr_' . $app_id], $app_secret);
+ // $signed_request should now have most of the old elements
+
+ $signed_request[uid] = $signed_request[user_id]; // for compatibility
+
+ if (!is_null($signed_request)) {
+ // the cookie is valid/signed correctly
+ // lets change "code" into an "access_token"
+ $access_token_response = file_get_contents("https://graph.facebook.com/oauth/access_token?client_id=$app_id&redirect_uri=&client_secret=$app_secret&code=$signed_request[code]");
+ parse_str($access_token_response);
+ $signed_request[access_token] = $access_token;
+ $signed_request[expires] = time() + $expires;
+ }
+ return $signed_request;
+}
+
+
+function parse_signed_request($signed_request, $secret) {
+ list($encoded_sig, $payload) = explode('.', $signed_request, 2);
+
+ // decode the data
+ $sig = base64_url_decode($encoded_sig);
+ $data = json_decode(base64_url_decode($payload), true);
+
+ if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
+ error_log('Unknown algorithm. Expected HMAC-SHA256');
+ return null;
+ }
+
+ // check sig
+ $expected_sig = hash_hmac('sha256', $payload, $secret, $raw = true);
+ if ($sig !== $expected_sig) {
+ error_log('Bad Signed JSON signature!');
+ return null;
+ }
+
+ return $data;
+}
+
+function base64_url_decode($input) {
+ return base64_decode(strtr($input, '-_', '+/'));
+}
+
diff --git a/function/RFC822.php b/function/RFC822.php
new file mode 100644
index 0000000..ce41f72
--- /dev/null
+++ b/function/RFC822.php
@@ -0,0 +1,872 @@
+<?php
+/**
+* RFC 822 Email address list validation Utility
+*
+* What is it?
+*
+* This class will take an address string, and parse it into it's consituent
+* parts, be that either addresses, groups, or combinations. Nested groups
+* are not supported. The structure it returns is pretty straight forward,
+* and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
+* print_r() to view the structure.
+*
+* How do I use it?
+*
+* $address_string = 'My Group: "Richard Heyes" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
+* $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', TRUE)
+* print_r($structure);
+*
+* @author Richard Heyes <richard@phpguru.org>
+* @author Chuck Hagenbuch <chuck@horde.org>
+* @version $Revision: 1.1 $
+* @package Mail
+*/
+
+class Mail_RFC822
+{
+ /**
+ * The address being parsed by the RFC822 object.
+ * @var string $address
+ */
+ var $address = '';
+
+ /**
+ * The default domain to use for unqualified addresses.
+ * @var string $default_domain
+ */
+ var $default_domain = 'localhost';
+
+ /**
+ * Should we return a nested array showing groups, or flatten everything?
+ * @var boolean $nestGroups
+ */
+ var $nestGroups = true;
+
+ /**
+ * Whether or not to validate atoms for non-ascii characters.
+ * @var boolean $validate
+ */
+ var $validate = true;
+
+ /**
+ * The array of raw addresses built up as we parse.
+ * @var array $addresses
+ */
+ var $addresses = array();
+
+ /**
+ * The final array of parsed address information that we build up.
+ * @var array $structure
+ */
+ var $structure = array();
+
+ /**
+ * The current error message, if any.
+ * @var string $error
+ */
+ var $error = null;
+
+ /**
+ * An internal counter/pointer.
+ * @var integer $index
+ */
+ var $index = null;
+
+ /**
+ * The number of groups that have been found in the address list.
+ * @var integer $num_groups
+ * @access public
+ */
+ var $num_groups = 0;
+
+ /**
+ * A variable so that we can tell whether or not we're inside a
+ * Mail_RFC822 object.
+ * @var boolean $mailRFC822
+ */
+ var $mailRFC822 = true;
+
+ /**
+ * A limit after which processing stops
+ * @var int $limit
+ */
+ var $limit = null;
+
+
+ /**
+ * Sets up the object. The address must either be set here or when
+ * calling parseAddressList(). One or the other.
+ *
+ * @access public
+ * @param string $address The address(es) to validate.
+ * @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost.
+ * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
+ * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+ *
+ * @return object Mail_RFC822 A new Mail_RFC822 object.
+ */
+ function __construct($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+ {
+ if (isset($address)) $this->address = $address;
+ if (isset($default_domain)) $this->default_domain = $default_domain;
+ if (isset($nest_groups)) $this->nestGroups = $nest_groups;
+ if (isset($validate)) $this->validate = $validate;
+ if (isset($limit)) $this->limit = $limit;
+ }
+
+
+ /**
+ * Starts the whole process. The address must either be set here
+ * or when creating the object. One or the other.
+ *
+ * @access public
+ * @param string $address The address(es) to validate.
+ * @param string $default_domain Default domain/host etc.
+ * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
+ * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+ *
+ * @return array A structured array of addresses.
+ */
+ function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+ {
+
+ if (!isset($this->mailRFC822)) {
+ $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
+ return $obj->parseAddressList();
+ }
+
+ if (isset($address)) $this->address = $address;
+ if (isset($default_domain)) $this->default_domain = $default_domain;
+ if (isset($nest_groups)) $this->nestGroups = $nest_groups;
+ if (isset($validate)) $this->validate = $validate;
+ if (isset($limit)) $this->limit = $limit;
+
+ $this->structure = array();
+ $this->addresses = array();
+ $this->error = null;
+ $this->index = null;
+
+ while ($this->address = $this->_splitAddresses($this->address)) {
+ continue;
+ }
+
+ if ($this->address === false || isset($this->error)) {
+ return false;
+ }
+
+ // Reset timer since large amounts of addresses can take a long time to
+ // get here
+ set_time_limit(30);
+
+ // Loop through all the addresses
+ for ($i = 0; $i < count($this->addresses); $i++){
+
+ if (($return = $this->_validateAddress($this->addresses[$i])) === false
+ || isset($this->error)) {
+ return false;
+ }
+
+ if (!$this->nestGroups) {
+ $this->structure = array_merge($this->structure, $return);
+ } else {
+ $this->structure[] = $return;
+ }
+ }
+
+ return $this->structure;
+ }
+
+ /**
+ * Splits an address into seperate addresses.
+ *
+ * @access private
+ * @param string $address The addresses to split.
+ * @return boolean Success or failure.
+ */
+ function _splitAddresses($address)
+ {
+
+ if (!empty($this->limit) AND count($this->addresses) == $this->limit) {
+ return '';
+ }
+
+ if ($this->_isGroup($address) && !isset($this->error)) {
+ $split_char = ';';
+ $is_group = true;
+ } elseif (!isset($this->error)) {
+ $split_char = ',';
+ $is_group = false;
+ } elseif (isset($this->error)) {
+ return false;
+ }
+
+ // Split the string based on the above ten or so lines.
+ $parts = explode($split_char, $address);
+ $string = $this->_splitCheck($parts, $split_char);
+
+ // If a group...
+ if ($is_group) {
+ // If $string does not contain a colon outside of
+ // brackets/quotes etc then something's fubar.
+
+ // First check there's a colon at all:
+ if (strpos($string, ':') === false) {
+ $this->error = 'Invalid address: ' . $string;
+ return false;
+ }
+
+ // Now check it's outside of brackets/quotes:
+ if (!$this->_splitCheck(explode(':', $string), ':'))
+ return false;
+
+ // We must have a group at this point, so increase the counter:
+ $this->num_groups++;
+ }
+
+ // $string now contains the first full address/group.
+ // Add to the addresses array.
+ $this->addresses[] = array(
+ 'address' => trim($string),
+ 'group' => $is_group
+ );
+
+ // Remove the now stored address from the initial line, the +1
+ // is to account for the explode character.
+ $address = trim(substr($address, strlen($string) + 1));
+
+ // If the next char is a comma and this was a group, then
+ // there are more addresses, otherwise, if there are any more
+ // chars, then there is another address.
+ if ($is_group && substr($address, 0, 1) == ','){
+ $address = trim(substr($address, 1));
+ return $address;
+
+ } elseif (strlen($address) > 0) {
+ return $address;
+
+ } else {
+ return '';
+ }
+
+ // If you got here then something's off
+ return false;
+ }
+
+ /**
+ * Checks for a group at the start of the string.
+ *
+ * @access private
+ * @param string $address The address to check.
+ * @return boolean Whether or not there is a group at the start of the string.
+ */
+ function _isGroup($address)
+ {
+ // First comma not in quotes, angles or escaped:
+ $parts = explode(',', $address);
+ $string = $this->_splitCheck($parts, ',');
+
+ // Now we have the first address, we can reliably check for a
+ // group by searching for a colon that's not escaped or in
+ // quotes or angle brackets.
+ if (count($parts = explode(':', $string)) > 1) {
+ $string2 = $this->_splitCheck($parts, ':');
+ return ($string2 !== $string);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * A common function that will check an exploded string.
+ *
+ * @access private
+ * @param array $parts The exloded string.
+ * @param string $char The char that was exploded on.
+ * @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
+ */
+ function _splitCheck($parts, $char)
+ {
+ $string = $parts[0];
+
+ for ($i = 0; $i < count($parts); $i++) {
+ if ($this->_hasUnclosedQuotes($string)
+ || $this->_hasUnclosedBrackets($string, '<>')
+ || $this->_hasUnclosedBrackets($string, '[]')
+ || $this->_hasUnclosedBrackets($string, '()')
+ || substr($string, -1) == '\\') {
+ if (isset($parts[$i + 1])) {
+ $string = $string . $char . $parts[$i + 1];
+ } else {
+ $this->error = 'Invalid address spec. Unclosed bracket or quotes';
+ return false;
+ }
+ } else {
+ $this->index = $i;
+ break;
+ }
+ }
+
+ return $string;
+ }
+
+ /**
+ * Checks if a string has an unclosed quotes or not.
+ *
+ * @access private
+ * @param string $string The string to check.
+ * @return boolean True if there are unclosed quotes inside the string, false otherwise.
+ */
+ function _hasUnclosedQuotes($string)
+ {
+ $string = explode('"', $string);
+ $string_cnt = count($string);
+
+ for ($i = 0; $i < (count($string) - 1); $i++)
+ if (substr($string[$i], -1) == '\\')
+ $string_cnt--;
+
+ return ($string_cnt % 2 === 0);
+ }
+
+ /**
+ * Checks if a string has an unclosed brackets or not. IMPORTANT:
+ * This function handles both angle brackets and square brackets;
+ *
+ * @access private
+ * @param string $string The string to check.
+ * @param string $chars The characters to check for.
+ * @return boolean True if there are unclosed brackets inside the string, false otherwise.
+ */
+ function _hasUnclosedBrackets($string, $chars)
+ {
+ $num_angle_start = substr_count($string, $chars[0]);
+ $num_angle_end = substr_count($string, $chars[1]);
+
+ $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
+ $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
+
+ if ($num_angle_start < $num_angle_end) {
+ $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
+ return false;
+ } else {
+ return ($num_angle_start > $num_angle_end);
+ }
+ }
+
+ /**
+ * Sub function that is used only by hasUnclosedBrackets().
+ *
+ * @access private
+ * @param string $string The string to check.
+ * @param integer &$num The number of occurences.
+ * @param string $char The character to count.
+ * @return integer The number of occurences of $char in $string, adjusted for backslashes.
+ */
+ function _hasUnclosedBracketsSub($string, &$num, $char)
+ {
+ $parts = explode($char, $string);
+ for ($i = 0; $i < count($parts); $i++){
+ if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
+ $num--;
+ if (isset($parts[$i + 1]))
+ $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
+ }
+
+ return $num;
+ }
+
+ /**
+ * Function to begin checking the address.
+ *
+ * @access private
+ * @param string $address The address to validate.
+ * @return mixed False on failure, or a structured array of address information on success.
+ */
+ function _validateAddress($address)
+ {
+ $is_group = false;
+
+ if ($address['group']) {
+ $is_group = true;
+
+ // Get the group part of the name
+ $parts = explode(':', $address['address']);
+ $groupname = $this->_splitCheck($parts, ':');
+ $structure = array();
+
+ // And validate the group part of the name.
+ if (!$this->_validatePhrase($groupname)){
+ $this->error = 'Group name did not validate.';
+ return false;
+ } else {
+ // Don't include groups if we are not nesting
+ // them. This avoids returning invalid addresses.
+ if ($this->nestGroups) {
+ $structure = new stdClass;
+ $structure->groupname = $groupname;
+ }
+ }
+
+ $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
+ }
+
+ // If a group then split on comma and put into an array.
+ // Otherwise, Just put the whole address in an array.
+ if ($is_group) {
+ while (strlen($address['address']) > 0) {
+ $parts = explode(',', $address['address']);
+ $addresses[] = $this->_splitCheck($parts, ',');
+ $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
+ }
+ } else {
+ $addresses[] = $address['address'];
+ }
+
+ // Check that $addresses is set, if address like this:
+ // Groupname:;
+ // Then errors were appearing.
+ if (!isset($addresses)){
+ $this->error = 'Empty group.';
+ return false;
+ }
+
+ for ($i = 0; $i < count($addresses); $i++) {
+ $addresses[$i] = trim($addresses[$i]);
+ }
+
+ // Validate each mailbox.
+ // Format could be one of: name <geezer@domain.com>
+ // geezer@domain.com
+ // geezer
+ // ... or any other format valid by RFC 822.
+ array_walk($addresses, array($this, 'validateMailbox'));
+
+ // Nested format
+ if ($this->nestGroups) {
+ if ($is_group) {
+ $structure->addresses = $addresses;
+ } else {
+ $structure = $addresses[0];
+ }
+
+ // Flat format
+ } else {
+ if ($is_group) {
+ $structure = array_merge($structure, $addresses);
+ } else {
+ $structure = $addresses;
+ }
+ }
+
+ return $structure;
+ }
+
+ /**
+ * Function to validate a phrase.
+ *
+ * @access private
+ * @param string $phrase The phrase to check.
+ * @return boolean Success or failure.
+ */
+ function _validatePhrase($phrase)
+ {
+ // Splits on one or more Tab or space.
+ $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
+
+ $phrase_parts = array();
+ while (count($parts) > 0){
+ $phrase_parts[] = $this->_splitCheck($parts, ' ');
+ for ($i = 0; $i < $this->index + 1; $i++)
+ array_shift($parts);
+ }
+
+ for ($i = 0; $i < count($phrase_parts); $i++) {
+ // If quoted string:
+ if (substr($phrase_parts[$i], 0, 1) == '"') {
+ if (!$this->_validateQuotedString($phrase_parts[$i]))
+ return false;
+ continue;
+ }
+
+ // Otherwise it's an atom:
+ if (!$this->_validateAtom($phrase_parts[$i])) return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Function to validate an atom which from rfc822 is:
+ * atom = 1*<any CHAR except specials, SPACE and CTLs>
+ *
+ * If validation ($this->validate) has been turned off, then
+ * validateAtom() doesn't actually check anything. This is so that you
+ * can split a list of addresses up before encoding personal names
+ * (umlauts, etc.), for example.
+ *
+ * @access private
+ * @param string $atom The string to check.
+ * @return boolean Success or failure.
+ */
+ function _validateAtom($atom)
+ {
+ if (!$this->validate) {
+ // Validation has been turned off; assume the atom is okay.
+ return true;
+ }
+
+ // Check for any char from ASCII 0 - ASCII 127
+ if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
+ return false;
+ }
+
+ // Check for specials:
+ if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
+ return false;
+ }
+
+ // Check for control characters (ASCII 0-31):
+ if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Function to validate quoted string, which is:
+ * quoted-string = <"> *(qtext/quoted-pair) <">
+ *
+ * @access private
+ * @param string $qstring The string to check
+ * @return boolean Success or failure.
+ */
+ function _validateQuotedString($qstring)
+ {
+ // Leading and trailing "
+ $qstring = substr($qstring, 1, -1);
+
+ // Perform check.
+ return !(preg_match('/(.)[\x0D\\\\"]/', $qstring, $matches) && $matches[1] != '\\');
+ }
+
+ /**
+ * Function to validate a mailbox, which is:
+ * mailbox = addr-spec ; simple address
+ * / phrase route-addr ; name and route-addr
+ *
+ * @access public
+ * @param string &$mailbox The string to check.
+ * @return boolean Success or failure.
+ */
+ function validateMailbox(&$mailbox)
+ {
+ // A couple of defaults.
+ $phrase = '';
+ $comment = '';
+
+ // Catch any RFC822 comments and store them separately
+ $_mailbox = $mailbox;
+ while (strlen(trim($_mailbox)) > 0) {
+ $parts = explode('(', $_mailbox);
+ $before_comment = $this->_splitCheck($parts, '(');
+ if ($before_comment != $_mailbox) {
+ // First char should be a (
+ $comment = substr(str_replace($before_comment, '', $_mailbox), 1);
+ $parts = explode(')', $comment);
+ $comment = $this->_splitCheck($parts, ')');
+ $comments[] = $comment;
+
+ // +1 is for the trailing )
+ $_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
+ } else {
+ break;
+ }
+ }
+
+ for($i=0; $i<count(@$comments); $i++){
+ $mailbox = str_replace('('.$comments[$i].')', '', $mailbox);
+ }
+ $mailbox = trim($mailbox);
+
+ // Check for name + route-addr
+ if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
+ $parts = explode('<', $mailbox);
+ $name = $this->_splitCheck($parts, '<');
+
+ $phrase = trim($name);
+ $route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
+
+ if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false)
+ return false;
+
+ // Only got addr-spec
+ } else {
+ // First snip angle brackets if present.
+ if (substr($mailbox,0,1) == '<' && substr($mailbox,-1) == '>')
+ $addr_spec = substr($mailbox,1,-1);
+ else
+ $addr_spec = $mailbox;
+
+ if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false)
+ return false;
+ }
+
+ // Construct the object that will be returned.
+ $mbox = new stdClass();
+
+ // Add the phrase (even if empty) and comments
+ $mbox->personal = $phrase;
+ $mbox->comment = isset($comments) ? $comments : array();
+
+ if (isset($route_addr)) {
+ $mbox->mailbox = $route_addr['local_part'];
+ $mbox->host = $route_addr['domain'];
+ $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
+ } else {
+ $mbox->mailbox = $addr_spec['local_part'];
+ $mbox->host = $addr_spec['domain'];
+ }
+
+ $mailbox = $mbox;
+ return true;
+ }
+
+ /**
+ * This function validates a route-addr which is:
+ * route-addr = "<" [route] addr-spec ">"
+ *
+ * Angle brackets have already been removed at the point of
+ * getting to this function.
+ *
+ * @access private
+ * @param string $route_addr The string to check.
+ * @return mixed False on failure, or an array containing validated address/route information on success.
+ */
+ function _validateRouteAddr($route_addr)
+ {
+ // Check for colon.
+ if (strpos($route_addr, ':') !== false) {
+ $parts = explode(':', $route_addr);
+ $route = $this->_splitCheck($parts, ':');
+ } else {
+ $route = $route_addr;
+ }
+
+ // If $route is same as $route_addr then the colon was in
+ // quotes or brackets or, of course, non existent.
+ if ($route === $route_addr){
+ unset($route);
+ $addr_spec = $route_addr;
+ if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+ return false;
+ }
+ } else {
+ // Validate route part.
+ if (($route = $this->_validateRoute($route)) === false) {
+ return false;
+ }
+
+ $addr_spec = substr($route_addr, strlen($route . ':'));
+
+ // Validate addr-spec part.
+ if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+ return false;
+ }
+ }
+
+ if (isset($route)) {
+ $return['adl'] = $route;
+ } else {
+ $return['adl'] = '';
+ }
+
+ $return = array_merge($return, $addr_spec);
+ return $return;
+ }
+
+ /**
+ * Function to validate a route, which is:
+ * route = 1#("@" domain) ":"
+ *
+ * @access private
+ * @param string $route The string to check.
+ * @return mixed False on failure, or the validated $route on success.
+ */
+ function _validateRoute($route)
+ {
+ // Split on comma.
+ $domains = explode(',', trim($route));
+
+ for ($i = 0; $i < count($domains); $i++) {
+ $domains[$i] = str_replace('@', '', trim($domains[$i]));
+ if (!$this->_validateDomain($domains[$i])) return false;
+ }
+
+ return $route;
+ }
+
+ /**
+ * Function to validate a domain, though this is not quite what
+ * you expect of a strict internet domain.
+ *
+ * domain = sub-domain *("." sub-domain)
+ *
+ * @access private
+ * @param string $domain The string to check.
+ * @return mixed False on failure, or the validated domain on success.
+ */
+ function _validateDomain($domain)
+ {
+ // Note the different use of $subdomains and $sub_domains
+ $subdomains = explode('.', $domain);
+
+ while (count($subdomains) > 0) {
+ $sub_domains[] = $this->_splitCheck($subdomains, '.');
+ for ($i = 0; $i < $this->index + 1; $i++)
+ array_shift($subdomains);
+ }
+
+ for ($i = 0; $i < count($sub_domains); $i++) {
+ if (!$this->_validateSubdomain(trim($sub_domains[$i])))
+ return false;
+ }
+
+ // Managed to get here, so return input.
+ return $domain;
+ }
+
+ /**
+ * Function to validate a subdomain:
+ * subdomain = domain-ref / domain-literal
+ *
+ * @access private
+ * @param string $subdomain The string to check.
+ * @return boolean Success or failure.
+ */
+ function _validateSubdomain($subdomain)
+ {
+ if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
+ if (!$this->_validateDliteral($arr[1])) return false;
+ } else {
+ if (!$this->_validateAtom($subdomain)) return false;
+ }
+
+ // Got here, so return successful.
+ return true;
+ }
+
+ /**
+ * Function to validate a domain literal:
+ * domain-literal = "[" *(dtext / quoted-pair) "]"
+ *
+ * @access private
+ * @param string $dliteral The string to check.
+ * @return boolean Success or failure.
+ */
+ function _validateDliteral($dliteral)
+ {
+ return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
+ }
+
+ /**
+ * Function to validate an addr-spec.
+ *
+ * addr-spec = local-part "@" domain
+ *
+ * @access private
+ * @param string $addr_spec The string to check.
+ * @return mixed False on failure, or the validated addr-spec on success.
+ */
+ function _validateAddrSpec($addr_spec)
+ {
+ $addr_spec = trim($addr_spec);
+
+ // Split on @ sign if there is one.
+ if (strpos($addr_spec, '@') !== false) {
+ $parts = explode('@', $addr_spec);
+ $local_part = $this->_splitCheck($parts, '@');
+ $domain = substr($addr_spec, strlen($local_part . '@'));
+
+ // No @ sign so assume the default domain.
+ } else {
+ $local_part = $addr_spec;
+ $domain = $this->default_domain;
+ }
+
+ if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
+ if (($domain = $this->_validateDomain($domain)) === false) return false;
+
+ // Got here so return successful.
+ return array('local_part' => $local_part, 'domain' => $domain);
+ }
+
+ /**
+ * Function to validate the local part of an address:
+ * local-part = word *("." word)
+ *
+ * @access private
+ * @param string $local_part
+ * @return mixed False on failure, or the validated local part on success.
+ */
+ function _validateLocalPart($local_part)
+ {
+ $parts = explode('.', $local_part);
+
+ // Split the local_part into words.
+ while (count($parts) > 0){
+ $words[] = $this->_splitCheck($parts, '.');
+ for ($i = 0; $i < $this->index + 1; $i++) {
+ array_shift($parts);
+ }
+ }
+
+ // Validate each word.
+ for ($i = 0; $i < count($words); $i++) {
+ if ($this->_validatePhrase(trim($words[$i])) === false) return false;
+ }
+
+ // Managed to get here, so return the input.
+ return $local_part;
+ }
+
+ /**
+ * Returns an approximate count of how many addresses are
+ * in the given string. This is APPROXIMATE as it only splits
+ * based on a comma which has no preceding backslash. Could be
+ * useful as large amounts of addresses will end up producing
+ * *large* structures when used with parseAddressList().
+ *
+ * @param string $data Addresses to count
+ * @return int Approximate count
+ */
+ function approximateCount($data)
+ {
+ return count(preg_split('/(?<!\\\\),/', $data));
+ }
+
+ /**
+ * This is a email validating function seperate to the rest
+ * of the class. It simply validates whether an email is of
+ * the common internet form: <user>@<domain>. This can be
+ * sufficient for most people. Optional stricter mode can
+ * be utilised which restricts mailbox characters allowed
+ * to alphanumeric, full stop, hyphen and underscore.
+ *
+ * @param string $data Address to check
+ * @param boolean $strict Optional stricter mode
+ * @return mixed False if it fails, an indexed array
+ * username/domain if it matches
+ */
+ function isValidInetAddress($data, $strict = false)
+ {
+ $regex = $strict ? '/^([.0-9a-z_-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,4})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,4})$/i';
+ if (preg_match($regex, trim($data), $matches)) {
+ return array($matches[1], $matches[2]);
+ } else {
+ return false;
+ }
+ }
+}
+
+?>
diff --git a/function/mimePart.php b/function/mimePart.php
new file mode 100644
index 0000000..120df42
--- /dev/null
+++ b/function/mimePart.php
@@ -0,0 +1,333 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Richard Heyes <richard@phpguru.org> |
+// +----------------------------------------------------------------------+
+
+/**
+*
+* Raw mime encoding class
+*
+* What is it?
+* This class enables you to manipulate and build
+* a mime email from the ground up.
+*
+* Why use this instead of mime.php?
+* mime.php is a userfriendly api to this class for
+* people who aren't interested in the internals of
+* mime mail. This class however allows full control
+* over the email.
+*
+* Eg.
+*
+* // Since multipart/mixed has no real body, (the body is
+* // the subpart), we set the body argument to blank.
+*
+* $params['content_type'] = 'multipart/mixed';
+* $email = new Mail_mimePart('', $params);
+*
+* // Here we add a text part to the multipart we have
+* // already. Assume $body contains plain text.
+*
+* $params['content_type'] = 'text/plain';
+* $params['encoding'] = '7bit';
+* $text = $email->addSubPart($body, $params);
+*
+* // Now add an attachment. Assume $attach is
+* the contents of the attachment
+*
+* $params['content_type'] = 'application/zip';
+* $params['encoding'] = 'base64';
+* $params['disposition'] = 'attachment';
+* $params['dfilename'] = 'example.zip';
+* $attach =& $email->addSubPart($body, $params);
+*
+* // Now build the email. Note that the encode
+* // function returns an associative array containing two
+* // elements, body and headers. You will need to add extra
+* // headers, (eg. Mime-Version) before sending.
+*
+* $email = $message->encode();
+* $email['headers'][] = 'Mime-Version: 1.0';
+*
+*
+* Further examples are available at http://www.phpguru.org
+*
+* TODO:
+* - Set encode() to return the $obj->encoded if encode()
+* has already been run. Unless a flag is passed to specifically
+* re-build the message.
+*
+* @author Richard Heyes <richard@phpguru.org>
+* @version $Revision: 1.3 $
+* @package Mail
+*/
+
+class Mail_mimePart {
+
+ /**
+ * The encoding type of this part
+ * @var string
+ */
+ var $_encoding;
+
+ /**
+ * An array of subparts
+ * @var array
+ */
+ var $_subparts;
+
+ /**
+ * The output of this part after being built
+ * @var string
+ */
+ var $_encoded;
+
+ /**
+ * Headers for this part
+ * @var array
+ */
+ var $_headers;
+
+ /**
+ * The body of this part (not encoded)
+ * @var string
+ */
+ var $_body;
+
+ /**
+ * Constructor.
+ *
+ * Sets up the object.
+ *
+ * @param $body - The body of the mime part if any.
+ * @param $params - An associative array of parameters:
+ * content_type - The content type for this part eg multipart/mixed
+ * encoding - The encoding to use, 7bit, 8bit, base64, or quoted-printable
+ * cid - Content ID to apply
+ * disposition - Content disposition, inline or attachment
+ * dfilename - Optional filename parameter for content disposition
+ * description - Content description
+ * charset - Character set to use
+ * @access public
+ */
+ function __construct($body = '', $params = array())
+ {
+ if (!defined('MAIL_MIMEPART_CRLF')) {
+ define('MAIL_MIMEPART_CRLF', defined('MAIL_MIME_CRLF') ? MAIL_MIME_CRLF : "\r\n", TRUE);
+ }
+
+ foreach ($params as $key => $value) {
+ switch ($key) {
+ case 'content_type':
+ $headers['Content-Type'] = $value . (isset($charset) ? '; charset="' . $charset . '"' : '');
+ break;
+
+ case 'encoding':
+ $this->_encoding = $value;
+ $headers['Content-Transfer-Encoding'] = $value;
+ break;
+
+ case 'cid':
+ $headers['Content-ID'] = '<' . $value . '>';
+ break;
+
+ case 'disposition':
+ $headers['Content-Disposition'] = $value . (isset($dfilename) ? '; filename="' . $dfilename . '"' : '');
+ break;
+
+ case 'dfilename':
+ if (isset($headers['Content-Disposition'])) {
+ $headers['Content-Disposition'] .= '; filename="' . $value . '"';
+ } else {
+ $dfilename = $value;
+ }
+ break;
+
+ case 'description':
+ $headers['Content-Description'] = $value;
+ break;
+
+ case 'charset':
+ if (isset($headers['Content-Type'])) {
+ $headers['Content-Type'] .= '; charset="' . $value . '"';
+ } else {
+ $charset = $value;
+ }
+ break;
+ }
+ }
+
+ // Default content-type
+ if (!isset($headers['Content-Type'])) {
+ $headers['Content-Type'] = 'text/plain';
+ }
+
+ //Default encoding
+ if (!isset($this->_encoding)) {
+ $this->_encoding = '7bit';
+ }
+
+ // Assign stuff to member variables
+ $this->_encoded = array();
+ $this->_headers = $headers;
+ $this->_body = $body;
+ }
+
+ /**
+ * encode()
+ *
+ * Encodes and returns the email. Also stores
+ * it in the encoded member variable
+ *
+ * @return An associative array containing two elements,
+ * body and headers. The headers element is itself
+ * an indexed array.
+ * @access public
+ */
+ function encode()
+ {
+ $encoded =& $this->_encoded;
+
+ if (!empty($this->_subparts)) {
+ srand((double)microtime()*1000000);
+ $boundary = '=_' . md5(uniqid(rand()) . microtime());
+ $this->_headers['Content-Type'] .= ';' . MAIL_MIMEPART_CRLF . "\t" . 'boundary="' . $boundary . '"';
+
+ // Add body parts to $subparts
+ for ($i = 0; $i < count($this->_subparts); $i++) {
+ $headers = array();
+ $tmp = $this->_subparts[$i]->encode();
+ foreach ($tmp['headers'] as $key => $value) {
+ $headers[] = $key . ': ' . $value;
+ }
+ $subparts[] = implode(MAIL_MIMEPART_CRLF, $headers) . MAIL_MIMEPART_CRLF . MAIL_MIMEPART_CRLF . $tmp['body'];
+ }
+
+ $encoded['body'] = '--' . $boundary . MAIL_MIMEPART_CRLF .
+ implode('--' . $boundary . MAIL_MIMEPART_CRLF, $subparts) .
+ '--' . $boundary.'--' . MAIL_MIMEPART_CRLF;
+
+ } else {
+ $encoded['body'] = $this->_getEncodedData($this->_body, $this->_encoding) . MAIL_MIMEPART_CRLF;
+ }
+
+ // Add headers to $encoded
+ $encoded['headers'] =& $this->_headers;
+
+ return $encoded;
+ }
+
+ /**
+ * &addSubPart()
+ *
+ * Adds a subpart to current mime part and returns
+ * a reference to it
+ *
+ * @param $body The body of the subpart, if any.
+ * @param $params The parameters for the subpart, same
+ * as the $params argument for constructor.
+ * @return A reference to the part you just added. It is
+ * crucial if using multipart/* in your subparts that
+ * you use =& in your script when calling this function,
+ * otherwise you will not be able to add further subparts.
+ * @access public
+ */
+ function &addSubPart($body, $params)
+ {
+ $this->_subparts[] = new Mail_mimePart($body, $params);
+ return $this->_subparts[count($this->_subparts) - 1];
+ }
+
+ /**
+ * _getEncodedData()
+ *
+ * Returns encoded data based upon encoding passed to it
+ *
+ * @param $data The data to encode.
+ * @param $encoding The encoding type to use, 7bit, base64,
+ * or quoted-printable.
+ * @access private
+ */
+ function _getEncodedData($data, $encoding)
+ {
+ switch ($encoding) {
+ case '8bit':
+ case '7bit':
+ return $data;
+ break;
+
+ case 'quoted-printable':
+ return $this->_quotedPrintableEncode($data);
+ break;
+
+ case 'base64':
+ return rtrim(chunk_split(base64_encode($data), 76, MAIL_MIMEPART_CRLF));
+ break;
+
+ default:
+ return $data;
+ }
+ }
+
+ /**
+ * quoteadPrintableEncode()
+ *
+ * Encodes data to quoted-printable standard.
+ *
+ * @param $input The data to encode
+ * @param $line_max Optional max line length. Should
+ * not be more than 76 chars
+ *
+ * @access private
+ */
+ function _quotedPrintableEncode($input , $line_max = 76)
+ {
+ $lines = preg_split("/\r?\n/", $input);
+ $eol = MAIL_MIMEPART_CRLF;
+ $escape = '=';
+ $output = '';
+
+ while(list(, $line) = each($lines)){
+
+ $linlen = strlen($line);
+ $newline = '';
+
+ for ($i = 0; $i < $linlen; $i++) {
+ $char = substr($line, $i, 1);
+ $dec = ord($char);
+
+ if (($dec == 32) AND ($i == ($linlen - 1))){ // convert space at eol only
+ $char = '=20';
+
+ } elseif($dec == 9) {
+ ; // Do nothing if a tab.
+ } elseif(($dec == 61) OR ($dec < 32 ) OR ($dec > 126)) {
+ $char = $escape . strtoupper(sprintf('%02s', dechex($dec)));
+ }
+
+ if ((strlen($newline) + strlen($char)) >= $line_max) { // MAIL_MIMEPART_CRLF is not counted
+ $output .= $newline . $escape . $eol; // soft line break; " =\r\n" is okay
+ $newline = '';
+ }
+ $newline .= $char;
+ } // end of for
+ $output .= $newline . $eol;
+ }
+ $output = substr($output, 0, -1 * strlen($eol)); // Don't want last crlf
+ return $output;
+ }
+} // End of class
+?>
diff --git a/function/smtp.php b/function/smtp.php
new file mode 100644
index 0000000..1d420b0
--- /dev/null
+++ b/function/smtp.php
@@ -0,0 +1,361 @@
+<?php
+/**
+* Filename.......: class.smtp.inc
+* Project........: SMTP Class
+* Version........: 1.0.5
+* Last Modified..: 21 December 2001
+*/
+
+ define('SMTP_STATUS_NOT_CONNECTED', 1, TRUE);
+ define('SMTP_STATUS_CONNECTED', 2, TRUE);
+
+ class smtp{
+
+ var $authenticated;
+ var $connection;
+ var $recipients;
+ var $headers;
+ var $timeout;
+ var $errors;
+ var $status;
+ var $body;
+ var $from;
+ var $host;
+ var $port;
+ var $helo;
+ var $auth;
+ var $user;
+ var $pass;
+
+ /**
+ * Constructor function. Arguments:
+ * $params - An assoc array of parameters:
+ *
+ * host - The hostname of the smtp server Default: localhost
+ * port - The port the smtp server runs on Default: 25
+ * helo - What to send as the HELO command Default: localhost
+ * (typically the hostname of the
+ * machine this script runs on)
+ * auth - Whether to use basic authentication Default: FALSE
+ * user - Username for authentication Default: <blank>
+ * pass - Password for authentication Default: <blank>
+ * timeout - The timeout in seconds for the call Default: 5
+ * to fsockopen()
+ */
+
+ function __construct($params = array()){
+
+ if(!defined('CRLF'))
+ define('CRLF', "\r\n", TRUE);
+
+ $this->authenticated = FALSE;
+ $this->timeout = 5;
+ $this->status = SMTP_STATUS_NOT_CONNECTED;
+ $this->host = 'localhost';
+ $this->port = 25;
+ $this->helo = 'localhost';
+ $this->auth = FALSE;
+ $this->user = '';
+ $this->pass = '';
+ $this->errors = array();
+
+ foreach($params as $key => $value){
+ $this->$key = $value;
+ }
+ }
+
+ /**
+ * Connect function. This will, when called
+ * statically, create a new smtp object,
+ * call the connect function (ie this function)
+ * and return it. When not called statically,
+ * it will connect to the server and send
+ * the HELO command.
+ */
+
+ function &connect($params = array()){
+
+ if(!isset($this->status)){
+ $obj = new smtp($params);
+ if($obj->connect()){
+ $obj->status = SMTP_STATUS_CONNECTED;
+ }
+
+ return $obj;
+
+ }else{
+ $this->connection = fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
+ if(function_exists('socket_set_timeout')){
+ @socket_set_timeout($this->connection, 5, 0);
+ }
+
+ $greeting = $this->get_data();
+ if(is_resource($this->connection)){
+ $this->status = SMTP_STATUS_CONNECTED;
+
+ return $this->auth ? $this->ehlo() : $this->helo();
+ }else{
+ $this->errors[] = 'Failed to connect to server: '.$errstr;
+ return FALSE;
+ }
+ }
+ }
+
+ /**
+ * Function which handles sending the mail.
+ * Arguments:
+ * $params - Optional assoc array of parameters.
+ * Can contain:
+ * recipients - Indexed array of recipients
+ * from - The from address. (used in MAIL FROM:),
+ * this will be the return path
+ * headers - Indexed array of headers, one header per array entry
+ * body - The body of the email
+ * It can also contain any of the parameters from the connect()
+ * function
+ */
+
+ function send($params = array()){
+
+ foreach($params as $key => $value){
+ $this->set($key, $value);
+ }
+
+ if($this->is_connected()){
+
+ // Do we auth or not? Note the distinction between the auth variable and auth() function
+ if($this->auth AND !$this->authenticated){
+ if(!$this->auth())
+ return FALSE;
+ }
+
+ $this->mail($this->from);
+ if(is_array($this->recipients))
+ foreach($this->recipients as $value)
+ $this->rcpt($value);
+ else
+ $this->rcpt($this->recipients);
+
+ if(!$this->data())
+ return FALSE;
+
+ // Transparency
+ $headers = str_replace(CRLF.'.', CRLF.'..', trim(implode(CRLF, $this->headers)));
+ $body = str_replace(CRLF.'.', CRLF.'..', $this->body);
+ $body = $body[0] == '.' ? '.'.$body : $body;
+
+ $this->send_data($headers);
+ $this->send_data('');
+ $this->send_data($body);
+ $this->send_data('.');
+
+ $result = (substr(trim($this->get_data()), 0, 3) === '250');
+ //$this->rset();
+ return $result;
+ }else{
+ $this->errors[] = 'Not connected!';
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function to implement HELO cmd
+ */
+
+ function helo(){
+ if(is_resource($this->connection)
+ AND $this->send_data('HELO '.$this->helo)
+ AND substr(trim($error = $this->get_data()), 0, 3) === '250' ){
+
+ return TRUE;
+
+ }else{
+ $this->errors[] = 'HELO command failed, output: ' . trim(substr(trim($error),3));
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function to implement EHLO cmd
+ */
+
+ function ehlo(){
+ if(is_resource($this->connection)
+ AND $this->send_data('EHLO '.$this->helo)
+ AND substr(trim($error = $this->get_data()), 0, 3) === '250' ){
+
+ return TRUE;
+
+ }else{
+ $this->errors[] = 'EHLO command failed, output: ' . trim(substr(trim($error),3));
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function to implement RSET cmd
+ */
+
+ function rset(){
+ if(is_resource($this->connection)
+ AND $this->send_data('RSET')
+ AND substr(trim($error = $this->get_data()), 0, 3) === '250' ){
+
+ return TRUE;
+
+ }else{
+ $this->errors[] = 'RSET command failed, output: ' . trim(substr(trim($error),3));
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function to implement QUIT cmd
+ */
+
+ function quit(){
+ if(is_resource($this->connection)
+ AND $this->send_data('QUIT')
+ AND substr(trim($error = $this->get_data()), 0, 3) === '221' ){
+
+ fclose($this->connection);
+ $this->status = SMTP_STATUS_NOT_CONNECTED;
+ return TRUE;
+
+ }else{
+ $this->errors[] = 'QUIT command failed, output: ' . trim(substr(trim($error),3));
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function to implement AUTH cmd
+ */
+
+ function auth(){
+ if(is_resource($this->connection)
+ AND $this->send_data('AUTH LOGIN')
+ AND substr(trim($error = $this->get_data()), 0, 3) === '334'
+ AND $this->send_data(base64_encode($this->user)) // Send username
+ AND substr(trim($error = $this->get_data()),0,3) === '334'
+ AND $this->send_data(base64_encode($this->pass)) // Send password
+ AND substr(trim($error = $this->get_data()),0,3) === '235' ){
+
+ $this->authenticated = TRUE;
+ return TRUE;
+
+ }else{
+ $this->errors[] = 'AUTH command failed: ' . trim(substr(trim($error),3));
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function that handles the MAIL FROM: cmd
+ */
+
+ function mail($from){
+
+ if($this->is_connected()
+ AND $this->send_data('MAIL FROM:<'.$from.'>')
+ AND substr(trim($this->get_data()), 0, 2) === '250' ){
+
+ return TRUE;
+
+ }else
+ return FALSE;
+ }
+
+ /**
+ * Function that handles the RCPT TO: cmd
+ */
+
+ function rcpt($to){
+
+ if($this->is_connected()
+ AND $this->send_data('RCPT TO:<'.$to.'>')
+ AND substr(trim($error = $this->get_data()), 0, 2) === '25' ){
+
+ return TRUE;
+
+ }else{
+ $this->errors[] = trim(substr(trim($error), 3));
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function that sends the DATA cmd
+ */
+
+ function data(){
+
+ if($this->is_connected()
+ AND $this->send_data('DATA')
+ AND substr(trim($error = $this->get_data()), 0, 3) === '354' ){
+
+ return TRUE;
+
+ }else{
+ $this->errors[] = trim(substr(trim($error), 3));
+ return FALSE;
+ }
+ }
+
+ /**
+ * Function to determine if this object
+ * is connected to the server or not.
+ */
+
+ function is_connected(){
+
+ return (is_resource($this->connection) AND ($this->status === SMTP_STATUS_CONNECTED));
+ }
+
+ /**
+ * Function to send a bit of data
+ */
+
+ function send_data($data){
+
+ if(is_resource($this->connection)){
+ return fwrite($this->connection, $data.CRLF, strlen($data)+2);
+
+ }else
+ return FALSE;
+ }
+
+ /**
+ * Function to get data.
+ */
+
+ function &get_data(){
+
+ $return = '';
+ $line = '';
+ $loops = 0;
+
+ if(is_resource($this->connection)){
+ while((strpos($return, CRLF) === FALSE OR substr($line,3,1) !== ' ') AND $loops < 100){
+ $line = fgets($this->connection, 512);
+ $return .= $line;
+ $loops++;
+ }
+ return $return;
+
+ }else
+ return FALSE;
+ }
+
+ /**
+ * Sets a variable
+ */
+
+ function set($var, $value){
+
+ $this->$var = $value;
+ return TRUE;
+ }
+
+ } // End of class
+?> \ No newline at end of file
diff --git a/function/thumb.php b/function/thumb.php
new file mode 100644
index 0000000..8eb6bb8
--- /dev/null
+++ b/function/thumb.php
@@ -0,0 +1,1244 @@
+<?php
+/**
+ * TimThumb by Ben Gillbanks and Mark Maunder
+ * Based on work done by Tim McDaniels and Darren Hoyt
+ * http://code.google.com/p/timthumb/
+ *
+ * GNU General Public License, version 2
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ *
+ * Examples and documentation available on the project homepage
+ * http://www.binarymoon.co.uk/projects/timthumb/
+ *
+ * $Rev$
+ */
+
+/*
+ * --- TimThumb CONFIGURATION ---
+ * To edit the configs it is best to create a file called timthumb-config.php
+ * and define variables you want to customize in there. It will automatically be
+ * loaded by timthumb. This will save you having to re-edit these variables
+ * everytime you download a new version
+*/
+define ('VERSION', '2.8.10'); // Version of this script
+//Load a config file if it exists. Otherwise, use the values below
+if( file_exists(dirname(__FILE__) . '/timthumb-config.php')) require_once('timthumb-config.php');
+if(! defined('DEBUG_ON') ) define ('DEBUG_ON', false); // Enable debug logging to web server error log (STDERR)
+if(! defined('DEBUG_LEVEL') ) define ('DEBUG_LEVEL', 1); // Debug level 1 is less noisy and 3 is the most noisy
+if(! defined('MEMORY_LIMIT') ) define ('MEMORY_LIMIT', '30M'); // Set PHP memory limit
+if(! defined('BLOCK_EXTERNAL_LEECHERS') ) define ('BLOCK_EXTERNAL_LEECHERS', false); // If the image or webshot is being loaded on an external site, display a red "No Hotlinking" gif.
+
+//Image fetching and caching
+if(! defined('ALLOW_EXTERNAL') ) define ('ALLOW_EXTERNAL', false); // Allow image fetching from external websites. Will check against ALLOWED_SITES if ALLOW_ALL_EXTERNAL_SITES is false
+if(! defined('ALLOW_ALL_EXTERNAL_SITES') ) define ('ALLOW_ALL_EXTERNAL_SITES', false); // Less secure.
+if(! defined('FILE_CACHE_ENABLED') ) define ('FILE_CACHE_ENABLED', TRUE); // Should we store resized/modified images on disk to speed things up?
+if(! defined('FILE_CACHE_TIME_BETWEEN_CLEANS')) define ('FILE_CACHE_TIME_BETWEEN_CLEANS', 86400); // How often the cache is cleaned
+
+if(! defined('FILE_CACHE_MAX_FILE_AGE') ) define ('FILE_CACHE_MAX_FILE_AGE', 86400); // How old does a file have to be to be deleted from the cache
+if(! defined('FILE_CACHE_SUFFIX') ) define ('FILE_CACHE_SUFFIX', '.timthumb.txt'); // What to put at the end of all files in the cache directory so we can identify them
+if(! defined('FILE_CACHE_PREFIX') ) define ('FILE_CACHE_PREFIX', 'timthumb'); // What to put at the beg of all files in the cache directory so we can identify them
+if(! defined('FILE_CACHE_DIRECTORY') ) define ('FILE_CACHE_DIRECTORY', './cache'); // Directory where images are cached. Left blank it will use the system temporary directory (which is better for security)
+if(! defined('MAX_FILE_SIZE') ) define ('MAX_FILE_SIZE', 10485760); // 10 Megs is 10485760. This is the max internal or external file size that we'll process.
+if(! defined('CURL_TIMEOUT') ) define ('CURL_TIMEOUT', 20); // Timeout duration for Curl. This only applies if you have Curl installed and aren't using PHP's default URL fetching mechanism.
+if(! defined('WAIT_BETWEEN_FETCH_ERRORS') ) define ('WAIT_BETWEEN_FETCH_ERRORS', 3600); //Time to wait between errors fetching remote file
+
+//Browser caching
+if(! defined('BROWSER_CACHE_MAX_AGE') ) define ('BROWSER_CACHE_MAX_AGE', 864000); // Time to cache in the browser
+if(! defined('BROWSER_CACHE_DISABLE') ) define ('BROWSER_CACHE_DISABLE', false); // Use for testing if you want to disable all browser caching
+
+//Image size and defaults
+if(! defined('MAX_WIDTH') ) define ('MAX_WIDTH', 1500); // Maximum image width
+if(! defined('MAX_HEIGHT') ) define ('MAX_HEIGHT', 1500); // Maximum image height
+if(! defined('NOT_FOUND_IMAGE') ) define ('NOT_FOUND_IMAGE', ''); // Image to serve if any 404 occurs
+if(! defined('ERROR_IMAGE') ) define ('ERROR_IMAGE', ''); // Image to serve if an error occurs instead of showing error message
+if(! defined('PNG_IS_TRANSPARENT') ) define ('PNG_IS_TRANSPARENT', FALSE); //42 Define if a png image should have a transparent background color. Use False value if you want to display a custom coloured canvas_colour
+if(! defined('DEFAULT_Q') ) define ('DEFAULT_Q', 90); // Default image quality. Allows overrid in timthumb-config.php
+if(! defined('DEFAULT_ZC') ) define ('DEFAULT_ZC', 1); // Default zoom/crop setting. Allows overrid in timthumb-config.php
+if(! defined('DEFAULT_F') ) define ('DEFAULT_F', ''); // Default image filters. Allows overrid in timthumb-config.php
+if(! defined('DEFAULT_S') ) define ('DEFAULT_S', 0); // Default sharpen value. Allows overrid in timthumb-config.php
+if(! defined('DEFAULT_CC') ) define ('DEFAULT_CC', 'ffffff'); // Default canvas colour. Allows overrid in timthumb-config.php
+
+
+//Image compression is enabled if either of these point to valid paths
+
+//These are now disabled by default because the file sizes of PNGs (and GIFs) are much smaller than we used to generate.
+//They only work for PNGs. GIFs and JPEGs are not affected.
+if(! defined('OPTIPNG_ENABLED') ) define ('OPTIPNG_ENABLED', false);
+if(! defined('OPTIPNG_PATH') ) define ('OPTIPNG_PATH', '/usr/bin/optipng'); //This will run first because it gives better compression than pngcrush.
+if(! defined('PNGCRUSH_ENABLED') ) define ('PNGCRUSH_ENABLED', false);
+if(! defined('PNGCRUSH_PATH') ) define ('PNGCRUSH_PATH', '/usr/bin/pngcrush'); //This will only run if OPTIPNG_PATH is not set or is not valid
+
+/*
+ -------====Website Screenshots configuration - BETA====-------
+
+ If you just want image thumbnails and don't want website screenshots, you can safely leave this as is.
+
+ If you would like to get website screenshots set up, you will need root access to your own server.
+
+ Enable ALLOW_ALL_EXTERNAL_SITES so you can fetch any external web page. This is more secure now that we're using a non-web folder for cache.
+ Enable BLOCK_EXTERNAL_LEECHERS so that your site doesn't generate thumbnails for the whole Internet.
+
+ Instructions to get website screenshots enabled on Ubuntu Linux:
+
+ 1. Install Xvfb with the following command: sudo apt-get install subversion libqt4-webkit libqt4-dev g++ xvfb
+ 2. Go to a directory where you can download some code
+ 3. Check-out the latest version of CutyCapt with the following command: svn co https://cutycapt.svn.sourceforge.net/svnroot/cutycapt
+ 4. Compile CutyCapt by doing: cd cutycapt/CutyCapt
+ 5. qmake
+ 6. make
+ 7. cp CutyCapt /usr/local/bin/
+ 8. Test it by running: xvfb-run --server-args="-screen 0, 1024x768x24" CutyCapt --url="http://markmaunder.com/" --out=test.png
+ 9. If you get a file called test.png with something in it, it probably worked. Now test the script by accessing it as follows:
+ 10. http://yoursite.com/path/to/timthumb.php?src=http://markmaunder.com/&webshot=1
+
+ Notes on performance:
+ The first time a webshot loads, it will take a few seconds.
+ From then on it uses the regular timthumb caching mechanism with the configurable options above
+ and loading will be very fast.
+
+ --ADVANCED USERS ONLY--
+ If you'd like a slight speedup (about 25%) and you know Linux, you can run the following command which will keep Xvfb running in the background.
+ nohup Xvfb :100 -ac -nolisten tcp -screen 0, 1024x768x24 > /dev/null 2>&1 &
+ Then set WEBSHOT_XVFB_RUNNING = true below. This will save your server having to fire off a new Xvfb server and shut it down every time a new shot is generated.
+ You will need to take responsibility for keeping Xvfb running in case it crashes. (It seems pretty stable)
+ You will also need to take responsibility for server security if you're running Xvfb as root.
+
+
+*/
+if(! defined('WEBSHOT_ENABLED') ) define ('WEBSHOT_ENABLED', false); //Beta feature. Adding webshot=1 to your query string will cause the script to return a browser screenshot rather than try to fetch an image.
+if(! defined('WEBSHOT_CUTYCAPT') ) define ('WEBSHOT_CUTYCAPT', '/usr/local/bin/CutyCapt'); //The path to CutyCapt.
+if(! defined('WEBSHOT_XVFB') ) define ('WEBSHOT_XVFB', '/usr/bin/xvfb-run'); //The path to the Xvfb server
+if(! defined('WEBSHOT_SCREEN_X') ) define ('WEBSHOT_SCREEN_X', '1024'); //1024 works ok
+if(! defined('WEBSHOT_SCREEN_Y') ) define ('WEBSHOT_SCREEN_Y', '768'); //768 works ok
+if(! defined('WEBSHOT_COLOR_DEPTH') ) define ('WEBSHOT_COLOR_DEPTH', '24'); //I haven't tested anything besides 24
+if(! defined('WEBSHOT_IMAGE_FORMAT') ) define ('WEBSHOT_IMAGE_FORMAT', 'png'); //png is about 2.5 times the size of jpg but is a LOT better quality
+if(! defined('WEBSHOT_TIMEOUT') ) define ('WEBSHOT_TIMEOUT', '20'); //Seconds to wait for a webshot
+if(! defined('WEBSHOT_USER_AGENT') ) define ('WEBSHOT_USER_AGENT', "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18"); //I hate to do this, but a non-browser robot user agent might not show what humans see. So we pretend to be Firefox
+if(! defined('WEBSHOT_JAVASCRIPT_ON') ) define ('WEBSHOT_JAVASCRIPT_ON', true); //Setting to false might give you a slight speedup and block ads. But it could cause other issues.
+if(! defined('WEBSHOT_JAVA_ON') ) define ('WEBSHOT_JAVA_ON', false); //Have only tested this as fase
+if(! defined('WEBSHOT_PLUGINS_ON') ) define ('WEBSHOT_PLUGINS_ON', true); //Enable flash and other plugins
+if(! defined('WEBSHOT_PROXY') ) define ('WEBSHOT_PROXY', ''); //In case you're behind a proxy server.
+if(! defined('WEBSHOT_XVFB_RUNNING') ) define ('WEBSHOT_XVFB_RUNNING', false); //ADVANCED: Enable this if you've got Xvfb running in the background.
+
+
+// If ALLOW_EXTERNAL is true and ALLOW_ALL_EXTERNAL_SITES is false, then external images will only be fetched from these domains and their subdomains.
+if(! isset($ALLOWED_SITES)){
+ $ALLOWED_SITES = array (
+ 'flickr.com',
+ 'staticflickr.com',
+ 'picasa.com',
+ 'img.youtube.com',
+ 'upload.wikimedia.org',
+ 'photobucket.com',
+ 'imgur.com',
+ 'imageshack.us',
+ 'tinypic.com',
+ );
+}
+// -------------------------------------------------------------
+// -------------- STOP EDITING CONFIGURATION HERE --------------
+// -------------------------------------------------------------
+
+timthumb::start();
+
+class timthumb {
+ protected $src = "";
+ protected $is404 = false;
+ protected $docRoot = "";
+ protected $lastURLError = false;
+ protected $localImage = "";
+ protected $localImageMTime = 0;
+ protected $url = false;
+ protected $myHost = "";
+ protected $isURL = false;
+ protected $cachefile = '';
+ protected $errors = array();
+ protected $toDeletes = array();
+ protected $cacheDirectory = '';
+ protected $startTime = 0;
+ protected $lastBenchTime = 0;
+ protected $cropTop = false;
+ protected $salt = "";
+ protected $fileCacheVersion = 1; //Generally if timthumb.php is modifed (upgraded) then the salt changes and all cache files are recreated. This is a backup mechanism to force regen.
+ protected $filePrependSecurityBlock = "<?php die('Execution denied!'); //"; //Designed to have three letter mime type, space, question mark and greater than symbol appended. 6 bytes total.
+ protected static $curlDataWritten = 0;
+ protected static $curlFH = false;
+ public static function start(){
+ $tim = new timthumb();
+ $tim->handleErrors();
+ $tim->securityChecks();
+ if($tim->tryBrowserCache()){
+ exit(0);
+ }
+ $tim->handleErrors();
+ if(FILE_CACHE_ENABLED && $tim->tryServerCache()){
+ exit(0);
+ }
+ $tim->handleErrors();
+ $tim->run();
+ $tim->handleErrors();
+ exit(0);
+ }
+ public function __construct(){
+ global $ALLOWED_SITES;
+ $this->startTime = microtime(true);
+ date_default_timezone_set('UTC');
+ $this->debug(1, "Starting new request from " . $this->getIP() . " to " . $_SERVER['REQUEST_URI']);
+ $this->calcDocRoot();
+ //On windows systems I'm assuming fileinode returns an empty string or a number that doesn't change. Check this.
+ $this->salt = @filemtime(__FILE__) . '-' . @fileinode(__FILE__);
+ $this->debug(3, "Salt is: " . $this->salt);
+ if(FILE_CACHE_DIRECTORY){
+ if(! is_dir(FILE_CACHE_DIRECTORY)){
+ @mkdir(FILE_CACHE_DIRECTORY);
+ if(! is_dir(FILE_CACHE_DIRECTORY)){
+ $this->error("Could not create the file cache directory.");
+ return false;
+ }
+ }
+ $this->cacheDirectory = FILE_CACHE_DIRECTORY;
+ if (!touch($this->cacheDirectory . '/index.html')) {
+ $this->error("Could not create the index.html file - to fix this create an empty file named index.html file in the cache directory.");
+ }
+ } else {
+ $this->cacheDirectory = sys_get_temp_dir();
+ }
+ //Clean the cache before we do anything because we don't want the first visitor after FILE_CACHE_TIME_BETWEEN_CLEANS expires to get a stale image.
+ $this->cleanCache();
+
+ $this->myHost = preg_replace('/^www\./i', '', $_SERVER['HTTP_HOST']);
+ $this->src = $this->param('src');
+ $this->url = parse_url($this->src);
+ $this->src = preg_replace('/https?:\/\/(?:www\.)?' . $this->myHost . '/i', '', $this->src);
+
+ if(strlen($this->src) <= 3){
+ $this->error("No image specified");
+ return false;
+ }
+ if(BLOCK_EXTERNAL_LEECHERS && array_key_exists('HTTP_REFERER', $_SERVER) && (! preg_match('/^https?:\/\/(?:www\.)?' . $this->myHost . '(?:$|\/)/i', $_SERVER['HTTP_REFERER']))){
+ // base64 encoded red image that says 'no hotlinkers'
+ // nothing to worry about! :)
+ $imgData = base64_decode("R0lGODlhUAAMAIAAAP8AAP///yH5BAAHAP8ALAAAAABQAAwAAAJpjI+py+0Po5y0OgAMjjv01YUZ\nOGplhWXfNa6JCLnWkXplrcBmW+spbwvaVr/cDyg7IoFC2KbYVC2NQ5MQ4ZNao9Ynzjl9ScNYpneb\nDULB3RP6JuPuaGfuuV4fumf8PuvqFyhYtjdoeFgAADs=");
+ header('Content-Type: image/gif');
+ header('Content-Length: ' . sizeof($imgData));
+ header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
+ header("Pragma: no-cache");
+ header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
+ echo $imgData;
+ return false;
+ exit(0);
+ }
+ if(preg_match('/^https?:\/\/[^\/]+/i', $this->src)){
+ $this->debug(2, "Is a request for an external URL: " . $this->src);
+ $this->isURL = true;
+ } else {
+ $this->debug(2, "Is a request for an internal file: " . $this->src);
+ }
+ if($this->isURL && (! ALLOW_EXTERNAL)){
+ $this->error("You are not allowed to fetch images from an external website.");
+ return false;
+ }
+ if($this->isURL){
+ if(ALLOW_ALL_EXTERNAL_SITES){
+ $this->debug(2, "Fetching from all external sites is enabled.");
+ } else {
+ $this->debug(2, "Fetching only from selected external sites is enabled.");
+ $allowed = false;
+ foreach($ALLOWED_SITES as $site){
+ if ((strtolower(substr($this->url['host'],-strlen($site)-1)) === strtolower(".$site")) || (strtolower($this->url['host'])===strtolower($site))) {
+ $this->debug(3, "URL hostname {$this->url['host']} matches $site so allowing.");
+ $allowed = true;
+ }
+ }
+ if(! $allowed){
+ return $this->error("You may not fetch images from that site. To enable this site in timthumb, you can either add it to \$ALLOWED_SITES and set ALLOW_EXTERNAL=true. Or you can set ALLOW_ALL_EXTERNAL_SITES=true, depending on your security needs.");
+ }
+ }
+ }
+
+ $cachePrefix = ($this->isURL ? '_ext_' : '_int_');
+ if($this->isURL){
+ $arr = explode('&', $_SERVER ['QUERY_STRING']);
+ asort($arr);
+ $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . implode('', $arr) . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
+ } else {
+ $this->localImage = $this->getLocalImagePath($this->src);
+ if(! $this->localImage){
+ $this->debug(1, "Could not find the local image: {$this->localImage}");
+ $this->error("Could not find the internal image you specified.");
+ $this->set404();
+ return false;
+ }
+ $this->debug(1, "Local image path is {$this->localImage}");
+ $this->localImageMTime = @filemtime($this->localImage);
+ //We include the mtime of the local file in case in changes on disk.
+ $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . $this->localImageMTime . $_SERVER ['QUERY_STRING'] . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
+ }
+ $this->debug(2, "Cache file is: " . $this->cachefile);
+
+ return true;
+ }
+ public function __destruct(){
+ foreach($this->toDeletes as $del){
+ $this->debug(2, "Deleting temp file $del");
+ @unlink($del);
+ }
+ }
+ public function run(){
+ if($this->isURL){
+ if(! ALLOW_EXTERNAL){
+ $this->debug(1, "Got a request for an external image but ALLOW_EXTERNAL is disabled so returning error msg.");
+ $this->error("You are not allowed to fetch images from an external website.");
+ return false;
+ }
+ $this->debug(3, "Got request for external image. Starting serveExternalImage.");
+ if($this->param('webshot')){
+ if(WEBSHOT_ENABLED){
+ $this->debug(3, "webshot param is set, so we're going to take a webshot.");
+ $this->serveWebshot();
+ } else {
+ $this->error("You added the webshot parameter but webshots are disabled on this server. You need to set WEBSHOT_ENABLED == true to enable webshots.");
+ }
+ } else {
+ $this->debug(3, "webshot is NOT set so we're going to try to fetch a regular image.");
+ $this->serveExternalImage();
+
+ }
+ } else {
+ $this->debug(3, "Got request for internal image. Starting serveInternalImage()");
+ $this->serveInternalImage();
+ }
+ return true;
+ }
+ protected function handleErrors(){
+ if($this->haveErrors()){
+ if(NOT_FOUND_IMAGE && $this->is404()){
+ if($this->serveImg(NOT_FOUND_IMAGE)){
+ exit(0);
+ } else {
+ $this->error("Additionally, the 404 image that is configured could not be found or there was an error serving it.");
+ }
+ }
+ if(ERROR_IMAGE){
+ if($this->serveImg(ERROR_IMAGE)){
+ exit(0);
+ } else {
+ $this->error("Additionally, the error image that is configured could not be found or there was an error serving it.");
+ }
+ }
+ $this->serveErrors();
+ exit(0);
+ }
+ return false;
+ }
+ protected function tryBrowserCache(){
+ if(BROWSER_CACHE_DISABLE){ $this->debug(3, "Browser caching is disabled"); return false; }
+ if(!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ){
+ $this->debug(3, "Got a conditional get");
+ $mtime = false;
+ //We've already checked if the real file exists in the constructor
+ if(! is_file($this->cachefile)){
+ //If we don't have something cached, regenerate the cached image.
+ return false;
+ }
+ if($this->localImageMTime){
+ $mtime = $this->localImageMTime;
+ $this->debug(3, "Local real file's modification time is $mtime");
+ } else if(is_file($this->cachefile)){ //If it's not a local request then use the mtime of the cached file to determine the 304
+ $mtime = @filemtime($this->cachefile);
+ $this->debug(3, "Cached file's modification time is $mtime");
+ }
+ if(! $mtime){ return false; }
+
+ $iftime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
+ $this->debug(3, "The conditional get's if-modified-since unixtime is $iftime");
+ if($iftime < 1){
+ $this->debug(3, "Got an invalid conditional get modified since time. Returning false.");
+ return false;
+ }
+ if($iftime < $mtime){ //Real file or cache file has been modified since last request, so force refetch.
+ $this->debug(3, "File has been modified since last fetch.");
+ return false;
+ } else { //Otherwise serve a 304
+ $this->debug(3, "File has not been modified since last get, so serving a 304.");
+ header ($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
+ $this->debug(1, "Returning 304 not modified");
+ return true;
+ }
+ }
+ return false;
+ }
+ protected function tryServerCache(){
+ $this->debug(3, "Trying server cache");
+ if(file_exists($this->cachefile)){
+ $this->debug(3, "Cachefile {$this->cachefile} exists");
+ if($this->isURL){
+ $this->debug(3, "This is an external request, so checking if the cachefile is empty which means the request failed previously.");
+ if(filesize($this->cachefile) < 1){
+ $this->debug(3, "Found an empty cachefile indicating a failed earlier request. Checking how old it is.");
+ //Fetching error occured previously
+ if(time() - @filemtime($this->cachefile) > WAIT_BETWEEN_FETCH_ERRORS){
+ $this->debug(3, "File is older than " . WAIT_BETWEEN_FETCH_ERRORS . " seconds. Deleting and returning false so app can try and load file.");
+ @unlink($this->cachefile);
+ return false; //to indicate we didn't serve from cache and app should try and load
+ } else {
+ $this->debug(3, "Empty cachefile is still fresh so returning message saying we had an error fetching this image from remote host.");
+ $this->set404();
+ $this->error("An error occured fetching image.");
+ return false;
+ }
+ }
+ } else {
+ $this->debug(3, "Trying to serve cachefile {$this->cachefile}");
+ }
+ if($this->serveCacheFile()){
+ $this->debug(3, "Succesfully served cachefile {$this->cachefile}");
+ return true;
+ } else {
+ $this->debug(3, "Failed to serve cachefile {$this->cachefile} - Deleting it from cache.");
+ //Image serving failed. We can't retry at this point, but lets remove it from cache so the next request recreates it
+ @unlink($this->cachefile);
+ return true;
+ }
+ }
+ }
+ protected function error($err){
+ $this->debug(3, "Adding error message: $err");
+ $this->errors[] = $err;
+ return false;
+
+ }
+ protected function haveErrors(){
+ if(sizeof($this->errors) > 0){
+ return true;
+ }
+ return false;
+ }
+ protected function serveErrors(){
+ header ($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
+ $html = '<ul>';
+ foreach($this->errors as $err){
+ $html .= '<li>' . htmlentities($err) . '</li>';
+ }
+ $html .= '</ul>';
+ echo '<h1>A TimThumb error has occured</h1>The following error(s) occured:<br />' . $html . '<br />';
+ echo '<br />Query String : ' . htmlentities ($_SERVER['QUERY_STRING']);
+ echo '<br />TimThumb version : ' . VERSION . '</pre>';
+ }
+ protected function serveInternalImage(){
+ $this->debug(3, "Local image path is $this->localImage");
+ if(! $this->localImage){
+ $this->sanityFail("localImage not set after verifying it earlier in the code.");
+ return false;
+ }
+ $fileSize = filesize($this->localImage);
+ if($fileSize > MAX_FILE_SIZE){
+ $this->error("The file you specified is greater than the maximum allowed file size.");
+ return false;
+ }
+ if($fileSize <= 0){
+ $this->error("The file you specified is <= 0 bytes.");
+ return false;
+ }
+ $this->debug(3, "Calling processImageAndWriteToCache() for local image.");
+ if($this->processImageAndWriteToCache($this->localImage)){
+ $this->serveCacheFile();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ protected function cleanCache(){
+ if (FILE_CACHE_TIME_BETWEEN_CLEANS < 0) {
+ return;
+ }
+ $this->debug(3, "cleanCache() called");
+ $lastCleanFile = $this->cacheDirectory . '/timthumb_cacheLastCleanTime.touch';
+
+ //If this is a new timthumb installation we need to create the file
+ if(! is_file($lastCleanFile)){
+ $this->debug(1, "File tracking last clean doesn't exist. Creating $lastCleanFile");
+ if (!touch($lastCleanFile)) {
+ $this->error("Could not create cache clean timestamp file.");
+ }
+ return;
+ }
+ if(@filemtime($lastCleanFile) < (time() - FILE_CACHE_TIME_BETWEEN_CLEANS) ){ //Cache was last cleaned more than 1 day ago
+ $this->debug(1, "Cache was last cleaned more than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago. Cleaning now.");
+ // Very slight race condition here, but worst case we'll have 2 or 3 servers cleaning the cache simultaneously once a day.
+ if (!touch($lastCleanFile)) {
+ $this->error("Could not create cache clean timestamp file.");
+ }
+ $files = glob($this->cacheDirectory . '/*' . FILE_CACHE_SUFFIX);
+ if ($files) {
+ $timeAgo = time() - FILE_CACHE_MAX_FILE_AGE;
+ foreach($files as $file){
+ if(@filemtime($file) < $timeAgo){
+ $this->debug(3, "Deleting cache file $file older than max age: " . FILE_CACHE_MAX_FILE_AGE . " seconds");
+ @unlink($file);
+ }
+ }
+ }
+ return true;
+ } else {
+ $this->debug(3, "Cache was cleaned less than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago so no cleaning needed.");
+ }
+ return false;
+ }
+ protected function processImageAndWriteToCache($localImage){
+ $sData = getimagesize($localImage);
+ $origType = $sData[2];
+ $mimeType = $sData['mime'];
+
+ $this->debug(3, "Mime type of image is $mimeType");
+ if(! preg_match('/^image\/(?:gif|jpg|jpeg|png)$/i', $mimeType)){
+ return $this->error("The image being resized is not a valid gif, jpg or png.");
+ }
+
+ if (!function_exists ('imagecreatetruecolor')) {
+ return $this->error('GD Library Error: imagecreatetruecolor does not exist - please contact your webhost and ask them to install the GD library');
+ }
+
+ if (function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
+ $imageFilters = array (
+ 1 => array (IMG_FILTER_NEGATE, 0),
+ 2 => array (IMG_FILTER_GRAYSCALE, 0),
+ 3 => array (IMG_FILTER_BRIGHTNESS, 1),
+ 4 => array (IMG_FILTER_CONTRAST, 1),
+ 5 => array (IMG_FILTER_COLORIZE, 4),
+ 6 => array (IMG_FILTER_EDGEDETECT, 0),
+ 7 => array (IMG_FILTER_EMBOSS, 0),
+ 8 => array (IMG_FILTER_GAUSSIAN_BLUR, 0),
+ 9 => array (IMG_FILTER_SELECTIVE_BLUR, 0),
+ 10 => array (IMG_FILTER_MEAN_REMOVAL, 0),
+ 11 => array (IMG_FILTER_SMOOTH, 0),
+ );
+ }
+
+ // get standard input properties
+ $new_width = (int) abs ($this->param('w', 0));
+ $new_height = (int) abs ($this->param('h', 0));
+ $zoom_crop = (int) $this->param('zc', DEFAULT_ZC);
+ $quality = (int) abs ($this->param('q', DEFAULT_Q));
+ $align = $this->cropTop ? 't' : $this->param('a', 'c');
+ $filters = $this->param('f', DEFAULT_F);
+ $sharpen = (bool) $this->param('s', DEFAULT_S);
+ $canvas_color = $this->param('cc', DEFAULT_CC);
+ $canvas_trans = (bool) $this->param('ct', '1');
+
+ // set default width and height if neither are set already
+ if ($new_width == 0 && $new_height == 0) {
+ $new_width = 100;
+ $new_height = 100;
+ }
+
+ // ensure size limits can not be abused
+ $new_width = min ($new_width, MAX_WIDTH);
+ $new_height = min ($new_height, MAX_HEIGHT);
+
+ // set memory limit to be able to have enough space to resize larger images
+ $this->setMemoryLimit();
+
+ // open the existing image
+ $image = $this->openImage ($mimeType, $localImage);
+ if ($image === false) {
+ return $this->error('Unable to open image.');
+ }
+
+ // Get original width and height
+ $width = imagesx ($image);
+ $height = imagesy ($image);
+ $origin_x = 0;
+ $origin_y = 0;
+
+ // generate new w/h if not provided
+ if ($new_width && !$new_height) {
+ $new_height = floor ($height * ($new_width / $width));
+ } else if ($new_height && !$new_width) {
+ $new_width = floor ($width * ($new_height / $height));
+ }
+
+ // scale down and add borders
+ if ($zoom_crop == 3) {
+
+ $final_height = $height * ($new_width / $width);
+
+ if ($final_height > $new_height) {
+ $new_width = $width * ($new_height / $height);
+ } else {
+ $new_height = $final_height;
+ }
+
+ }
+
+ // create a new true color image
+ $canvas = imagecreatetruecolor ($new_width, $new_height);
+ imagealphablending ($canvas, false);
+
+ if (strlen($canvas_color) == 3) { //if is 3-char notation, edit string into 6-char notation
+ $canvas_color = str_repeat(substr($canvas_color, 0, 1), 2) . str_repeat(substr($canvas_color, 1, 1), 2) . str_repeat(substr($canvas_color, 2, 1), 2);
+ } else if (strlen($canvas_color) != 6) {
+ $canvas_color = DEFAULT_CC; // on error return default canvas color
+ }
+
+ $canvas_color_R = hexdec (substr ($canvas_color, 0, 2));
+ $canvas_color_G = hexdec (substr ($canvas_color, 2, 2));
+ $canvas_color_B = hexdec (substr ($canvas_color, 4, 2));
+
+ // Create a new transparent color for image
+ // If is a png and PNG_IS_TRANSPARENT is false then remove the alpha transparency
+ // (and if is set a canvas color show it in the background)
+ if(preg_match('/^image\/png$/i', $mimeType) && !PNG_IS_TRANSPARENT && $canvas_trans){
+ $color = imagecolorallocatealpha ($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 127);
+ }else{
+ $color = imagecolorallocatealpha ($canvas, $canvas_color_R, $canvas_color_G, $canvas_color_B, 0);
+ }
+
+
+ // Completely fill the background of the new image with allocated color.
+ imagefill ($canvas, 0, 0, $color);
+
+ // scale down and add borders
+ if ($zoom_crop == 2) {
+
+ $final_height = $height * ($new_width / $width);
+
+ if ($final_height > $new_height) {
+
+ $origin_x = $new_width / 2;
+ $new_width = $width * ($new_height / $height);
+ $origin_x = round ($origin_x - ($new_width / 2));
+
+ } else {
+
+ $origin_y = $new_height / 2;
+ $new_height = $final_height;
+ $origin_y = round ($origin_y - ($new_height / 2));
+
+ }
+
+ }
+
+ // Restore transparency blending
+ imagesavealpha ($canvas, true);
+
+ if ($zoom_crop > 0) {
+
+ $src_x = $src_y = 0;
+ $src_w = $width;
+ $src_h = $height;
+
+ $cmp_x = $width / $new_width;
+ $cmp_y = $height / $new_height;
+
+ // calculate x or y coordinate and width or height of source
+ if ($cmp_x > $cmp_y) {
+
+ $src_w = round ($width / $cmp_x * $cmp_y);
+ $src_x = round (($width - ($width / $cmp_x * $cmp_y)) / 2);
+
+ } else if ($cmp_y > $cmp_x) {
+
+ $src_h = round ($height / $cmp_y * $cmp_x);
+ $src_y = round (($height - ($height / $cmp_y * $cmp_x)) / 2);
+
+ }
+
+ // positional cropping!
+ if ($align) {
+ if (strpos ($align, 't') !== false) {
+ $src_y = 0;
+ }
+ if (strpos ($align, 'b') !== false) {
+ $src_y = $height - $src_h;
+ }
+ if (strpos ($align, 'l') !== false) {
+ $src_x = 0;
+ }
+ if (strpos ($align, 'r') !== false) {
+ $src_x = $width - $src_w;
+ }
+ }
+
+ imagecopyresampled ($canvas, $image, $origin_x, $origin_y, $src_x, $src_y, $new_width, $new_height, $src_w, $src_h);
+
+ } else {
+
+ // copy and resize part of an image with resampling
+ imagecopyresampled ($canvas, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
+
+ }
+
+ if ($filters != '' && function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
+ // apply filters to image
+ $filterList = explode ('|', $filters);
+ foreach ($filterList as $fl) {
+
+ $filterSettings = explode (',', $fl);
+ if (isset ($imageFilters[$filterSettings[0]])) {
+
+ for ($i = 0; $i < 4; $i ++) {
+ if (!isset ($filterSettings[$i])) {
+ $filterSettings[$i] = null;
+ } else {
+ $filterSettings[$i] = (int) $filterSettings[$i];
+ }
+ }
+
+ switch ($imageFilters[$filterSettings[0]][1]) {
+
+ case 1:
+
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1]);
+ break;
+
+ case 2:
+
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2]);
+ break;
+
+ case 3:
+
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3]);
+ break;
+
+ case 4:
+
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3], $filterSettings[4]);
+ break;
+
+ default:
+
+ imagefilter ($canvas, $imageFilters[$filterSettings[0]][0]);
+ break;
+
+ }
+ }
+ }
+ }
+
+ // sharpen image
+ if ($sharpen && function_exists ('imageconvolution')) {
+
+ $sharpenMatrix = array (
+ array (-1,-1,-1),
+ array (-1,16,-1),
+ array (-1,-1,-1),
+ );
+
+ $divisor = 8;
+ $offset = 0;
+
+ imageconvolution ($canvas, $sharpenMatrix, $divisor, $offset);
+
+ }
+ //Straight from Wordpress core code. Reduces filesize by up to 70% for PNG's
+ if ( (IMAGETYPE_PNG == $origType || IMAGETYPE_GIF == $origType) && function_exists('imageistruecolor') && !imageistruecolor( $image ) && imagecolortransparent( $image ) > 0 ){
+ imagetruecolortopalette( $canvas, false, imagecolorstotal( $image ) );
+ }
+
+ $imgType = "";
+ $tempfile = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
+ if(preg_match('/^image\/(?:jpg|jpeg)$/i', $mimeType)){
+ $imgType = 'jpg';
+ imagejpeg($canvas, $tempfile, $quality);
+ } else if(preg_match('/^image\/png$/i', $mimeType)){
+ $imgType = 'png';
+ imagepng($canvas, $tempfile, floor($quality * 0.09));
+ } else if(preg_match('/^image\/gif$/i', $mimeType)){
+ $imgType = 'gif';
+ imagegif($canvas, $tempfile);
+ } else {
+ return $this->sanityFail("Could not match mime type after verifying it previously.");
+ }
+
+ if($imgType == 'png' && OPTIPNG_ENABLED && OPTIPNG_PATH && @is_file(OPTIPNG_PATH)){
+ $exec = OPTIPNG_PATH;
+ $this->debug(3, "optipng'ing $tempfile");
+ $presize = filesize($tempfile);
+ $out = `$exec -o1 $tempfile`; //you can use up to -o7 but it really slows things down
+ clearstatcache();
+ $aftersize = filesize($tempfile);
+ $sizeDrop = $presize - $aftersize;
+ if($sizeDrop > 0){
+ $this->debug(1, "optipng reduced size by $sizeDrop");
+ } else if($sizeDrop < 0){
+ $this->debug(1, "optipng increased size! Difference was: $sizeDrop");
+ } else {
+ $this->debug(1, "optipng did not change image size.");
+ }
+ } else if($imgType == 'png' && PNGCRUSH_ENABLED && PNGCRUSH_PATH && @is_file(PNGCRUSH_PATH)){
+ $exec = PNGCRUSH_PATH;
+ $tempfile2 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
+ $this->debug(3, "pngcrush'ing $tempfile to $tempfile2");
+ $out = `$exec $tempfile $tempfile2`;
+ $todel = "";
+ if(is_file($tempfile2)){
+ $sizeDrop = filesize($tempfile) - filesize($tempfile2);
+ if($sizeDrop > 0){
+ $this->debug(1, "pngcrush was succesful and gave a $sizeDrop byte size reduction");
+ $todel = $tempfile;
+ $tempfile = $tempfile2;
+ } else {
+ $this->debug(1, "pngcrush did not reduce file size. Difference was $sizeDrop bytes.");
+ $todel = $tempfile2;
+ }
+ } else {
+ $this->debug(3, "pngcrush failed with output: $out");
+ $todel = $tempfile2;
+ }
+ @unlink($todel);
+ }
+
+ $this->debug(3, "Rewriting image with security header.");
+ $tempfile4 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
+ $context = stream_context_create ();
+ $fp = fopen($tempfile,'r',0,$context);
+ file_put_contents($tempfile4, $this->filePrependSecurityBlock . $imgType . ' ?' . '>'); //6 extra bytes, first 3 being image type
+ file_put_contents($tempfile4, $fp, FILE_APPEND);
+ fclose($fp);
+ @unlink($tempfile);
+ $this->debug(3, "Locking and replacing cache file.");
+ $lockFile = $this->cachefile . '.lock';
+ $fh = fopen($lockFile, 'w');
+ if(! $fh){
+ return $this->error("Could not open the lockfile for writing an image.");
+ }
+ if(flock($fh, LOCK_EX)){
+ @unlink($this->cachefile); //rename generally overwrites, but doing this in case of platform specific quirks. File might not exist yet.
+ rename($tempfile4, $this->cachefile);
+ flock($fh, LOCK_UN);
+ fclose($fh);
+ @unlink($lockFile);
+ } else {
+ fclose($fh);
+ @unlink($lockFile);
+ @unlink($tempfile4);
+ return $this->error("Could not get a lock for writing.");
+ }
+ $this->debug(3, "Done image replace with security header. Cleaning up and running cleanCache()");
+ imagedestroy($canvas);
+ imagedestroy($image);
+ return true;
+ }
+ protected function calcDocRoot(){
+ $docRoot = @$_SERVER['DOCUMENT_ROOT'];
+ if (defined('LOCAL_FILE_BASE_DIRECTORY')) {
+ $docRoot = LOCAL_FILE_BASE_DIRECTORY;
+ }
+ if(!isset($docRoot)){
+ $this->debug(3, "DOCUMENT_ROOT is not set. This is probably windows. Starting search 1.");
+ if(isset($_SERVER['SCRIPT_FILENAME'])){
+ $docRoot = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0-strlen($_SERVER['PHP_SELF'])));
+ $this->debug(3, "Generated docRoot using SCRIPT_FILENAME and PHP_SELF as: $docRoot");
+ }
+ }
+ if(!isset($docRoot)){
+ $this->debug(3, "DOCUMENT_ROOT still is not set. Starting search 2.");
+ if(isset($_SERVER['PATH_TRANSLATED'])){
+ $docRoot = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0-strlen($_SERVER['PHP_SELF'])));
+ $this->debug(3, "Generated docRoot using PATH_TRANSLATED and PHP_SELF as: $docRoot");
+ }
+ }
+ if($docRoot && $_SERVER['DOCUMENT_ROOT'] != '/'){ $docRoot = preg_replace('/\/$/', '', $docRoot); }
+ $this->debug(3, "Doc root is: " . $docRoot);
+ $this->docRoot = $docRoot;
+
+ }
+ protected function getLocalImagePath($src){
+ $src = ltrim($src, '/'); //strip off the leading '/'
+ if(! $this->docRoot){
+ $this->debug(3, "We have no document root set, so as a last resort, lets check if the image is in the current dir and serve that.");
+ //We don't support serving images outside the current dir if we don't have a doc root for security reasons.
+ $file = preg_replace('/^.*?([^\/\\\\]+)$/', '$1', $src); //strip off any path info and just leave the filename.
+ if(is_file($file)){
+ return $this->realpath($file);
+ }
+ return $this->error("Could not find your website document root and the file specified doesn't exist in timthumbs directory. We don't support serving files outside timthumb's directory without a document root for security reasons.");
+ } //Do not go past this point without docRoot set
+
+ //Try src under docRoot
+ if(file_exists ($this->docRoot . '/' . $src)) {
+ $this->debug(3, "Found file as " . $this->docRoot . '/' . $src);
+ $real = $this->realpath($this->docRoot . '/' . $src);
+ if(stripos($real, $this->docRoot) === 0){
+ return $real;
+ } else {
+ $this->debug(1, "Security block: The file specified occurs outside the document root.");
+ //allow search to continue
+ }
+ }
+ //Check absolute paths and then verify the real path is under doc root
+ $absolute = $this->realpath('/' . $src);
+ if($absolute && file_exists($absolute)){ //realpath does file_exists check, so can probably skip the exists check here
+ $this->debug(3, "Found absolute path: $absolute");
+ if(! $this->docRoot){ $this->sanityFail("docRoot not set when checking absolute path."); }
+ if(stripos($absolute, $this->docRoot) === 0){
+ return $absolute;
+ } else {
+ $this->debug(1, "Security block: The file specified occurs outside the document root.");
+ //and continue search
+ }
+ }
+
+ $base = $this->docRoot;
+
+ // account for Windows directory structure
+ if (strstr($_SERVER['SCRIPT_FILENAME'],':')) {
+ $sub_directories = explode('\\', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
+ } else {
+ $sub_directories = explode('/', str_replace($this->docRoot, '', $_SERVER['SCRIPT_FILENAME']));
+ }
+
+ foreach ($sub_directories as $sub){
+ $base .= $sub . '/';
+ $this->debug(3, "Trying file as: " . $base . $src);
+ if(file_exists($base . $src)){
+ $this->debug(3, "Found file as: " . $base . $src);
+ $real = $this->realpath($base . $src);
+ if(stripos($real, $this->realpath($this->docRoot)) === 0){
+ return $real;
+ } else {
+ $this->debug(1, "Security block: The file specified occurs outside the document root.");
+ //And continue search
+ }
+ }
+ }
+ return false;
+ }
+ protected function realpath($path){
+ //try to remove any relative paths
+ $remove_relatives = '/\w+\/\.\.\//';
+ while(preg_match($remove_relatives,$path)){
+ $path = preg_replace($remove_relatives, '', $path);
+ }
+ //if any remain use PHP realpath to strip them out, otherwise return $path
+ //if using realpath, any symlinks will also be resolved
+ return preg_match('#^\.\./|/\.\./#', $path) ? realpath($path) : $path;
+ }
+ protected function toDelete($name){
+ $this->debug(3, "Scheduling file $name to delete on destruct.");
+ $this->toDeletes[] = $name;
+ }
+ protected function serveWebshot(){
+ $this->debug(3, "Starting serveWebshot");
+ $instr = "Please follow the instructions at http://code.google.com/p/timthumb/ to set your server up for taking website screenshots.";
+ if(! is_file(WEBSHOT_CUTYCAPT)){
+ return $this->error("CutyCapt is not installed. $instr");
+ }
+ if(! is_file(WEBSHOT_XVFB)){
+ return $this->Error("Xvfb is not installed. $instr");
+ }
+ $cuty = WEBSHOT_CUTYCAPT;
+ $xv = WEBSHOT_XVFB;
+ $screenX = WEBSHOT_SCREEN_X;
+ $screenY = WEBSHOT_SCREEN_Y;
+ $colDepth = WEBSHOT_COLOR_DEPTH;
+ $format = WEBSHOT_IMAGE_FORMAT;
+ $timeout = WEBSHOT_TIMEOUT * 1000;
+ $ua = WEBSHOT_USER_AGENT;
+ $jsOn = WEBSHOT_JAVASCRIPT_ON ? 'on' : 'off';
+ $javaOn = WEBSHOT_JAVA_ON ? 'on' : 'off';
+ $pluginsOn = WEBSHOT_PLUGINS_ON ? 'on' : 'off';
+ $proxy = WEBSHOT_PROXY ? ' --http-proxy=' . WEBSHOT_PROXY : '';
+ $tempfile = tempnam($this->cacheDirectory, 'timthumb_webshot');
+ $url = $this->src;
+ if(! preg_match('/^https?:\/\/[a-zA-Z0-9\.\-]+/i', $url)){
+ return $this->error("Invalid URL supplied.");
+ }
+ $url = preg_replace('/[^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=]+/', '', $url); //RFC 3986
+ //Very important we don't allow injection of shell commands here. URL is between quotes and we are only allowing through chars allowed by a the RFC
+ // which AFAIKT can't be used for shell injection.
+ if(WEBSHOT_XVFB_RUNNING){
+ putenv('DISPLAY=:100.0');
+ $command = "$cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
+ } else {
+ $command = "$xv --server-args=\"-screen 0, {$screenX}x{$screenY}x{$colDepth}\" $cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
+ }
+ $this->debug(3, "Executing command: $command");
+ $out = `$command`;
+ $this->debug(3, "Received output: $out");
+ if(! is_file($tempfile)){
+ $this->set404();
+ return $this->error("The command to create a thumbnail failed.");
+ }
+ $this->cropTop = true;
+ if($this->processImageAndWriteToCache($tempfile)){
+ $this->debug(3, "Image processed succesfully. Serving from cache");
+ return $this->serveCacheFile();
+ } else {
+ return false;
+ }
+ }
+ protected function serveExternalImage(){
+ if(! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+/i', $this->src)){
+ $this->error("Invalid URL supplied.");
+ return false;
+ }
+ $tempfile = tempnam($this->cacheDirectory, 'timthumb');
+ $this->debug(3, "Fetching external image into temporary file $tempfile");
+ $this->toDelete($tempfile);
+ #fetch file here
+ if(! $this->getURL($this->src, $tempfile)){
+ @unlink($this->cachefile);
+ touch($this->cachefile);
+ $this->debug(3, "Error fetching URL: " . $this->lastURLError);
+ $this->error("Error reading the URL you specified from remote host." . $this->lastURLError);
+ return false;
+ }
+
+ $mimeType = $this->getMimeType($tempfile);
+ if(! preg_match("/^image\/(?:jpg|jpeg|gif|png)$/i", $mimeType)){
+ $this->debug(3, "Remote file has invalid mime type: $mimeType");
+ @unlink($this->cachefile);
+ touch($this->cachefile);
+ $this->error("The remote file is not a valid image.");
+ return false;
+ }
+ if($this->processImageAndWriteToCache($tempfile)){
+ $this->debug(3, "Image processed succesfully. Serving from cache");
+ return $this->serveCacheFile();
+ } else {
+ return false;
+ }
+ }
+ public static function curlWrite($h, $d){
+ fwrite(self::$curlFH, $d);
+ self::$curlDataWritten += strlen($d);
+ if(self::$curlDataWritten > MAX_FILE_SIZE){
+ return 0;
+ } else {
+ return strlen($d);
+ }
+ }
+ protected function serveCacheFile(){
+ $this->debug(3, "Serving {$this->cachefile}");
+ if(! is_file($this->cachefile)){
+ $this->error("serveCacheFile called in timthumb but we couldn't find the cached file.");
+ return false;
+ }
+ $fp = fopen($this->cachefile, 'rb');
+ if(! $fp){ return $this->error("Could not open cachefile."); }
+ fseek($fp, strlen($this->filePrependSecurityBlock), SEEK_SET);
+ $imgType = fread($fp, 3);
+ fseek($fp, 3, SEEK_CUR);
+ if(ftell($fp) != strlen($this->filePrependSecurityBlock) + 6){
+ @unlink($this->cachefile);
+ return $this->error("The cached image file seems to be corrupt.");
+ }
+ $imageDataSize = filesize($this->cachefile) - (strlen($this->filePrependSecurityBlock) + 6);
+ $this->sendImageHeaders($imgType, $imageDataSize);
+ $bytesSent = @fpassthru($fp);
+ fclose($fp);
+ if($bytesSent > 0){
+ return true;
+ }
+ $content = file_get_contents ($this->cachefile);
+ if ($content != FALSE) {
+ $content = substr($content, strlen($this->filePrependSecurityBlock) + 6);
+ echo $content;
+ $this->debug(3, "Served using file_get_contents and echo");
+ return true;
+ } else {
+ $this->error("Cache file could not be loaded.");
+ return false;
+ }
+ }
+ protected function sendImageHeaders($mimeType, $dataSize){
+ if(! preg_match('/^image\//i', $mimeType)){
+ $mimeType = 'image/' . $mimeType;
+ }
+ if(strtolower($mimeType) == 'image/jpg'){
+ $mimeType = 'image/jpeg';
+ }
+ $gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +10 days')) . ' GMT';
+ $gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
+ // send content headers then display image
+ header ('Content-Type: ' . $mimeType);
+ header ('Accept-Ranges: none'); //Changed this because we don't accept range requests
+ header ('Last-Modified: ' . $gmdate_modified);
+ header ('Content-Length: ' . $dataSize);
+ if(BROWSER_CACHE_DISABLE){
+ $this->debug(3, "Browser cache is disabled so setting non-caching headers.");
+ header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
+ header("Pragma: no-cache");
+ header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
+ } else {
+ $this->debug(3, "Browser caching is enabled");
+ header('Cache-Control: max-age=' . BROWSER_CACHE_MAX_AGE . ', must-revalidate');
+ header('Expires: ' . $gmdate_expires);
+ }
+ return true;
+ }
+ protected function securityChecks(){
+ }
+ protected function param($property, $default = ''){
+ if (isset ($_GET[$property])) {
+ return $_GET[$property];
+ } else {
+ return $default;
+ }
+ }
+ protected function openImage($mimeType, $src){
+ switch ($mimeType) {
+ case 'image/jpeg':
+ $image = imagecreatefromjpeg ($src);
+ break;
+
+ case 'image/png':
+ $image = imagecreatefrompng ($src);
+ break;
+
+ case 'image/gif':
+ $image = imagecreatefromgif ($src);
+ break;
+
+ default:
+ $this->error("Unrecognised mimeType");
+ }
+
+ return $image;
+ }
+ protected function getIP(){
+ $rem = @$_SERVER["REMOTE_ADDR"];
+ $ff = @$_SERVER["HTTP_X_FORWARDED_FOR"];
+ $ci = @$_SERVER["HTTP_CLIENT_IP"];
+ if(preg_match('/^(?:192\.168|172\.16|10\.|127\.)/', $rem)){
+ if($ff){ return $ff; }
+ if($ci){ return $ci; }
+ return $rem;
+ } else {
+ if($rem){ return $rem; }
+ if($ff){ return $ff; }
+ if($ci){ return $ci; }
+ return "UNKNOWN";
+ }
+ }
+ protected function debug($level, $msg){
+ if(DEBUG_ON && $level <= DEBUG_LEVEL){
+ $execTime = sprintf('%.6f', microtime(true) - $this->startTime);
+ $tick = sprintf('%.6f', 0);
+ if($this->lastBenchTime > 0){
+ $tick = sprintf('%.6f', microtime(true) - $this->lastBenchTime);
+ }
+ $this->lastBenchTime = microtime(true);
+ error_log("TimThumb Debug line " . __LINE__ . " [$execTime : $tick]: $msg");
+ }
+ }
+ protected function sanityFail($msg){
+ return $this->error("There is a problem in the timthumb code. Message: Please report this error at <a href='http://code.google.com/p/timthumb/issues/list'>timthumb's bug tracking page</a>: $msg");
+ }
+ protected function getMimeType($file){
+ $info = getimagesize($file);
+ if(is_array($info) && $info['mime']){
+ return $info['mime'];
+ }
+ return '';
+ }
+ protected function setMemoryLimit(){
+ $inimem = ini_get('memory_limit');
+ $inibytes = timthumb::returnBytes($inimem);
+ $ourbytes = timthumb::returnBytes(MEMORY_LIMIT);
+ if($inibytes < $ourbytes){
+ ini_set ('memory_limit', MEMORY_LIMIT);
+ $this->debug(3, "Increased memory from $inimem to " . MEMORY_LIMIT);
+ } else {
+ $this->debug(3, "Not adjusting memory size because the current setting is " . $inimem . " and our size of " . MEMORY_LIMIT . " is smaller.");
+ }
+ }
+ protected static function returnBytes($size_str){
+ switch (substr ($size_str, -1))
+ {
+ case 'M': case 'm': return (int)$size_str * 1048576;
+ case 'K': case 'k': return (int)$size_str * 1024;
+ case 'G': case 'g': return (int)$size_str * 1073741824;
+ default: return $size_str;
+ }
+ }
+ protected function getURL($url, $tempfile){
+ $this->lastURLError = false;
+ $url = preg_replace('/ /', '%20', $url);
+ if(function_exists('curl_init')){
+ $this->debug(3, "Curl is installed so using it to fetch URL.");
+ self::$curlFH = fopen($tempfile, 'w');
+ if(! self::$curlFH){
+ $this->error("Could not open $tempfile for writing.");
+ return false;
+ }
+ self::$curlDataWritten = 0;
+ $this->debug(3, "Fetching url with curl: $url");
+ $curl = curl_init($url);
+ curl_setopt ($curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
+ curl_setopt ($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30");
+ curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
+ curl_setopt ($curl, CURLOPT_HEADER, 0);
+ curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
+ curl_setopt ($curl, CURLOPT_WRITEFUNCTION, 'timthumb::curlWrite');
+ @curl_setopt ($curl, CURLOPT_FOLLOWLOCATION, true);
+ @curl_setopt ($curl, CURLOPT_MAXREDIRS, 10);
+
+ $curlResult = curl_exec($curl);
+ fclose(self::$curlFH);
+ $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+ if($httpStatus == 404){
+ $this->set404();
+ }
+ if($curlResult){
+ curl_close($curl);
+ return true;
+ } else {
+ $this->lastURLError = curl_error($curl);
+ curl_close($curl);
+ return false;
+ }
+ } else {
+ $img = @file_get_contents ($url);
+ if($img === false){
+ $err = error_get_last();
+ if(is_array($err) && $err['message']){
+ $this->lastURLError = $err['message'];
+ } else {
+ $this->lastURLError = $err;
+ }
+ if(preg_match('/404/', $this->lastURLError)){
+ $this->set404();
+ }
+
+ return false;
+ }
+ if(! file_put_contents($tempfile, $img)){
+ $this->error("Could not write to $tempfile.");
+ return false;
+ }
+ return true;
+ }
+
+ }
+ protected function serveImg($file){
+ $s = getimagesize($file);
+ if(! ($s && $s['mime'])){
+ return false;
+ }
+ header ('Content-Type: ' . $s['mime']);
+ header ('Content-Length: ' . filesize($file) );
+ header ('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
+ header ("Pragma: no-cache");
+ $bytes = @readfile($file);
+ if($bytes > 0){
+ return true;
+ }
+ $content = @file_get_contents ($file);
+ if ($content != FALSE){
+ echo $content;
+ return true;
+ }
+ return false;
+
+ }
+ protected function set404(){
+ $this->is404 = true;
+ }
+ protected function is404(){
+ return $this->is404;
+ }
+}