Re: Adding HTTP Log & HTPasswd classes
| From: | Stig S. Bakken | 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