php runtime and server environment
Protocols And Wrappers
PHP stream wrappers let filesystem-style functions work with different kinds of resources. The same functions that read a local file can also read from memory streams, HTTP URLs, compressed data, and special PHP streams.
This is powerful, but it is also a common source of security mistakes. If user input can control a path or URL passed to file_get_contents(), fopen(), include, or similar functions, wrappers can change what the code actually reads.
Seeing registered wrappers
You can ask PHP which wrappers are available:
<?php
declare(strict_types=1);
$wrappers = stream_get_wrappers();
foreach (['file', 'http', 'https', 'php', 'data'] as $name) {
echo $name . ': ' . (in_array($name, $wrappers, true) ? 'available' : 'missing') . PHP_EOL;
}
// Prints:
// file: available
// http: available
// https: available
// php: available
// data: available
The exact list depends on extensions and PHP configuration.
Common wrappers
The file:// wrapper reads local files. Most local paths use it implicitly:
<?php
declare(strict_types=1);
$path = __FILE__;
echo basename($path) . PHP_EOL;
echo str_starts_with(file_get_contents($path), '<?php') ? 'php file' : 'other file';
echo PHP_EOL;
// Prints:
// article-example.php
// php file
The filename in the example output is illustrative. When you run similar code from a real file, basename(__FILE__) prints that file's actual name.
The php:// wrapper exposes special streams:
<?php
declare(strict_types=1);
$handle = fopen('php://temp', 'w+');
if ($handle === false) {
throw new RuntimeException('Could not open temporary stream.');
}
fwrite($handle, 'temporary data');
rewind($handle);
echo stream_get_contents($handle) . PHP_EOL;
// Prints:
// temporary data
php://temp is useful for temporary data that may spill to disk after a memory threshold. php://memory stays in memory. php://input reads the raw HTTP request body in web requests, which is common for JSON APIs.
The data:// wrapper can represent inline data:
<?php
declare(strict_types=1);
$value = file_get_contents('data://text/plain,Hello%20PHP');
if ($value === false) {
throw new RuntimeException('Could not read data stream.');
}
echo $value . PHP_EOL;
// Prints:
// Hello PHP
Do not treat this as harmless if users can control it. A wrapper that looks like a string can still cause surprising behaviour in code that expects a normal file path.
Remote wrappers
If allow_url_fopen is enabled, functions such as file_get_contents() can read HTTP and HTTPS URLs:
<?php
declare(strict_types=1);
echo 'allow_url_fopen: ' . ini_get('allow_url_fopen') . PHP_EOL;
// Prints:
// allow_url_fopen: 1
For serious HTTP integrations, prefer a proper HTTP client. Remote wrappers have limited ergonomics for status codes, retries, redirects, authentication, logging, testing, and structured errors.
Wrapper security
Never pass unsanitised user input directly into file or include operations:
<?php
declare(strict_types=1);
function isAllowedLocalTemplate(string $name): bool
{
$allowed = ['home', 'account', 'help'];
return in_array($name, $allowed, true);
}
foreach (['home', 'php://filter/resource=index.php'] as $template) {
echo $template . ': ' . (isAllowedLocalTemplate($template) ? 'allowed' : 'blocked') . PHP_EOL;
}
// Prints:
// home: allowed
// php://filter/resource=index.php: blocked
An allow-list is safer than trying to remove dangerous characters or protocols. This is especially important around include, require, template loading, file downloads, image processing, and import tools.
Checking a scheme
When a value is supposed to be a URL or stream URI, parse it and decide what schemes are allowed.
<?php
declare(strict_types=1);
function hasAllowedScheme(string $uri, array $allowedSchemes): bool
{
$scheme = parse_url($uri, PHP_URL_SCHEME);
return is_string($scheme) && in_array(strtolower($scheme), $allowedSchemes, true);
}
echo hasAllowedScheme('https://example.com/feed.json', ['https']) ? 'allowed' : 'blocked';
echo PHP_EOL;
echo hasAllowedScheme('data://text/plain,hello', ['https']) ? 'allowed' : 'blocked';
echo PHP_EOL;
// Prints:
// allowed
// blocked
For local files, scheme checks are not enough. Resolve paths with realpath() and confirm they stay inside the directory you intended.
What you should be able to do
After this lesson, you should be able to explain what stream wrappers are, list available wrappers, use php://temp safely, recognise why allow_url_fopen matters, and avoid passing user-controlled wrapper strings into file or include operations.
Practice
Task: Allow Safe Wrapper Schemes
Create a PHP script that decides whether a stream URI is allowed for a feed importer.
The importer should only accept https:// URLs. It must reject local files, php:// streams, and data:// streams.
Requirements
- Write a function that checks the URI scheme with
parse_url(). - Accept only the
httpsscheme. - Test at least one allowed URL and three blocked values.
- Print a clear result for each value.
- Add a short note explaining why this matters before passing values to
file_get_contents().
Check your work
The script should be runnable without making any network requests. It is only deciding whether the value would be allowed.
Show solution
One possible solution is to parse the scheme and compare it with a small allow-list.
<?php
declare(strict_types=1);
function isAllowedFeedUri(string $uri): bool
{
$scheme = parse_url($uri, PHP_URL_SCHEME);
return is_string($scheme) && strtolower($scheme) === 'https';
}
$values = [
'https://example.com/feed.json',
'http://example.com/feed.json',
'/var/www/private/feed.json',
'php://filter/resource=index.php',
'data://text/plain,hello',
];
foreach ($values as $value) {
echo $value . ': ' . (isAllowedFeedUri($value) ? 'allowed' : 'blocked') . PHP_EOL;
}
// Prints:
// https://example.com/feed.json: allowed
// http://example.com/feed.json: blocked
// /var/www/private/feed.json: blocked
// php://filter/resource=index.php: blocked
// data://text/plain,hello: blocked
This matters because stream wrappers make some strings behave like resources. If user input goes straight into file_get_contents(), a value that looks like a path or URL may read from a wrapper the developer did not intend to support.