Test if a directory is a sub directory of another folder

directory, filesystems, php

Solution

The following code works even if the last directory in the path doesn't exist yet. Any foul play or additional missing directories returns false.

The limit to this function is that it only works with paths that (mostly) already exist and directory names using standard English chars (/.hts/, /files.90.3r3/, /my_photos/) etc..)

function is_sub_dir($path = NULL, $parent_folder = SITE_PATH) {

    //Get directory path minus last folder
    $dir = dirname($path);
    $folder = substr($path, strlen($dir));

    //Check the the base dir is valid
    $dir = realpath($dir);

    //Only allow valid filename characters
    $folder = preg_replace('/[^a-z0-9\.\-_]/i', '', $folder);

    //If this is a bad path or a bad end folder name
    if( !$dir OR !$folder OR $folder === '.') {
        return FALSE;
    }

    //Rebuild path
    $path = $dir. DS. $folder;

    //If this path is higher than the parent folder
    if( strcasecmp($path, $parent_folder) > 0 ) {
        return $path;
    }

    return FALSE;
}

Problem

I am trying to create a function that will block access to some of my low level directories. For example, while building my site I don't want to allow uploads to be uploaded any lower than /var/www/html/site/uploads/ should I make a mistake while coding. This will also help to prevent a directory delete typo while removing cache files or whatever. This is easily done with realpath() and strcasecmp(). The problem is that I can't use realpath() to generate an absolute path because any calls to this function with directories that don't exist will return FALSE. Below is my best attempt at looking at paths to validate them. ``` function is_sub_dir($path = NULL, $parent_folder = NULL) { //Convert both to real paths //Fails if they both don't exist //$path = realpath($path); //$parent_folder = realpath($parent_folder); //Neither path is valid if( !$path OR !$parent_folder ) { return FALSE; } //Standarize the paths $path = str_replace('\\', '/', $path); $parent_folder = str_replace('\\', '/', $parent_folder); //Any evil parent directory requests? if(strpos($path, '/..') !== FALSE) { return FALSE; } //If the path is greater if( strcasecmp($path, $parent_folder) > 0 ) { return $path; } return FALSE; } //BAD FOLDER!!! var_dump(is_sub_dir('/var/www/html/site/uploads/../', '/var/www/html/site/uploads/')); ``` Does anyone know how to properly put file path blocks in place to guard against low-level folder access? :UPDATED: I want to further explain that this checking method will be used on multiple severs as well as in the creation of directories that are higher than a given directory. For example, in my uploads directory I wish to allow administrators to create new sub directories such as ...uploads/sub/. By figuring out a reliable way to insure that the directory given is in fact higher than the parent directory - I can feel safer allowing my admins to work with the file system in the uploads folder. So since I might need to verify that uploads/sub is higher than uploads/ before I create it I can't use realpath() because uploads/sub doesn't exist again. As for the actual location of the uploads folder that is figured on the fly by PHP. ``` define('UPLOAD_PATH', realpath(dirname(__FILE__))); ``` :UPDATE 2: I have an idea, what if I were to use realpath to compare the the whole path minus the last directory segment. Then even if that last directory segment still needed to be created - the rest of the path could be forced to match the minimum parent directory?

Original source