php.de

Zurück   php.de > Webentwicklung > PHP Einsteiger

PHP Einsteiger PHP Problemlösungen für Spracheinsteiger
Archive: 2004, 2004/2, 2005, 2005/2, 2006, 2007, 2008, 2009, 2010,

Antwort
 
LinkBack Themen-Optionen Thema bewerten
Alt 02.02.2012, 04:56  
Erfahrener Benutzer
 
Benutzerbild von mermshaus
 
Registriert seit: 14.06.2009
Beiträge: 1.711
PHP-Kenntnisse:
Fortgeschritten
mermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz sein
Standard Pfade normalisieren/kanonisieren

Hallo.

Ich arbeite daran, Verzeichnispfade mit Bestandteilen ".", ".." und "//" in eine normalisierte Form zu bringen.

realpath ist keine Option, da die Pfade nicht zwangsläufig existieren.

Beispielsweise folgende Umformungen sollen vorgenommen werden:

Code:
///dir1/../dir1/dir2/                    =>    /dir1/dir2
./dir1/.//dir2/../dir3//dir4/.././/..    =>    dir1
dir1/../../                              =>    ..
/./dir1/./dir2///../../..///             =>    /
Charakteristika:
  1. Relative und absolute Pfade sind zulässig.
  2. Relative Pfade beginnen nicht mit "./", sondern mit dem obersten Verzeichnisnamen ("dir" statt "./dir").
  3. Pfade enden niemals auf "/" (Ausnahme: Root-Pfad "/").
  4. Pfade können mit ".." beginnen.
  5. Eine leere Rückgabe (etwa zu den Eingaben "" und "dir/..") wird zu ".".
  6. "/.." wird zu "/".

Erläuterungen:

Zu 2 und 3: Anwender sollen gezwungen werden, beim Konkatenieren von Teilpfaden explizit einen Slash einzufügen. Die Idee ist, dass ein überzähliger Slash nie zu einem Fehler führt, ein vergessener aber schon.

PHP-Code:
$p '/dir1'  '/' 'dir2';  // OK (gewünschter Fall)
$p '/dir1/' '/' 'dir2';  // OK (dir1 endet versehentlich mit Slash)
$p '/dir1'  '/' '/dir2'// OK (dir2 beginnt versehentlich mit Slash)
$p '/dir1/' '/' '/dir2'// OK (beide "Fehler" treten auf) 
Auf diese Weise muss nicht ständig gerätselt werden, ob beide zu verbindenden Pfadteile passend mit Slashes ausgestattet sind oder nicht.

Zu 5: Hier wird der Punkt gewählt, um bei einer Konkatenation nicht versehentlich einen absoluten Pfad zu erzeugen.

PHP-Code:
$p ''  '/' 'dir2'// FEHLER
$p '.' '/' 'dir2'// OK 
Der Punkt ist als Pfadbestandteil zwischen zwei Slashes oder am Ende des Pfads hinter einem Slash zudem unproblematisch.

Ein PHPUnit-Test dazu, der einige Fälle abdeckt, die mir in den Sinn gekommen sind:

PHP-Code:
class NormalizePathTest extends PHPUnit_Framework_TestCase
{
    public function 
test()
    {
        
$f = function ($s) {
            return 
normalizePath($s);
        };

        
$this->assertEquals('.',     $f(''));
        
$this->assertEquals('.',     $f('.'));
        
$this->assertEquals('.',     $f('./'));
        
$this->assertEquals('/',     $f('/'));
        
$this->assertEquals('/',     $f('///'));
        
$this->assertEquals('/',     $f('//././/'));
        
$this->assertEquals('dir',  $f('dir'));
        
$this->assertEquals('/dir'$f('/dir'));
        
$this->assertEquals('/dir'$f('//dir'));
        
$this->assertEquals('dir',  $f('dir/'));
        
$this->assertEquals('.',  $f('./dir1/..'));
        
$this->assertEquals('.',  $f('././//./dir1/../dir2/..//'));
        
$this->assertEquals('dir',  $f('dir/./'));
        
$this->assertEquals('dir',  $f('dir/.//'));
        
$this->assertEquals('dir',  $f('./dir'));
        
$this->assertEquals('../dir'$f('../dir'));
        
$this->assertEquals('../dir'$f('./../dir'));

        
// ../ tests
        
$this->assertEquals('/dir1'$f('/dir/../dir1'));
        
$this->assertEquals('..',    $f('./dir/../dir1/../..///.//'));
        
$this->assertEquals('/',     $f('/dir1/dir2/dir3/../../..'));
        
$this->assertEquals('/dir1'$f('/dir1/dir2/dir3/dir4/../../../'));
        
$this->assertEquals('dir1',  $f('./dir1/dir2/dir3/dir4/../../../'));
        
$this->assertEquals('dir1',  $f('./dir1/.//dir2/../dir3//dir4/.././/..'));
        
$this->assertEquals('/',     $f('/../../'));

        
$this->assertEquals('/dir1/dir2'$f('///dir1/../dir1/dir2/'));
        
$this->assertEquals('dir1',       $f('./dir1/.//dir2/../dir3//dir4/.././/..'));
        
$this->assertEquals('..',         $f('dir1/../../'));
        
$this->assertEquals('/',          $f('  /./dir1/./dir2///../../../// '));

        
// http://www.php.net/manual/en/function.realpath.php#84012
        
$this->assertEquals('this/a/test/is'$f('this/a/./test/.///is'));
    }

Wer kein PHPUnit zur Hand hat, kann das hier einsetzen:

PHP-Code:
class PHPUnit_Framework_TestCase
{
    public function 
assertEquals($expected$actual)
    {
        if (
$expected !== $actual) {
            throw new 
Exception('FAILURE. Expected: "' $expected
                    
'" Actual: "' $actual '"');
        }
    }
}

$test = new NormalizePathTest();
$test->test(); 
Ich habe eine Implementation geschrieben, die die Tests besteht.

PHP-Code:
function normalizePath($path)
{
    if (!
is_string($path)) {
        throw new 
InvalidArgumentException('$path must be of type string');
    }

    
$path trim($path);

    
$isAbsolute = (substr($path01) === '/');

    
$newComponents = array();

    
$components explode('/'$path);

    
// Used to determine whether to remove a dir from the stack when ".." is
    // encountered
    
$namedDirDepth 0;

    
// "continue" means not to add the current path component to the new path
    
foreach ($components as $component) {
        if (
$component === '' || $component === '.') {
            continue;
        }

        if (
$component === '..') {
            if (
$namedDirDepth === && $isAbsolute) {
                
// "/.." becomes "/". Root is the topmost directory
                
continue;
            }

            if (
$namedDirDepth !== 0) {
                
$popped array_pop($newComponents);
                
$namedDirDepth--;

                if (
$popped !== null) {
                    if (
$popped !== '..') {
                        continue;
                    }

                    
// We popped a "..", push it back
                    
array_push($newComponents$popped);
                    
$namedDirDepth++;
                }
            }
        } else {
            
$namedDirDepth++;
        }

        
$newComponents[] = $component;
    }

    
$newPath = ($isAbsolute '/' '') . implode('/'$newComponents);

    if (
$newPath === '') {
        
$newPath '.';
    }

    return 
$newPath;

Beispielsweise auf der Doku-Seite zu realpath gibt es etliche ähnliche Implementationen, die aber meines Erachtens alle unvollständig oder fehlerhaft sind.

Meine Fragen dazu sind:
  1. Hat jemand Anmerkungen zur Vorgehensweise bei der Normalisierung?
  2. Gibt es eine Funktion, die das leistet, doch bereits irgendwo im PHP-Kern (oder anderswo)? Geht es einfacher?
  3. Findet jemand einen Fehler?

Meine Paranoia kennt beim Zusammenbau von Pfaden keine Grenzen.
__________________
Blog | Buch | Kaloa

Geändert von mermshaus (02.02.2012 um 05:01 Uhr).
mermshaus ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

Registriert seit: 21.08.2005
Beiträge: 4682
PHP-Kenntnisse:
Fortgeschritten

Alt 02.02.2012, 07:37  
Erfahrener Benutzer
 
Benutzerbild von tr0y
 
Registriert seit: 26.07.2010
Beiträge: 4.821
PHP-Kenntnisse:
Fortgeschritten
tr0y ist ein wunderbarer Anblicktr0y ist ein wunderbarer Anblicktr0y ist ein wunderbarer Anblicktr0y ist ein wunderbarer Anblicktr0y ist ein wunderbarer Anblicktr0y ist ein wunderbarer Anblicktr0y ist ein wunderbarer Anblicktr0y ist ein wunderbarer Anblick
tr0y eine Nachricht über MSN schicken
Standard

Die umformung ist die eine Sache, wie gewährleistes du das die Pfade auch valide sind ?

Wenn du in die Luft normalisieren willst ( ../../../../../../../ ), kennt deine Function nicht die Basis worauf der Pfad läuft oder was er überhaupt darstellt und würde entsprechend normalisiert ebendso auf "../../../../../../../" weiterlauten.

Mein Path-Normalizierer würde wohl in etwa so aussehen:
PHP-Code:
<?php

    
class virtualRealPath {
    
        protected
            
$_rawPath$_pathSep$_absolutePath false$_realPath false;
    
        public function 
__construct$pathToParse$pathSep '/' ) {
            
// detect separator length and type
            
if ( !is_string($pathSep) || strlen($pathSep) > || strlen($pathSep) < )
                throw new 
LogicException('Second argument of virtualRealPath::__construct()'.
                    
' must be an string with an length of 1');
            
            
// check path type
            
if ( is_string($pathToParse) ) {
                
$this->_rawPath explode($pathSep$pathToParse);
            }
            elseif( 
is_array($pathToParse) ) {
                
$this->_rawPath explode($pathSepjoin($pathSep$pathToParse));
            }
            else {
                throw new 
LogicException('First Argument must be an path-explaining'.
                    
' Array or a path-string.');
            }
            
            
// store response data
            
$this->_pathSep $pathSep;
            
$this->_absolutePath $pathToParse[0] === $pathSep;
        }
        
        public function 
validate() {
            
// create parser vehicle
            
$pObj = new virtualPathVehicle();
            
// drive the parse
            
$this->_realPath $pObj->drive$this->_rawPath );
            
            
// return vehicle for cachable possibility-checks
            
return $pObj;
        }
        
        public function 
getNormalizedPath() {
            
// return normalized path if there is a validated path, otherwise false
            
if ( $this->_realPath === false ) return false;
            else return 
join($this->_pathSep$this->_realPath);
        }
    
    }
    
    class 
virtualPathVehicle {
    
        public function 
__construct() {
            
/* insert presettings an Base Path mechanics here */
        
}
        
        public function 
drive( array $inboundPath ) {
            if ( 
count($inboundPath) === )
                throw new 
LogicException('First argument of virtualRealPath::__construct() runs into nothing');
            
            
/* insert base path mechanics here */
            
            
return $runpath $this->normalize($inboundPath);
        }
        
        protected function 
normalize( array $pathRoad ) {
            
// build base array
            
$spin = array();
            
            
// extract inbound array into base array
            
foreach ( $pathRoad as $item ) {
                switch( (string)
$item ) {
                    
// on root-node or mid-nodes
                    
case '':
                        
// on root-node append trailing spin
                        
if ( count($spin) === )
                            
$spin[] = '';
                        
// on mid-node append nothing
                        
break;
                    case 
'.'/* append nothing */ break;
                    case 
'..':
                        
// on backstepping
                        
if ( count($spin) > ) {
                            
// append backstep, if...
                            
$out array_pop($spin);
                            if ( 
$out === '..' ) {
                                
// there is allready one, append another one and append the given back
                                
array_push($spin$out);
                                
array_push($spin'..');
                            }
                            elseif ( 
$out === '' ) {
                                
array_push($spin'..');
                            }
                            
// there is none, drop therm and append nothig
                        
}
                        else 
// otherwise append backstep
                            
array_push($spin'..');
                        break;
                    default: 
                        
// if there is no path operator, append the item
                        
array_push($spin, (string)$item);
                }
            }
            
            
// give back result
            
return $spin;
        }
    
    }
Bei Risiken und Nebenwirkungen konsultieren sie das Handbuch und flamen sie den Entwickler. ( P.S.: is' hier im editor gefummelt, keine 100%ige Gewähr. )

Anwendung:
PHP-Code:
$o = new virtualRealPath('C:\a\b\c\d\e\..\..\.\..\.\f\n'"\\");
$o->validate();
echo 
$o->getNormalizedPath(); // "C:\a\b\f\n" 
Nochwas:

Wie schon bereits erwähnt kannst du zwar einfach vor dir hin normalisieren aber die Pfade an sich basieren auf irgend eine Ebene ( Root-Knoten, Root-Verzeichnis, ... ), sie sollten also auch real validiert werden bevor du sie irgendwo hinklebst. Hier als Beispiel mal Windows:

base path:
Code:
C:\a\b\c
parse path:
Code:
../../../../../
resultiert in eine Pfad-Kollision, da die Backsteps des Pfads tiefer gehen als der Root-Pfad. Windows selbst nutzt hier das Root-Dir als infinitiven Knoten und gibt bei

base path:
Code:
C:\
parse path:
Code:
..\
den Root-Directory...
Code:
C:\
... zurück

Seh ich aber als "lazy implemention" an, aus "strict" sicht bist du dann eine ebene über dem Root-Directory, aus Windowssicht dann in der Device-Ebene:

hd0
Code:
c:\
parse-pfad
Code:
..\
device-grid
Code:
\\
Selbiges würde für xpath-"Pfade" zählen oder für andere virtuelle Entitäten für die es mitunter wichtig sein kann das der eingehende Pfad-Selektor "strict" behandelt wird.
__________________
Lasse mir ohne Anwendung von Gewalt Dinge schenken, Amazon weiß darüber bald mehr.

Geändert von tr0y (02.02.2012 um 09:44 Uhr). Grund: *code einfüg*
tr0y ist offline   Mit Zitat antworten
Alt 02.02.2012, 10:13  
Erfahrener Benutzer
 
Registriert seit: 10.01.2010
Beiträge: 382
PHP-Kenntnisse:
Fortgeschritten
ChrisvA befindet sich auf einem aufstrebenden Ast
Standard

Ohne jetzt mir die Implementationen genau angesehen zu haben, aber ich würde da folgendermaßen vorgehen:
Schritt1:
Zunächst ./ zu entfernen und // durch / ersetzten.
Diesen Schritt so lange wiederholen, bis es keine Ersetzungen mehr zu machen gibt.
Schritt2:
Anschließend nach dem Muster
(dir)/../
suchen und durch nichts ersetzten.
Schritt 2 wieder so oft wie Möglich wiederholen.

Danach sollte eigentlich nicht überflüssiges mehr im Pfad sein.
ChrisvA ist offline   Mit Zitat antworten
Alt 02.02.2012, 17:20  
Erfahrener Benutzer
 
Benutzerbild von mermshaus
 
Registriert seit: 14.06.2009
Beiträge: 1.711
PHP-Kenntnisse:
Fortgeschritten
mermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz sein
Standard

Danke für eure Antworten und den Code.



@tr0y:

Am Rande: Die Validierung der einzelnen Pfad-Komponenten etwa auf illegale Zeichen ist vielleicht auch noch ein Thema. Dazu (und zu Nicht-Unix-Systemen) habe ich mir noch keine Gedanken gemacht.

Dass die Pfade in der Luft hängen ("../.." bleibt "../.."), ist beabsichtigt oder besser gesagt nicht zu vermeiden.

Eine Zielanwendung sollen Pfadangaben in Konfigurationsdateien sein.

Code:
export.dir = "../../export"
Das Ausgangsverzeichnis ist hierbei meinetwegen das Verzeichnis der Konfigurationsdatei.

PHP-Code:
$exportPath normalizePath($configFileDir '/' $config['export.dir']); 
Das Ziel ist die Generierung eines möglichst „lesbaren“ Pfads. Die Verankerung übernimmt dabei letztlich die Anwendung an einer nicht festgelegten Stelle.

Der Pfad wird hier im Beispiel neu angelegt und dient zudem als Basis für weitere Pfade unterhalb des Export-Verzeichnisses, die auch alle noch nicht existieren.

Außerdem ist mir wichtig, dass die Funktion geschachtelt werden kann, dass also auch dieser Code zum gleichen Ergebnis führt:

PHP-Code:
$exportPath np(np($configFileDir) . '/' np($config['export.dir'])); 
Wie ich das letztlich in ein API gieße, habe ich noch nicht überlegt. Pfade als Objekte wären eine Möglichkeit, die aber möglicherweise etwas unhandlich ist. Leute hacken auf Pfaden zu gerne mit String-Funktionen rum. Andererseits wäre ein Pfadobjekt wohl am saubersten.

Mit einigen fast ausschließlich kosmetischen Anpassungen schafft es dein Code aber durch alle Tests außer denen, die vom Root aus noch weiter nach oben wollen ("/.."). Den Algorithmus werde ich noch mal genau anschauen, auch wenn wir grundsätzlich natürlich denselben Ansatz nutzen.

Zitat:
Seh ich aber als "lazy implemention" an, aus "strict" sicht bist du dann eine ebene über dem Root-Directory
Ob es günstiger wäre, an der Stelle eine Exception zu werfen, habe ich auch noch nicht entschieden.

Ich richte mich derzeit danach, wie meine System-Konsole arbeitet:

Code:
~$ cd /../..////./home/marc////Desktop/
~/Desktop$


@ChrisvA:

Die regulären Ausdrücke tun sich leider schwer mit solchen Eingaben:

Code:
/../../dir    =>    /dir
dir/          =>    dir
dir/.         =>    dir
dir/./        =>    dir
Das wird für meinen Geschmack zu viel Gefrickel, bis diese Fälle alle abgedeckt sind. Die „sequenzielle“ Lösung hat damit keine Probleme.

Versuch:

PHP-Code:
function cva($path)
{
    
$tmp trim($path);

    
// Replace multiple slashes by one
    
$tmp preg_replace('~/{2,}~''/'$tmp);

    
// Remove "./" at start or after slash
    
$tmp preg_replace('~(?<=/|^)\./~'''$tmp);

    
// Remove "dir/.." sequences
    
while (=== preg_match('~\w+/\.{2}/?~'$tmp)) {
        
$tmp preg_replace('~\w+/\.{2}/?~'''$tmp);
    }

    
// Fix "///" or "dir/"
    
if ($tmp !== '/' && $tmp !== '') {
        
$tmp rtrim($tmp'/');

        if (
$tmp === '') {
            
$tmp '/';
        }
    }

    if (
$tmp === '') {
        
$tmp '.';
    }

    return 
$tmp;

Erweiterter Test:

PHP-Code:
class NormalizePathTest extends PHPUnit_Framework_TestCase
{
    public function 
test()
    {
        
$f = function ($s) {
            return 
cva($s);
        };

        
$this->assertEquals('.',     $f(''));
        
$this->assertEquals('.',     $f('.'));
        
$this->assertEquals('.',     $f('./'));
        
$this->assertEquals('/',     $f('/'));
        
$this->assertEquals('/',     $f('///'));
        
$this->assertEquals('/',     $f('//././/'));
        
$this->assertEquals('dir',  $f('dir'));
        
$this->assertEquals('/dir'$f('/dir'));
        
$this->assertEquals('/dir'$f('//dir'));
        
$this->assertEquals('dir',  $f('dir/'));
        
$this->assertEquals('dir',  $f('dir/.'));
        
$this->assertEquals('dir',  $f('dir/.///'));
        
$this->assertEquals('.',  $f('./dir1/..'));
        
$this->assertEquals('.',  $f('././//./dir1/../dir2/..//'));
        
$this->assertEquals('dir',  $f('dir/./'));
        
$this->assertEquals('dir',  $f('dir/.//'));
        
$this->assertEquals('dir',  $f('./dir'));
        
$this->assertEquals('../dir'$f('../dir'));
        
$this->assertEquals('../dir'$f('./../dir'));

        
// ../ tests
        
$this->assertEquals('/dir1'$f('/dir/../dir1'));
        
$this->assertEquals('..',    $f('./dir/../dir1/../..///.//'));
        
$this->assertEquals('/',     $f('/dir1/dir2/dir3/../../..'));
        
$this->assertEquals('/dir1'$f('/dir1/dir2/dir3/dir4/../../../'));
        
$this->assertEquals('dir1',  $f('./dir1/dir2/dir3/dir4/../../../'));
        
$this->assertEquals('dir1',  $f('./dir1/.//dir2/../dir3//dir4/.././/..'));
        
$this->assertEquals('/',     $f('/../../'));
        
$this->assertEquals('/dir',  $f('/../../dir/'));
        
$this->assertEquals('../..'$f('../../'));

        
$this->assertEquals('/dir1/dir2'$f('///dir1/../dir1/dir2/'));
        
$this->assertEquals('dir1',       $f('./dir1/.//dir2/../dir3//dir4/.././/..'));
        
$this->assertEquals('..',         $f('dir1/../../'));
        
$this->assertEquals('/',          $f('  /./dir1/./dir2///../../../// '));

        
// http://www.php.net/manual/en/function.realpath.php#84012
        
$this->assertEquals('this/a/test/is'$f('this/a/./test/.///is'));
    }

__________________
Blog | Buch | Kaloa
mermshaus ist offline   Mit Zitat antworten
Alt 02.02.2012, 17:50  
Supermoderator HD
 
Benutzerbild von Manko10
 
Registriert seit: 16.03.2008
Beiträge: 8.695
PHP-Kenntnisse:
Fortgeschritten
Manko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende Zukunft
Standard

Pfadnormalisierungen ohne real existierende Ordner oder Dateien sind eigentlich relativ sinnlos.
jeder Pfad hat irgendwo ein Wurzelverzeichnis, bei dem es nicht mehr weiter geht. Deine Pfade können aber theoretisch in der Hierarchie unendlich nach oben steigen. Wie willst du das unterbinden?
/.. ist nun einmal eindeutig Quatsch und damit äquivalent zu /. Allerdings kannst du bei der Form ../.. nicht mehr erkennen, ob der Pfad gültig sein könnte oder nicht. D.h. du müsstest von vornherein ein Basis-Verzeichnis angeben, über das Pfade nicht hinausdürfen. Hierfür müsstest du jeweils die einzelnen Levels zählen.
Ferner weiß du nicht, was aus symbolischen Links wird. Nun könnte ./bla durchaus ein gültiger Pfad innerhalb deines Basis-Verzeichnisses sein, aber was ist, wenn er auf einen symbolischen Link verweist, der zu einem ganz anderen Verzeichnis außerhalb deines Basis-Verzeichnisses führt? Mit realpath() kannst du das prüfen, mit einer hypothetischen Normalisierung eines "in der Luft hängenden" Teilpfades ist das jedoch nicht möglich.

Mein Ansatz wäre da eher, auf existierende Dateien zu setzen. Relative Pfade kann man ja immer noch dadurch validieren, dass man die Überschneidung des Pfads zum Basis-Verzeichnis und des gerade überprüften realpaths herausrechnet. Das ist mit String-Operationen ja recht einfach.
__________________
Refining Linux Advent Calendar series “24 Outstanding ZSH Gems
Manko10 ist offline   Mit Zitat antworten
Alt 02.02.2012, 19:28  
Erfahrener Benutzer
 
Registriert seit: 10.01.2010
Beiträge: 382
PHP-Kenntnisse:
Fortgeschritten
ChrisvA befindet sich auf einem aufstrebenden Ast
Standard

Zitat:
Zitat von mermshaus Beitrag anzeigen
[...]
Code:
/../../dir    =>    /dir
[...]
[...]
PHP-Code:
function cva($path)
{
    
$tmp trim($path);

    
// Replace multiple slashes by one
    
$tmp preg_replace('~/{2,}~''/'$tmp);

    
// Remove "./" at start or after slash
    
$tmp preg_replace('~(?<=/|^)\./~'''$tmp);

    
// Remove "dir/.." sequences
    
while (=== preg_match('~\w+/\.{2}/?~'$tmp)) {
   
//Hier wuerde ich auf den Parameter Count setzen, das spart das Pruefen
        
$tmp preg_replace('~\w+/\.{2}/?~'''$tmp);
    }

    
// Fix "///" or "dir/"
    
if ($tmp !== '/' && $tmp !== '') {
        
$tmp rtrim($tmp'/');

        if (
$tmp === '') {
            
$tmp '/';
        }
    }

    if (
$tmp === '') {
        
$tmp '.';
    }

    return 
$tmp;

[...]
Da ich selber bei deinen Regulären Ausdrücken an meine Kenntnissgrenzen komme, kann ich nur eine Vermutung abgeben, dass der Fehler beim Entfernen von ./ liegt.
Außerdem fehlt der Fall /dir/..

Aber ich kann verstehen wenn dir eine andere Lösung besser gefällt. Wollte eben nur einen recht kurzen Ansatz liefern, der mir nicht ganz so komplex erschien.
ChrisvA ist offline   Mit Zitat antworten
Alt 02.02.2012, 20:55  
Erfahrener Benutzer
 
Benutzerbild von mermshaus
 
Registriert seit: 14.06.2009
Beiträge: 1.711
PHP-Kenntnisse:
Fortgeschritten
mermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz seinmermshaus kann auf vieles stolz sein
Standard

@Manko:

Zitat:
Pfadnormalisierungen ohne real existierende Ordner oder Dateien sind eigentlich relativ sinnlos.
Für so was wie Existenzprüfung oder das Auflösen von symbolischen Links? – Ja. Das ist aber nicht das Ziel.

Mein konkretes Anliegen ist derzeit die Ausgabe von Meldungen mit zusammengesetzten Pfaden (ein Anteil "../../export/helloworld" kommt aus einer Konfigurationsdatei) wie „Trying to create directory: /home/user/projects/export/helloworld“, wobei alles ab „export“ nicht existiert. „Trying to create directory: /home/user/projects/demos/helloworld/../../export/helloworld“ würde ich gern vermeiden.

Eine normalisierte Form ist auch für die Funktion günstig, die das Verzeichnis dann tatsächlich erstellen soll.

Dass die Funktion auch in der Lage sein soll, relative Pfade zu normalisieren, ist der Wunsch nach einer allgemeinen Lösung. Es spricht in meinen Augen nichts dagegen.

Dass es wahrscheinlich sinnvoll ist, existierende Pfade in der kanonisch absoluten Variante anzugeben, ist eine andere Frage. Das ist eine relevante Überlegung bei jedem Einsatz von Pfaden.

Zitat:
jeder Pfad hat irgendwo ein Wurzelverzeichnis, bei dem es nicht mehr weiter geht. Deine Pfade können aber theoretisch in der Hierarchie unendlich nach oben steigen. Wie willst du das unterbinden?
Ich brauche das nicht zu unterbinden. Ein Pfad, der an die falsche Stelle zeigt, ist so oder so ein Problem, auch bei absoluten Pfaden.

Zitat:
/.. ist nun einmal eindeutig Quatsch und damit äquivalent zu /.
Das System verarbeitet es auf eine bestimmte Weise. Würde es einen Fehler werfen, würde ich auch einen werfen.

Zitat:
Allerdings kannst du bei der Form ../.. nicht mehr erkennen, ob der Pfad gültig sein könnte oder nicht.
Also, es war jetzt nicht meine Absicht, den Einsatz von relativen Pfaden zu empfehlen. Ich sehe aber auch keinen Grund, die willkürlich von der Normalisierung auszuschließen.

Dass ein Pfad, mit dem auf Dateisystemebene gearbeitet wird, absolut sein muss, ist logisch. Umgekehrt macht das relative Pfadangaben aber nicht überflüssig.

Zitat:
D.h. du müsstest von vornherein ein Basis-Verzeichnis angeben, über das Pfade nicht hinausdürfen. Hierfür müsstest du jeweils die einzelnen Levels zählen.
Die Validierung eines Pfads ist Sache der Anwendung, die die Funktion nutzt. Die Funktion führt lediglich einen Algorithmus durch.

Zitat:
Mein Ansatz wäre da eher, auf existierende Dateien zu setzen.
Es muss auch möglich sein, die Position einer zu erzeugenden Datei anzugeben. Für die Erzeugung brauche ich zwar die Normalisierung nicht zwingend, sie ist aber hilfreich.

Für eine möglichst lesbare Ausgabe von nicht existierenden Pfaden brauche ich sie aber.



@ChrisvA:

Ja, wie gesagt, meiner Einschätzung nach müssten bei regulären Ausdrücken so viele Feinheiten beachtet werden, dass gegenüber dem Ansatz von tr0y oder mir am Ende nichts gewonnen ist – wahrscheinlich wohl im Gegenteil.

Das ist übrigens ein häufig auftretender Effekt bei Regex. 97 % der Einsatzfälle schafft man locker flockig aus dem Handgelenk, der Rest wird richtig knifflig.
__________________
Blog | Buch | Kaloa
mermshaus ist offline   Mit Zitat antworten
Alt 02.02.2012, 21:07  
Supermoderator HD
 
Benutzerbild von Manko10
 
Registriert seit: 16.03.2008
Beiträge: 8.695
PHP-Kenntnisse:
Fortgeschritten
Manko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende ZukunftManko10 hat eine strahlende Zukunft
Standard

Zitat:
Mein konkretes Anliegen ist derzeit die Ausgabe von Meldungen mit zusammengesetzten Pfaden (ein Anteil "../../export/helloworld" kommt aus einer Konfigurationsdatei) wie „Trying to create directory: /home/user/projects/export/helloworld“, wobei alles ab „export“ nicht existiert. „Trying to create directory: /home/user/projects/demos/helloworld/../../export/helloworld“ würde ich gern vermeiden.
Okay, dann habe ich dein Anliegen falsch interpretiert. Wenn es nicht um sicherheitskritische Dinge geht, sondern einfach nur um die Anzeige simpler Pfade, dann kannst du natürlich auch Pfade normalisieren, die nicht existieren.
In dem Falle würde ich das auch in etwa so machen wie du es oben tust: den Base-Path in seine Bestandteile splitten und dann den relativen Pfad durchgehen und dessen Einzelteile auf den Basepath-Stack schmeißen. Triffst du dabei auf . tust du schlicht nichts, triffst du auf .., schmeißt du das letzte Element wieder vom Stack runter.
__________________
Refining Linux Advent Calendar series “24 Outstanding ZSH Gems
Manko10 ist offline   Mit Zitat antworten
Alt 02.02.2012, 21:32  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 35.879
PHP-Kenntnisse:
Fortgeschritten
nikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunft
Standard

tl;dr

ich würde jedenfalls damit anfangen:

PHP-Code:
$dir rtrim (preg_replace ('#/./|/{2,}#' '/' $dir '/') , '/'); // je nach Wunsch . '/' 
Die Verwendung eines Root-Pfades in der Config würde ich mir auf jeden Fall offen halten. Und dann abprüfen: Wenn erstes Zeichen kein Slash, dann Basispfad davorsetzen.
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 02.02.2012, 21:59  
Moderator
 
Benutzerbild von Chriz
 
Registriert seit: 11.05.2008
Beiträge: 6.247
Chriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer Anblick
Standard

Zitat:
Zitat von mermshaus Beitrag anzeigen
1. Relative und absolute Pfade sind zulässig.
2. Relative Pfade beginnen nicht mit "./", sondern mit dem obersten Verzeichnisnamen ("dir" statt "./dir").
3. Pfade enden niemals auf "/" (Ausnahme: Root-Pfad "/").
4. Pfade können mit ".." beginnen.
5. Eine leere Rückgabe (etwa zu den Eingaben "" und "dir/..") wird zu ".".
"/.." wird zu "/".
Einige Anmerkungen dazu:

Deine Vorbedingungen sind schön und gut, aber wozu? Das erhöht nur die Fehleranfälligkeit und nimmt Implikationen vor ("meine Pfade dürfen nicht mit ./ beginnen"), die man eigentlich nicht vornehmen braucht. Außerdem wird die Funktion dann bei Webpfaden ungültig, wenn du den führenden Slash nur alleinstehend akzeptierst.

1. ACK, aber dann bitte alle gültigen Varianten
2. Wozu die Einschränkung? Du reduzierst die Verwendung damit auf deine Schreibweise, die ich beispielsweise schon nicht nachvollziehen kann
3. Damit wird die Funktion unverwendbar für Webpfade, wieder eine unnötige Einschränkung. Wenn du Pfadinjection o.ä. vermeiden willst, solltest du NACH der Umwandlung gesondert prüfen, ob du den gültigen Dateipfad verlassen hast (~ open_base_dir restriction)
4. ACK, dass "" zu "." wird, aber warum wird "../" zu "/"? Das ist falsch.

Zusatz: Die Angabe "//" ist einfach falsch! Höchstens "\\" könnte eine gültige Windowsnetzwerkadresse sein, aber falsch bleibt falsch => Exception.

Ich würde es wie folgt aufbauen:
PHP-Code:
<?php
define
("DIRECTORY_SEPARATOR_INVERSE"DIRECTORY_SEPARATOR == "/" "\\" "/");
$dir "../alpha/beta/../gamma/";

$dirString str_replace(DIRECTORY_SEPARATOR_INVERSEDIRECTORY_SEPARATOR$dir);
$dirString rtrim($dirStringDIRECTORY_SEPARATOR); // eigentlich erlaubst du damit doch "//" am Ende, evtl. mit strrpos() und substr() abfangen
$dirSplit explode(DIRECTORY_SEPARATOR$dirString);
if (
$dirSplit[0] === "") {
  
array_shift($dirSplit); // leading empty-string removal
}
// consistency check
$splitter array_count_values($dirSplit);
if (isset(
$splitter["."]) && $splitter["."] > 1) {
  throw new 
Exception("invalid amount of '.' directory path values");
}
if (isset(
$splitter[""])) { // empty string
  
throw new Exception("empty directory path found"); // siehe Zusatz
}
// hier Algorithmus zum Ersetzen von ../
?>
Ich wäre wirklich nur da streng, wo es ein Fehler bzw. schlampig ist ("//"). Ich mache es aber gelegentlich so, dass ich nur dann fies und gemein Exceptions werfe, wenn jemand die faire Chance hatte, den Fehlerfall abzuwenden, mit einer exists/has/is()-Methode:

PHP-Code:
<?php
class PathNormalize {
  public function 
isNormalizable($path) {
   
$path $this->_normalize($pathfalse);
   return 
is_string($path);
  }
  
// @throws Exception
  
public function normalize($path) {
   return 
$this->_normalize($pathtrue);
  }
  protected function 
_normalize($path$exception) {
    
// normale prüfung und umwandlung

    // inline-fehlerbehandlungen auf diese Art
    
if ($exception) { 
      throw new 
Exception("..");
    }
    return 
self::ERR_EMPTY_DIRPATH// int
  
}
}
?>
Wer trotz isNormalizable() die Methode normalize() dann noch mit Garbage füttert, bekommt halt die Exception.
__________________
"Nuschel ich?" - "Was?"
Chriz ist offline   Mit Zitat antworten
Antwort


Themen-Optionen
Thema bewerten
Thema bewerten:

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are an
Gehe zu

Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
[Erledigt] In Basispfad wechseln und von dort Pfade aufrufen Phil7789 PHP Einsteiger 4 11.09.2011 21:24
[Erledigt] Php Pfade S@ndviper PHP Einsteiger 17 18.08.2011 13:31
[Erledigt] Pfade für Anwendung definieren chunky PHP Einsteiger 7 09.05.2011 17:49
Apache und relative Pfade Cyron Server, Hosting und Workstations 5 09.11.2010 07:35
Pfade in der Webentwicklung Asipak Wiki Diskussionsforum 0 30.04.2010 08:06
Schrägstriche und Pfade chunky PHP Tipps 2010 10 19.02.2010 07:53
relative Pfade von Grafiken bei modrewrite jjkkll HTML, Usability und Barrierefreiheit 1 03.12.2009 16:43
filemtime+relative Pfade kanti PHP Tipps 2008 5 19.12.2008 08:17
PHP: Falsche Pfade bei include/require ohne include_path Zergling-new Tutorials 4 20.05.2008 09:55
welche pfade denn nun für welche funktion? Promaetheus PHP Tipps 2006 6 08.11.2006 21:33
opendir() auf externe pfade anwenden PHP-Fortgeschrittene 4 14.09.2005 00:10
Pfade nicht Erkannt Server, Hosting und Workstations 1 29.07.2005 13:56
Pfade in einem Formular angeben HTML, Usability und Barrierefreiheit 2 05.01.2005 10:58
Nested Set Trees und Pfade Datenbanken 0 16.12.2004 16:51
Wie gebe ich die Pfade bei lokaler installation an??? PHP Tipps 2004 1 23.08.2004 11:29

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
pfad normalisieren php, kanonisierung php, php extends slashes, kanonisierung normalisierung, pfad normalisieren, php slash im pfad zählen, php pfad normalisieren, mermshaus pfade, php funktion kanonisieren, kanonisieren javascript

Alle Zeitangaben in WEZ +2. Es ist jetzt 03:38 Uhr.




Powered by vBulletin® Version 3.7.2 (Deutsch)
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
Aprilia-Forum, Aquaristik-Forum, Liebeskummer-Forum, Zierfisch-Forum, Geizkragen-Forum