<?php
    
# Written by Mik Mifflin (UziMonkey), 2004
    # No license really at this point, but if you
    # use it, credit me and tell me about it.

    # PHP input validation classes

    # validator base class
    # Can be used as an identity validator
    
class Validator {
        function 
validate ($string) { 
            if (isset(
$string)) {
                return 
true;
            } else {
                return 
false;
            }
        }
        function 
checkLimits ($string) { return true; }
        function 
translate ($string) { return $string; }
    }

    
# An integer validator class
    
class IntegerValidator extends Validator {
        var 
$minimum;
        var 
$maximum;

        function 
IntegerValidator ($min=0,$max=0) {
            
$this->minimum $min;
            
$this->maximum $max;
        }

        function 
validate ($string) {
            if (
Validator::validate($string) == false) {
                return 
false;
            }

            
# String may only consist of numbers and an optional -
            
if (preg_match ("/^-?[0-9]+$/",$string) == 0) {
                return 
false;
            } else {
                return 
true;
            }
        }

        
# Make sure the number is within bounds
        
function checkLimits ($string) {
            
# If the range isn't valid (both 0's), just skip this
            
if ($this->minimum == && $this->maximum == 0) {
                return 
true;
            }

            
# Convert to an integer and make sure you get a valid one
            
$number intval($string);

            
# Does the number fall within the range?
            
if ($number >= $this->minimum && $number <= $this->maximum) {
                return 
true;
            } else {
                return 
false;
            }
        }

        
# No translation to be done for integers, inherit from Validator
    
}

    
# A username class validator
    # Usernames must begin with a letter or number
    # Following characters may also contain an underscore
    # There is a minimum of x (default 1) characters
    # There is a maximum of y (default 20) characters
    
class UsernameValidator extends Validator {
        var 
$minlen;
        var 
$maxlen;

        function 
UsernameValidator ($minlen=1,$maxlen=20) {
            
$this->minlen $minlen;
            
$this->maxlen $maxlen;
        }

        
# Must not start with underscore, must also be 1 character long
        
function validate ($string) {
            if (
Validator::validate ($string) == false) {
                return 
false;
            }

            if (
preg_match("/^[a-zA-Z0-9][a-zA-Z0-9_]*$/",$string) == 0) {
                return 
false;
            } else {
                return 
true;
            }
        }

        
# Length must be within range
        
function checkLimits ($string) {
            
$len strlen($string);
            if (
$len >= $this->minlen && $len <= $this->maxlen) {
                return 
true;
            } else {
                return 
false;
            }
        }

        
# No checklimits or translate
    
}

    
# A password class validator
    # A password may contain any characters
    # Must also be x characters long (default is 6)
    #     and less than y chars long (default is 20)
    # The md5sum of the password is returned from translate
    # Nothing is escaped from this because the md5 sum is safe
    
class PasswordValidator extends Validator {
        var 
$minlen;
        var 
$maxlen;

        
# Default length of 6
        
function PasswordValidator ($minlen=6$maxlen=20) {
            
$this->minlen $minlen;
            
$this->maxlen $maxlen;
        }

        
# No validation function, handled by Validator::validate

        # Check password length
        
function checkLimits ($string) {
            
$len strlen($string);
            if (
$len >= $this->minlen && $len <= $this->maxlen) {
                return 
true;
            } else {
                return 
false;
            }
        }

        
# Generate md5 sum
        
function translate ($string) {
            return 
md5($string);
        }
    }

    
# A relative path validator
    # Paths cannot contain .. or begin with /
    # Also, during translation, all links are resolved and
    # the final path is checked against the limiter again
    
class RelativePathValidator extends Validator {
        var 
$basepath;

        
# No default base path, / would be dangerous!
        
function RelativePathValidator ($basepath) {
            
# Basepath must be an absolute path and end in /
            # It also must be a directory
            
if (preg_match ('/^\/([0-9a-zA-Z_\/]|\.(?!\.))+\/$/',$basepath) == 0) {
                
# Fail closed
                # TODO:  report this somehow
                
unset($this->basepath);
            } else {
                
$this->basepath $basepath;
            }
        }

        
# String must not start with /, must not contain ..'s and
        # must only contain valid filename characters
        
function validate ($string) {
            if (
Validator::validate ($string) == false) {
                return 
false;
            }

            
# Fail if basepath is invalid
            
if (!isset($this->basepath)) {
                return 
false;
            }

            
# Check before the link resolution
            
if (preg_match ('/^(?!\/)([0-9a-zA-Z_\/]|\.(?!\.))+$/',$string) == 0) {
                return 
false;
            }

            return 
true;
        }

        
# No limiter function since these things need to be
        # performed in translate to prevent race condition

        
function translate ($string) {
            
# Fail if basepath is invalid
            
if (!isset($this->basepath)) {
                return 
false;
            }

            
# Resolve symlinks
            
$path $this->basepath;

            
# It's assumed explode will produce at least one element
            # Else it wouldn't have passed the validate
            
foreach (explode("/",$string) as $elem) {
                
$path .= $elem;
                
# Resolve symlinks until there are no more
                
while (is_link($path)) {
                    
$link readlink($path);
                    
# Bail on error
                    
if ($link == false) {
                        return 
false;
                    }

                    
# Replace last element with link target 
                    
if (substr($link,0,1) != "/") {
                        
$path substr($path,0,strrpos($path,"/")+1);
                        
$path .= $link;
                    } else {
                        
# Replace path with absolute link
                        
$path $link;
                    }
                }

                
# The path must be a directory
                
if (!is_dir($path)) {
                    return 
false;
                }

                
# Make sure there is a trailing /
                
if (substr($path,-1,1) != "/") {
                    
$path .= "/";
                }
            }

            
# Links can introduce .., check again
            
if (strpos($path,"..") != false) {
                return 
false;
            }

            
# Finally, make sure it's really the child of basepath
            
$pos strpos($path,$this->basepath);
            if (
$pos === false || $pos != 0) {
                return 
false;
            }

            return 
$path;
        }
    }
?>