I'm working on a class whose purpose is to restrict users to making only 10 requests within any 30 second period. It utilizes a file to maintain IP addresses, last request time. and the number of tries they've made. The problem is that, no matter what I try, I can't get the filesize. I've tried using clearstatcache()
, and I've tried using a function I found in the comments on the filesize() page of the PHP manual.
Here's the code, in it's current debugging state.
// Makes sure user can only try to generate a coupon x number of times over x amount of seconds
class IpChecker{
const WAIT_TIME = 30; //seconds until user can try again
const MAX_TRIES = 10; // maximum tries
const COUPON_IP = 0;
const COUPON_TIME = 1;
const COUPON_TRIES = 2;
private $ip_data;
private $path;
private $fh;
private $safe;
public function __construct(){
clearstatcache();
$this->path = realpath(dirname(__FILE__))."/ips/.ips";
$this->fh = fopen($this->path,'w+');
$this->filesize = $this->realfilesize($this->fh);
echo "fs: ".$this->filesize; exit;
$this->getIPs();
$this->checkIP();
$this->logRequest();
fclose($this->fh);
$this->safe || die(json_encode("You have exhausted all available tries. Please try again later."));
}
private function logRequest(){
$str = "";
foreach($this->ip_data as $data){
foreach($data as $col){
if(self::WAIT_TIME < (time() - $col[self::COUPON_TIME])) $str .= $col."\t";
}
$str = rtrim($str, '\t');
$str .= "\n";
}
$str = rtrim($str, '\n');
try{
$fw = fwrite($this->fh, $str) || die(json_encode("Unable to check IP"));
}catch(Exception $e){
die(json_encode($e));
}
}
private function checkIP(){
$IP = $_SERVER['REMOTE_ADDR'];
$TIME = time();
$safe = true;
$user_logged = false;
echo "<pre>"; var_dump($this->ip_data); exit;
foreach($this->ip_data as $key=>$data){
echo "<prE>"; var_dump($data); exit;
// if($data[$key][self::COUPON_IP] == $IP){
// $user_logged = true;
// if(
// (($TIME - $data[$key][self::COUPON_TIME]) < self::WAIT_TIME) ||
// (self::MAX_TRIES >= $data[$key][self::COUPON_TRIES])
// ) $safe = false;
// $this->ip_data[$key][self::COUPON_TRIES] = $this->ip_data[$key][self::COUPON_TRIES]+1;
// $this->ip_data[$key][self::COUPON_TIME] = $TIME;
// }
}
if(!$user_logged){
die("user not logged");
$this->ip_data[] = array(
self::COUPON_IP => $IP,
self::COUPON_TIME => $TIME,
self::COUPON_TRIES => 1
);
}
$this->safe = $safe;
}
private function getIPs(){
$IP_DATA = array();
echo file_get_contents($this->path); exit;
// this always returns 0.
$size = filesize($this->path);
echo "filesize: ".$size; exit;
if($size){
$IPs = fread($this->fh,$size);
$IP_ARR = explode("\n",$IPs);
foreach($IP_ARR as $line) $IP_DATA[] = explode("\t",$line);
}
$this->ip_data = $IP_DATA;
}
// Copied from the comments in the PHP Manual for filesize()
public function realfilesize($fp) {
$return = false;
if (is_resource($fp)) {
if (PHP_INT_SIZE < 8) {
// 32bit
if (0 === fseek($fp, 0, SEEK_END)) {
$return = 0.0;
$step = 0x7FFFFFFF;
while ($step > 0) {
if (0 === fseek($fp, - $step, SEEK_CUR)) {
$return += floatval($step);
} else {
$step >>= 1;
}
}
}
} elseif (0 === fseek($fp, 0, SEEK_END)) {
// 64bit
$return = ftell($fp);
}
}
return $return;
}
}
How can I get the real filesize? I'm on PHP 5.2.
I wasn;t able to figure out what was wrong with my code, if anythign, but I worked around it like this:
/**
* Makes sure user can only access a given page
* x number of times over x amount of seconds.
* If multiple instances of this class are used, the $namespace
* properties for each should be unique.
* Default wait time is 90 seconds while default request limit
* is 10 tries.
*
* -+ Usage +-
* Just create the object with any parameters, no need to assign,
* just make sure it's initialized at the top of the page:
* new RequestThrottler;
*
* -+- Parameters -+-
* null RequestThrottler ( [ string $namespace [, int $WaitTime [, int $MaxTries ] ] ] )
*/
class RequestThrottler{
// settings
private static $WAIT_TIME; // seconds until count expires
private static $MAX_TRIES; // maximum tries
// used to keep session variables unique
// in the event that this class is used in multiple places.
private $namespace;
// for debugging
const DBG = false;
// array index constants
const _TIME = 0;
const _TRIES = 1;
// defines whether or not access is permitted
private $safe;
// seconds until reset
private $secs;
/**
* -+- Constructor -+-
* @param String $namespace - A unique prefix for SESSION data
* @param Int $WaitTime - Total seconds before user can try again
* @param Int $MaxTries - Total tries user can make until their request is denied
*/
public function __construct($namespace='Throttler', $WaitTime=90, $MaxTries=10){
// make sure a session is available
if(!headers_sent() && !isset($_SESSION)) session_start();
if(!isset($_SESSION)) die(json_encode("No session available"));
// save settings
$this->namespace = $namespace;
self::$MAX_TRIES = $MaxTries;
self::$WAIT_TIME = $WaitTime;
// do the footwork
$this->checkHistory();
// if set to debug mode, print a short helpful string
if(self::DBG) die(json_encode(
"You are ".($this->safe ? 'SAFE' : 'NOT SAFE')."! "
. "This is try number {$_SESSION[$this->namespace.'_ATTEMPTS'][self::_TRIES]} of ".self::$MAX_TRIES.". "
. $this->secs." seconds since last attempt. "
. (self::$WAIT_TIME - $this->secs)." seconds until reset."
));
// if not safe, kill the script, show a message
$this->safe || die(json_encode("You're going too fast. Please try again in ".(self::$WAIT_TIME - $this->secs)." seconds."));
}
/**
* -+- checkHistory -+-
* Does the footwork to determine whether
* or not to throttle the current user/request.
*/
private function checkHistory(){
$TIME = time();
$safe = true;
// make sure session is iniitialized
if( !isset($_SESSION[$this->namespace.'_ATTEMPTS']) ||
!isset($_SESSION[$this->namespace.'_ATTEMPTS'][self::_TRIES]) ||
!isset($_SESSION[$this->namespace.'_ATTEMPTS'][self::_TIME]) )
$_SESSION[$this->namespace.'_ATTEMPTS'] = array(
self::_TIME =>$TIME,
self::_TRIES => 1
);
else $_SESSION[$this->namespace.'_ATTEMPTS'][self::_TRIES] =
$_SESSION[$this->namespace.'_ATTEMPTS'][self::_TRIES]+1;
// get seconds since last attempt
$secondSinceLastAttempt = $TIME - $_SESSION[$this->namespace.'_ATTEMPTS'][self::_TIME];
// reset the counter if the wait time has expired
if($secondSinceLastAttempt > self::$WAIT_TIME){
$_SESSION[$this->namespace.'_ATTEMPTS'][self::_TIME] = $TIME;
$_SESSION[$this->namespace.'_ATTEMPTS'][self::_TRIES] = 1;
$secondSinceLastAttempt = 0;
}
// finally, determine if we're safe
if($_SESSION[$this->namespace.'_ATTEMPTS'][self::_TRIES] >= self::$MAX_TRIES) $safe=false;
// log this for debugging
$this->secs = $secondSinceLastAttempt;
// save the "safe" flag
$this->safe = $safe;
}
}