<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\controllers\Controller;
use seekquarry\yioop\controllers\StaticController;
use seekquarry\yioop\library\UnitTest;
/**
* Checks the parts of the controller every controller inherits: the
* cleaning of values that came from a person, and the reduction of a folder
* path they named to one that cannot reach outside where it is resolved.
*
* Wiki resources live in folders under a page, and a person names a folder
* when moving a resource or when looking inside one. A path naming the
* parent folder repeatedly could otherwise reach the rest of the disk, so
* these cases pin down that the reduction resolves those steps rather than
* striking the characters out of the text, which leaves ways through.
*
* @author Chris Pollett
*/
class ControllerTest extends UnitTest
{
/**
* A controller whose inherited cleaning is under test.
* @var StaticController
*/
public $controller;
/**
* Builds a controller without running its constructor, since the
* cleaning works on a value handed to it and reads neither disk nor
* database.
*/
public function setUp()
{
$reflection = new \ReflectionClass(StaticController::class);
$this->controller = $reflection->newInstanceWithoutConstructor();
}
/**
* Lets go of the controller so one case does not leak into the next.
*/
public function tearDown()
{
$this->controller = null;
}
/**
* An ordinary path is left as it stands, apart from writing every
* separator the one way and dropping empty steps.
*/
public function ordinaryPathTestCase()
{
$this->assertEqual(Controller::normalizePath("docs/images"),
"docs/images", "a plain path is unchanged");
$this->assertEqual(Controller::normalizePath("docs\\\\images"),
"docs/images", "a backslash is written as a separator");
$this->assertEqual(Controller::normalizePath("/docs//images/"),
"docs/images", "empty steps and edge separators are dropped");
$this->assertEqual(Controller::normalizePath("docs/./images"),
"docs/images", "a step naming the folder itself is dropped");
}
/**
* A step naming the parent folder removes the step before it, and one
* with nothing before it is dropped, so no path reaches above where it
* starts.
*/
public function parentStepsResolveTestCase()
{
$this->assertEqual(Controller::normalizePath("docs/../images"),
"images", "a parent step removes the folder before it");
$this->assertEqual(Controller::normalizePath("../../etc"), "etc",
"parent steps at the start have nothing to remove");
$this->assertEqual(Controller::normalizePath("a/b/../../../c"), "c",
"more parent steps than folders still cannot reach outside");
$this->assertEqual(Controller::normalizePath(".."), "",
"a path that is only a parent step comes back empty");
}
/**
* Asking the cleaner for a path gives back the reduced form, so a
* caller reading a folder name out of a request need not remember to
* reduce it separately.
*/
public function cleanPathTypeTestCase()
{
$this->assertEqual($this->controller->clean("docs/../images",
"path"), "images", "the cleaner reduces a path it is given");
$this->assertEqual($this->controller->clean("../../etc", "path"),
"etc", "the cleaner leaves no way above where it starts");
$this->assertEqual($this->controller->clean("docs/images", "path"),
"docs/images", "a plain path comes back unchanged");
}
/**
* Paths written to defeat striking the two-dot sequence out of the text
* are reduced to names that stay inside, since the reduction reads whole
* steps rather than characters.
*/
public function textStrippingDefeatsTestCase()
{
$this->assertEqual(Controller::normalizePath("....//etc"),
"..../etc", "four dots name a folder, not two parent steps");
$this->assertEqual(Controller::normalizePath(".../....//secret"),
".../..../secret", "runs of dots are ordinary folder names");
$this->assertTrue(
strpos(Controller::normalizePath("../../../../etc/passwd"),
"..") === false, "no parent step survives the reduction");
}
}