Skip to content
← Back to Functions
Code

Path Safety Resolver

Resolves a requested path inside an approved base directory and rejects traversal.

Function signature

ogResolveSafePath(base_path, requested_path, options = array())

Categories

  • File and Upload Safety

Parameters

base_pathApproved root directory used to contain all local file operations.requested_pathCaller-supplied relative path to resolve safely under the base directory.optionsOptional documented policy controls for the helper.

Return value

Public-safe status string returned by the function for controller branching or logging.

  • success
  • message
  • data

Compatibility

Existing function name, slug, path, and call order preserved; advertised metadata corrected to the actual source behavior.

Minimum PHP version: 7.4

Security notes

Use caller-owned allowlists and context-specific escaping; validate file paths, routes, email tokens, cart totals, discount rules, and tax-region rules before production use.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Resolves a requested path inside an approved base directory and rejects traversal.
 *
 * Primary use case: File managers, downloads, imports.
 * Typical inputs: base path, requested path.
 * Typical output: safe absolute path or error.
 *
 * Implementation note: Use realpath carefully and handle non-existing target paths.
 *
 * @return array Structured result data with success, message, and data keys.
 */
function ogResolveSafePath($base_path, $requested_path, $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	$base_path = (string)$base_path;
	$requested_path = trim((string)$requested_path);
	if (!is_array($options)) {
		$options = array();
	}

	$base_real = realpath($base_path);
	if ($base_real === false || !is_dir($base_real)) {
		$result['message'] = 'Approved base path is missing or is not a directory.';
		return $result;
	}

	if (empty($requested_path) || strpos($requested_path, "\0") !== false) {
		$result['message'] = 'Requested path is invalid.';
		return $result;
	}

	$requested_path = str_replace('\\', '/', $requested_path);
	if (preg_match('/(^|\/)\.\.(\/|$)/', $requested_path)) {
		$result['message'] = 'Path traversal was rejected.';
		return $result;
	}
	if (preg_match('/^[a-zA-Z]:\//', $requested_path) || substr($requested_path, 0, 1) == '/') {
		$result['message'] = 'Absolute requested paths are not accepted.';
		return $result;
	}

	$must_exist = true;
	if (isset($options['must_exist']) && $options['must_exist'] === false) {
		$must_exist = false;
	}

	$target_path = $base_real . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $requested_path);
	$target_real = realpath($target_path);
	if ($target_real === false) {
		if ($must_exist) {
			$result['message'] = 'Requested path does not exist.';
			return $result;
		}

		$parent_real = realpath(dirname($target_path));
		if ($parent_real === false || strpos($parent_real, $base_real) !== 0) {
			$result['message'] = 'Target parent is outside the approved base path.';
			return $result;
		}

		$target_real = $parent_real . DIRECTORY_SEPARATOR . basename($target_path);
	} else {
		if (strpos($target_real, $base_real) !== 0) {
			$result['message'] = 'Resolved path is outside the approved base path.';
			return $result;
		}
		if (is_link($target_real)) {
			$result['message'] = 'Symbolic links are not accepted.';
			return $result;
		}
	}

	$result['success'] = true;
	$result['message'] = 'Safe path resolved.';
	$result['data'] = array(
		'base_path' => $base_real,
		'requested_path' => $requested_path,
		'safe_path' => $target_real,
		'exists' => file_exists($target_real)
	);

	return $result;
}