Guidelines for developing secure file browser APIs in PHP with strict validation, path security, and comprehensive testing
You are an expert PHP developer working on the Web File Browser API project. Follow these guidelines strictly to maintain security, code quality, and architectural consistency.
Every endpoint MUST include `bootstrap.php` first:
```php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../../src/web-file-browser-api/bootstrap.php';
```
The bootstrap provides:
**Important**: Never redefine `API_DATA_DIR` or `API_TRASH_DIR` constants in endpoints.
Use this structure for all endpoints:
```php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../../src/web-file-browser-api/bootstrap.php';
validateMethod(['POST']); // or ['GET'], ['PUT'], etc.
try {
// 1. Get and validate input
$input = getInput(INPUT_POST, 'param', '');
// 2. Resolve paths securely
$path = resolvePath($input);
// 3. Perform business logic
// ... your code here ...
// 4. Send success response
sendSuccess(['result' => 'data']);
} catch (Throwable $e) {
handleError($e);
}
```
All user-provided paths MUST be validated:
```php
// For data directory operations
$safePath = resolvePath($userInput);
// For operations supporting trash
$safePath = resolvePathWithTrash($userInput);
// Never use user input directly:
// BAD: file_exists($userInput)
// GOOD: file_exists(resolvePath($userInput))
```
Internal implementation uses `PathSecurity::resolveSafePath()` which:
Validate all filenames before file operations:
```php
PathSecurity::validateFileName($filename);
```
This checks for:
For upload endpoints:
```php
// 1. Verify file was actually uploaded
if (!is_uploaded_file($_FILES['file']['tmp_name'])) {
throw new ValidationException('Invalid upload');
}
// 2. Check size limits (from Config.php)
if ($_FILES['file']['size'] > Config::MAX_UPLOAD_SIZE) {
throw new ValidationException('File too large');
}
// 3. Validate MIME type using file content (not extension)
$fileInfo = new FileInfo($_FILES['file']['tmp_name']);
if (!in_array($fileInfo->getMimeType(), Config::ALLOWED_MIME_TYPES)) {
throw new ValidationException('File type not allowed');
}
// 4. Generate safe destination path
$destPath = PathSecurity::constructSequentialFilePath(
$targetDir,
$_FILES['file']['name']
);
// 5. Move atomically
if (!move_uploaded_file($_FILES['file']['tmp_name'], $destPath)) {
throw new RuntimeException('Upload failed');
}
```
Use appropriate exception types:
```php
// Path/security issues
throw new PathException('Path outside allowed directory');
// Input validation issues
throw new ValidationException('Invalid filename');
// Directory operation issues
throw new DirectoryException('Failed to create directory');
// Business logic issues
throw new RuntimeException('Operation failed');
```
The `handleError()` function automatically:
```php
// Always use resolved paths
$path = resolvePath($userInput);
// Check existence
if (!file_exists($path)) {
throw new RuntimeException('File not found');
}
// Read with proper resource handling
$content = file_get_contents($path);
if ($content === false) {
throw new RuntimeException('Failed to read file');
}
// Write atomically when possible
if (file_put_contents($path, $content, LOCK_EX) === false) {
throw new RuntimeException('Failed to write file');
}
```
```php
$safePath = PathSecurity::constructSequentialFilePath($dir, $filename);
// Generates: file.txt, file-1.txt, file-2.txt, etc.
```
Edit `src/web-file-browser-api/Config.php` for:
Test individual classes in isolation:
```php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../src/web-file-browser-api/ClassName.php';
echo "Testing ClassName...\n";
// Test case 1
try {
ClassName::method($input);
echo "✓ Test passed\n";
} catch (Exception $e) {
echo "✗ Test failed: {$e->getMessage()}\n";
}
echo "All tests passed!\n";
```
Run: `php test/run-all.php`
Test HTTP endpoints with automatic server management:
```php
<?php
declare(strict_types=1);
require_once __DIR__ . '/TestSetup.php'; // Auto-starts server
require_once __DIR__ . '/ApiTestHelpers.php';
echo "Testing endpoint...\n";
const DATA_DIR = __DIR__ . '/../public/data';
// Test success case
$response = ApiTestHelpers::post('/endpoint/', ['param' => 'value']);
ApiTestHelpers::assertSuccess($response);
// Register files for cleanup
if (isset($response['json']['files'])) {
foreach ($response['json']['files'] as $file) {
ApiTestHelpers::registerUploadedFile(DATA_DIR, $file);
}
}
// Test error case
$response = ApiTestHelpers::post('/endpoint/', ['param' => '../../../etc/passwd']);
ApiTestHelpers::assertError($response);
assert($response['status'] === 400);
echo "All tests passed!\n";
```
Run: `php test-api/run-all.php` (all tests) or `php test-api/endpoint-name.test.php` (individual)
**Test Cleanup Best Practices**:
1. Create directory under `public/web-file-browser-api/`
2. Add `index.php` with `bootstrap.php` inclusion
3. Use `validateMethod()` to enforce HTTP methods
4. Validate and resolve all paths using `resolvePath()`
5. Use `try-catch` with `handleError()` for exceptions
6. Return data with `sendSuccess()` or `sendError()`
7. Add corresponding test in `test-api/`
1. Create class in `src/web-file-browser-api/`
2. Use `final class` for stateless utilities
3. Use `static` methods when appropriate
4. Add strict type hints for all parameters and return values
5. Throw exceptions for invalid inputs (don't return false/null)
6. Add unit test in `test/`
Follow PSR-12 Extended Coding Style Guide:
❌ **Don't**:
✅ **Do**:
1. **Start with security**: Validate inputs and resolve paths first
2. **Use strict types**: Add `declare(strict_types=1);` to every file
3. **Fail fast**: Validate early, throw exceptions immediately
4. **Keep it simple**: Prefer clarity over cleverness
5. **Write tests**: Add both unit tests and API tests for new functionality
6. **Document edge cases**: Add comments for non-obvious security checks
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/web-file-browser-api-development/raw