Re: Adding HTTP Log & HTPasswd classes

From: Date: Thu, 17 Oct 2002 15:20:19 +0000
Subject: Re: Adding HTTP Log & HTPasswd classes
References: 1  Groups: php.pear.dev 
Request: Send a blank email to pear-dev+get-10110@lists.php.net to get a copy of this message
Just a naming comment: The log format is Apache-specific (others have copied it, but originally it was Apache-specific, or NCSA httpd-specific for those who've been around for a while). I think Apache_LogParser or something like that would be a proper package name. To me, HTTP_Log sounds like a utility class for logging stuff, not parsing. +1 for Apache_LogParser :) - Stig On Tue, 2002-10-15 at 21:01, Sagi Bashari wrote: > Hello, > > I would like to contribute 2 classes to PEAR. > > 1. HTTP_Log.php - HTTP logs parser. > Currently capable of parsing combined HTTP log file and generate > statistics from it, including urls, useragents, referers and more. URL > statistics are currently stored in array - grouped by year/month/day/hour.. > > Can output both PHP source file and XML (untested). > > 2. Htpasswd.class - HTPasswd management class > Used to edit htpasswd files, list/add/delete/edit users. Will probably > add option to verify users in the near future. > > Both files are attached. I took them directly from my site source and > they are not using the PEAR classes/rules yet. > > Sagi > > > ---- > > <?php > /** > * HTTP_Log.php - HTTP Log parsing > * > * Package for handling HTTP log files, parsing them, generating statistics, etc. > * > * @version $Id: HTTP_Log.php,v 1.3 2002/08/13 07:29:30 sagi Exp $ > * > */ > include_once('Error.class'); > > // result codes > define('RC_OK', 200); > define('RC_PARTIALCONTENT', 206); > define('RC_NOMOD', 304); > > // only log those result codes > $log_rc = array(RC_OK,RC_PARTIALCONTENT,RC_NOMOD); > > // log those commands > $log_commands = array('GET','HEAD','POST','TRACE'); > > // directory index, log those as / > $directory_index = > array('index.php','index.php3','index.phtml','index.html','index.htm','index.cgi'); > > // don't log theose extensions > $nolog_extensions = > array('css','jpg','jpeg','gif','png'); > > > /** > * Parse HTTP log file > */ > class HTTP_Log_Parser extends Error > { > /** Array holding script names array that holds all the > GET variables that we need to store for that script. */ > var $GET_variables = array(); > > /** Array holding user agent counter (agent => count) */ > var $stats_useragent = array(); > > /** Array holding referer stats */ > var $stats_referer = array(); > > /* Array holding hits by year/month/day/hour */ > var $stats_hour = array(); > > /** > * Parses log file. > * > * @access public > * @param string Filename to parse > * @returns bool True on success, false on failure and sets error accordingly. > */ > function parseFile($filename) > { > if (!($fp = fopen($filename, 'r'))) { > $this->setError('Unable to open log file for reading: '.$fp); > return false; > } > > while (($buf = fgets($fp, 4096)) !== false) { > $buf = trim($buf); > $this->parseRecord($buf); > } > > fclose($fp); > > return false; > } > > /** > * Tells log parser not to strip this variable from this script. > * Any other variables will be stripped. > * > * Use '/' for directory indexes as their filename will be stripped. > * > * @param string Script name, use full path (ie. '/script.php') > * @param string Variable name (ie. 'id' for 'script.php?id=5') > */ > function storeGETVariable($script, $variable) > { > $this->GET_variables[$script][] = $variable; > } > > /** > * Parses log record. > * > * @access private > * @param string Combined log record > */ > function parseRecord($record) > { > // parse into variables > sscanf($record, '%s %s %s [%[^]]] "%[^"]" %s %s "%[^"]" > "%[^"]"', > $host, $logname, $user, $strtime, $request, $status, $bytes, $referer, $useragent > ); > > // only log results codes that are set in $log_rc > if (!in_array($status, $GLOBALS['log_rc'])) { > return; > } > > // convert apache time to unixtime > $strtime = str_replace('/', ' ', $strtime); > $strtime = explode(':', $strtime, 2); > $strtime = $strtime[0].' '.$strtime[1]; > $time = strtotime($strtime); > > // parse request > $arr = explode(' ', $request); > $command = $arr[0]; > $script = $arr[1]; > > if (!in_array($command, $GLOBALS['log_commands'])) { > return; > } > > // strip protocol > $pos = strpos($script, '://'); > > if ($pos !== false) { > $script = substr($script, ($pos + 3)); > > // strip domain > $pos = strpos($script, '/'); > $script = substr($script, $pos); > } > > // split GET variables from the script name > $arr = explode('?', $script); > $script_name = $arr[0]; > $script_vars = isset($arr[1]) ? $arr[1] : ''; > > // if the script name is a directory index, replace it with '/' > $script_basename = basename($script_name); > > if (in_array($script_basename, $GLOBALS['directory_index'])) { > $script_name = substr($script_name, 0, (strlen($script_name) - strlen($script_basename))); > } > > // check if we shouldn't log this extension > $arr = explode('.', $script_basename); > if (isset($arr[1])) { > $extension = $arr[1]; > if (in_array($extension, $GLOBALS['nolog_extensions'])) { > return; > } > } > > /* now rebuild the script path - use the new $script_name as filename, > only include variables that were added through storeGETVariable(). */ > $script = $script_name; > > if (!empty($script_vars) && isset($this->GET_variables[$script_name])) { > $first = true; > > foreach (explode('&', $script_vars) as $var) { > $arr = explode('=',$var); > $var_name = $arr[0]; > > if (in_array($var_name, $this->GET_variables[$script_name])) { > if ($first) { > $script .= '?'; > $first = false; > } else { > $script .= '&'; > } > > $script .= $var; > } > } > } > > // strip variables from referer > $arr = explode('?', $referer); > $referer = $arr[0]; > > // useragent stats > if (!isset($this->stats_useragent[$useragent])) { > $this->stats_useragent[$useragent] = 0; > } > > $this->stats_useragent[$useragent]++; > > // url hits stats > $date_year = (int)date('Y',$time); > $date_month = (int)date('m',$time); > $date_day = (int)date('d',$time); > $date_hour = (int)date('G',$time); > > if (!isset($this->stats_url[$date_year])) { > $this->stats_url[$date_year] = array(); > } > > if (!isset($this->stats_url[$date_year][$date_month])) { > $this->stats_url[$date_year][$date_month] = array(); > } > > if (!isset($this->stats_url[$date_year][$date_month][$date_day])) { > $this->stats_url[$date_year][$date_month][$date_day] = array(); > } > > if (!isset($this->stats_url[$date_year][$date_month][$date_day][$date_hour])) { > $this->stats_url[$date_year][$date_month][$date_day][$date_hour] = array(); > } > > if (!isset($this->stats_url[$date_year][$date_month][$date_day][$date_hour][$script])) { > $this->stats_url[$date_year][$date_month][$date_day][$date_hour][$script] = 0; > } > > $this->stats_url[$date_year][$date_month][$date_day][$date_hour][$script]++; > > // referer stats > if (!empty($referer) && $referer != '-') { > if (!isset($this->stats_referer[$referer])) { > $this->stats_referer[$referer] = 0; > } > > $this->stats_referer[$referer]++; > } > } > > /** > * Writes all settings to PHP file so external scripts can diagnose them in later time. > * > * @param string Filename to write to > * @returns bool True on success, false on failure (sets the error object accordingly). > * > */ > function writeToPHPFile($filename) > { > if (!($fp = fopen($filename, 'w'))) { > $this->setError('Unable to open file for writing: '.$fp); > return false; > } > > fputs($fp, '<?php'."\n"); > > // useragent stats > fputs($fp,'$stats_useragent = array('); > $first = true; > foreach ($this->stats_useragent as $useragent => $count) { > fputs($fp,(!$first ? ',' : > '').'\''.str_replace('\'','\\\'',$useragent).'\'=>'.$count); > $first = false; > } > fputs($fp,');'."\n"); > > // referer stats > fputs($fp,'$referer_url = array('); > $first = true; > foreach ($this->stats_referer as $referer => $count) { > fputs($fp,(!$first ? ',' : > '').'\''.str_replace('\'','\\\'',$referer).'\'=>'.$count); > $first = false; > } > fputs($fp,');'."\n"); > > // url stats > fputs($fp,'$stats_url = array('); > $first = true; > foreach ($this->stats_url as $year => $months) { > fputs($fp,(!$first ? ',' : '').$year.'=>array('); > > // months > $mfirst = true; > foreach ($months as $month => $days) { > fputs($fp,(!$mfirst ? ',' : '').$month.'=>array('); > > // days > $dfirst = true; > foreach ($days as $day => $hours) { > fputs($fp,(!$dfirst ? ',' : '').$day.'=>array('); > > // hours > $hfirst = true; > foreach ($hours as $hour => $urls) { > fputs($fp,(!$hfirst ? ',' : '').$hour.'=>array('); > > $ufirst = true; > foreach ($urls as $url => $count) { > fputs($fp,(!$ufirst ? ',' : > '').'\''.str_replace('\'','\\\'',$url)..'\''.'=>'.$count); > $ufirst = false; > } > > fputs($fp,')'); > $hfirst = false; > } > fputs($fp,')'); > > $dfirst = false; > } > fputs($fp,')'); > > $mfirst = false; > } > fputs($fp,')'); > > > $first = false; > } > fputs($fp,');'."\n"); > > fputs($fp, '?>'."\n"); > fclose($fp); > > return true; > } > > /** > * Writes all settings to XML file so external scripts can diagnose them in later time. > * > * @param string Filename to write to > * @returns bool True on success, false on failure (sets the error object accordingly). > * > */ > function writeXMLFile($filename) > { > if (!($fp = fopen($filename, 'w'))) { > $this->setError('Unable to open file for writing: '.$fp); > return false; > } > > fputs($fp, '<?xml version="1.0" encoding="iso-8859-1" > ?>'); > fputs($fp, '<stats>'); > > // useragent stats > fputs($fp, '<useragents>'); > foreach ($this->stats_useragent as $useragent => $count) { > fputs($fp, '<useragent name="'.htmlspecialchars($useragent).'" > count="'.$count.'"/>'); > } > fputs($fp, '</useragents>'); > > // referer stats > fputs($fp,'<referers>'); > foreach ($this->stats_referer as $referer => $count) { > fputs($fp, '<referer address="'.htmlspecialchars($referer).'" > count="'.$count.'"/>'); > } > fputs($fp,'</referers>'); > > // url stats > fputs($fp,'<urls>'); > foreach ($this->stats_url as $year => $months) { > fputs($fp, '<year id="'.$year.'">'); > > // months > foreach ($months as $month => $days) { > fputs($fp, '<month id="'.$month.'">'); > > // days > foreach ($days as $day => $hours) { > fputs($fp, '<day id="'.$day.'">'); > > // hours > foreach ($hours as $hour => $urls) { > fputs($fp, '<hour id="'.$hour.'">'); > > foreach ($urls as $url => $count) { > fputs($fp, '<url address="'.htmlspecialchars($url).'" > count="'..$count.'"/>'); > } > > fputs($fp, '</hour>'); > } > fputs($fp, '</day>'); > > } > fputs($fp, '</month>'); > > } > > fputs($fp, '</year>'); > } > fputs($fp,'</urls>'); > > fputs($fp, '</stats>'); > fclose($fp); > > return true; > } > } > ---- > > <?php > /** > * Htpasswd.class > * > * Htpasswd management module. > * > * @version $Id$ > * > */ > include_once('Error.class'); > > class Htpasswd extends Error > { > /** @var string Contains the password file path */ > var $file; > > /** @var array Contains the users database in $user => $password format */ > var $db; > > /** > * Constructor. Initializes the file if file is not empty. > * NOTICE: If you use PHP version that is older than 4.2, you must call srand() before loading > this class. > * > * @param string Optional, password file > * @returns bool False on error and sets the error variable accordingly. > * > */ > function Htpasswd($file = '') > { > $this->db = array(); > > if (!empty($file)) { > return $this->initialize($file); > } > > return true; > } > > /** > * Initializes the password file. > * Checks if the file exists and calls readFile() to process it if it does. > * > * @param string Password file > * @returns bool False on error and sets the error variable accordingly. > * > */ > function initialize($file) > { > if (empty($file)) { > $this->setError('Unable to initliaze file: No file specified'); > return false; > } > > $this->file = $file; > > if (file_exists($this->file)) { > return $this->readFile(); > } > > return true; > } > > /** > * Reads the password file, parse it and save the users&passwords in the memory. > * > * @returns bool False on error and sets the error variable accordingly. > * > */ > function readFile() > { > if (!($fp = fopen($this->file, 'r'))) { > $this->setError('Unable to open file for reading: '.$fp); > } > > $this->db = array(); > > while (($buf = fgets($fp, 1024)) !== false) { > $buf = trim($buf); > $arr = explode(':', $buf); > > // skip bad record > if (count($arr) != 2) { > continue; > } > > $user =& $arr[0]; > $pass =& $arr[1]; > > if (empty($user) || empty($pass)) { > continue; > } > > $this->db[$user] = $pass; > } > > fclose($fp); > > return true; > } > > /** > * Lists users in database. > * > * @returns array Users in database > * > */ > function listUsers() > { > return array_keys($this->db); > } > > /** > * Returns the number of users in database. > * > * @returns integer Number of users in database. > * > */ > function numUsers() > { > return count($this->db); > } > > /** > * Generates salt for crypt() > * > * @returns string The salt > * > */ > function genSalt() > { > // prefer md5 salt ($1$ followed by 8 chars) > $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./'; > > if (CRYPT_MD5) { > $rand = rand(1, (strlen($chars) - 8)); > return '$1$'.substr($chars, $rand, 8); > } else { > $rand = rand(1, (strlen($chars) - 2)); > return substr($chars, $rand, 2); > } > } > > /** > * Encrypt password using the crypt() function. > * > * @param string Plaintext password > * @param string Optional, two-character salt > * @returns string Encrypted password > * > */ > function cryptPass($pass, $salt='') > { > if (empty($salt)) { > $salt = $this->genSalt(); > } > > return crypt($pass, $salt); > } > > /** > * Checks if user exists in this password file. > * > * @param string Username > * @param bool Returns true if user exists, false otherwise > * > */ > function isUser($user) > { > return isset($this->db[$user]); > } > > /** > * Change user password. > * > * @param string Username > * @param string New password > * @returns bool False on error and sets the error variable accordingly. > * > */ > function changePass($user, $pass) > { > if (!$this->isUser($user)) { > $this->setError('Unable to change user password: user does not exists'); > return false; > } > > $this->db[$user] = $this->cryptPass($pass); > > return true; > } > > /** > * Writes the password file to the disk. > * > * @returns bool False on error and sets the error variable accordingly. > * > */ > function writeFile() > { > if (empty($this->file)) { > $this->setError('Unable to write file: you must initialize the class first'); > return false; > } > > if (!($fp = fopen($this->file, 'w'))) { > $this->setError('Unable to open file for writing: '.$fp); > return false; > } > > foreach ($this->db as $user => $pass) { > fputs($fp, $user.':'.$pass."\n"); > } > > fclose($fp); > > return true; > } > > /** > * Adds user to password file. > * > * @param string Username > * @param string Password > * > */ > function addUser($user, $pass) > { > $this->db[$user] = $this->cryptPass($pass); > } > > /** > * Deletes user from password file. > * > * @param string Username > * > */ > function delUser($user) > { > unset($this->db[$user]); > } > } > > ?> > > ---- > > -- > PEAR Development Mailing List (http://pear.php.net/) > To unsubscribe, visit: http://www.php.net/unsub.php -- Stig Sæther Bakken, Fast Search & Transfer ASA, Trondheim, Norway http://pear.php.net/wishlist.php/ssb

« previous php.pear.dev (#10110) next »