mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
Add SQLite database schema and documentation
Added complete database schema for Post-ERG thesis archive: - schema.sql with full relational database structure - README.md with schema documentation and usage examples - SETUP.md with comprehensive setup and maintenance guide - posterg_fiche-technique.md with technical specifications - Database_TFE_test.csv and .ods with example data Database features: - Normalized relational schema (3NF) - Support for multiple authors, supervisors, languages, formats, keywords - Publication workflow (submission → defense → jury review → publication) - Access control (Libre/Interne/Interdit) - File attachments tracking - Predefined reference tables for orientations, AP programs, finalities - Views for simplified querying - Automatic timestamps and cascade deletes
This commit is contained in:
36
formulaire/.htaccess
Normal file
36
formulaire/.htaccess
Normal file
@@ -0,0 +1,36 @@
|
||||
# Security headers
|
||||
<IfModule mod_headers.c>
|
||||
# Prevent clickjacking
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
|
||||
# Prevent MIME type sniffing
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
|
||||
# Enable XSS protection
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
|
||||
# Referrer policy
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Content Security Policy (adjust as needed)
|
||||
Header always set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
|
||||
</IfModule>
|
||||
|
||||
# Prevent directory listing
|
||||
Options -Indexes
|
||||
|
||||
# Protect sensitive files
|
||||
<FilesMatch "^\.">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
<FilesMatch "(composer\.(json|lock)|error\.log)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# PHP security settings (if .htaccess can override)
|
||||
<IfModule mod_php.c>
|
||||
php_flag display_errors Off
|
||||
php_flag log_errors On
|
||||
php_value error_log error.log
|
||||
</IfModule>
|
||||
163
formulaire/SECURITY.md
Normal file
163
formulaire/SECURITY.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Security Improvements
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Critical Vulnerability Fixes
|
||||
|
||||
#### Path Traversal in thanks.php (CRITICAL)
|
||||
- **Before**: User could access ANY file on the system via `?file=../../../../etc/passwd`
|
||||
- **After**:
|
||||
- Validates file path using `realpath()` to resolve symlinks
|
||||
- Ensures file is within allowed `data/yaml/` directory
|
||||
- Verifies file extension is `.yaml`
|
||||
- Proper error handling without exposing system paths
|
||||
|
||||
#### CSRF Protection
|
||||
- **Before**: Form could be submitted from any website
|
||||
- **After**:
|
||||
- Session-based CSRF tokens generated for each form load
|
||||
- Token validated on submission using timing-safe comparison (`hash_equals()`)
|
||||
- Token cleared after successful submission
|
||||
|
||||
### 2. Input Validation & Sanitization
|
||||
|
||||
#### Deprecated Functions Replaced
|
||||
- **Before**: Used `FILTER_SANITIZE_STRING` (deprecated in PHP 8.1+)
|
||||
- **After**: Custom `sanitize_string()` function using `htmlspecialchars()` and `strip_tags()`
|
||||
|
||||
#### Enhanced Validation
|
||||
- Required fields properly validated with custom `validate_required()` function
|
||||
- Email validation using `FILTER_VALIDATE_EMAIL`
|
||||
- URL validation using `FILTER_VALIDATE_URL`
|
||||
- Year validation with reasonable range checking (2000 to current year + 1)
|
||||
- Comprehensive error messages for validation failures
|
||||
|
||||
### 3. File Upload Security
|
||||
|
||||
#### Random Filenames
|
||||
- **Before**: Used original or predictable filenames (author + timestamp)
|
||||
- **After**:
|
||||
- Generates cryptographically secure random filenames using `random_bytes()`
|
||||
- Prevents file overwrites
|
||||
- Prevents path traversal attacks via malicious filenames
|
||||
- Stores mapping to original filename for reference
|
||||
|
||||
#### Enhanced File Validation
|
||||
- MIME type checking using `finfo`
|
||||
- File extension whitelist
|
||||
- File size limits (50MB max)
|
||||
- Proper error handling for upload errors
|
||||
- Cover image restricted to JPEG/PNG only
|
||||
|
||||
### 4. Bug Fixes
|
||||
|
||||
- Fixed undefined variable `$memoireFolder` (used before definition)
|
||||
- Fixed undefined variable `$resume` (should be `$description`)
|
||||
- Fixed variable ordering (generate `$uniqueId` before using it)
|
||||
- Added proper `__DIR__` prefix for absolute paths
|
||||
|
||||
### 5. Error Handling
|
||||
|
||||
- Try-catch block wraps entire form processing
|
||||
- Detailed error logging (not exposed to users)
|
||||
- User-friendly error messages
|
||||
- Proper exit after redirect
|
||||
- No system path exposure in error messages
|
||||
|
||||
## Nginx Configuration Notes
|
||||
|
||||
Since this form is behind nginx password authentication, additional security layers:
|
||||
|
||||
### Recommended nginx config:
|
||||
```nginx
|
||||
location /formulaire {
|
||||
auth_basic "Restricted Access";
|
||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||||
|
||||
# Rate limiting
|
||||
limit_req zone=form_limit burst=5 nodelay;
|
||||
|
||||
# File upload size
|
||||
client_max_body_size 100M;
|
||||
|
||||
# Timeout settings
|
||||
client_body_timeout 60s;
|
||||
|
||||
# Prevent access to sensitive files
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
location ~ /(vendor|composer\.(json|lock)|error\.log)$ {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Additional Recommendations
|
||||
|
||||
### 1. Database Migration (In Progress)
|
||||
Moving to SQLite will provide:
|
||||
- Structured data storage
|
||||
- Better query capabilities
|
||||
- Easier data management
|
||||
- Prepared statements for SQL injection prevention
|
||||
|
||||
### 2. File Storage
|
||||
- Consider moving uploaded files outside web root
|
||||
- Serve files through PHP script with access control
|
||||
- Implement file scanning for malware if possible
|
||||
|
||||
### 3. Monitoring
|
||||
- Regularly review `error.log` for suspicious activity
|
||||
- Monitor file upload patterns
|
||||
- Set up alerts for failed CSRF validations
|
||||
|
||||
### 4. Backup Strategy
|
||||
- Regular backups of `data/` directory
|
||||
- Version control for code changes
|
||||
- Test restore procedures
|
||||
|
||||
### 5. PHP Configuration
|
||||
Ensure these settings in php.ini:
|
||||
```ini
|
||||
file_uploads = On
|
||||
upload_max_filesize = 100M
|
||||
post_max_size = 100M
|
||||
max_execution_time = 60
|
||||
max_input_time = 60
|
||||
memory_limit = 256M
|
||||
|
||||
# Security
|
||||
expose_php = Off
|
||||
allow_url_fopen = Off
|
||||
allow_url_include = Off
|
||||
display_errors = Off
|
||||
log_errors = On
|
||||
```
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Form submission with all fields
|
||||
- [ ] Form submission with minimal required fields
|
||||
- [ ] Invalid email format
|
||||
- [ ] Invalid URL format
|
||||
- [ ] Invalid year
|
||||
- [ ] File upload (various formats)
|
||||
- [ ] Large file upload (>50MB, should fail)
|
||||
- [ ] Invalid file types
|
||||
- [ ] Multiple file uploads
|
||||
- [ ] Cover image upload
|
||||
- [ ] CSRF token validation (try submitting with wrong token)
|
||||
- [ ] Path traversal attempt in thanks.php
|
||||
- [ ] Error handling for missing directories
|
||||
|
||||
## Known Limitations
|
||||
|
||||
1. **No atomic transactions**: File operations and YAML save not atomic
|
||||
2. **No rollback**: Failed submissions may leave partial files
|
||||
3. **Session storage**: CSRF tokens in default PHP session (consider database sessions)
|
||||
4. **No upload progress**: Large files have no progress indicator
|
||||
5. **No duplicate detection**: Same submission can be made multiple times
|
||||
|
||||
These limitations will be addressed in the SQLite migration.
|
||||
@@ -5,6 +5,16 @@ ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
ini_set('error_log', 'error.log');
|
||||
|
||||
// Start session for CSRF protection
|
||||
session_start();
|
||||
|
||||
// Verify CSRF token
|
||||
if (!isset($_POST['csrf_token']) || !isset($_SESSION['csrf_token']) ||
|
||||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
||||
error_log("CSRF token validation failed");
|
||||
die("Erreur de sécurité : token invalide. Veuillez recharger le formulaire.");
|
||||
}
|
||||
|
||||
// Log the content of the $_FILES array
|
||||
error_log("FILES array: " . print_r($_FILES, true));
|
||||
|
||||
@@ -12,136 +22,211 @@ require_once 'vendor/autoload.php';
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Behat\Transliterator\Transliterator;
|
||||
|
||||
// Helper function to sanitize string input (replacement for deprecated FILTER_SANITIZE_STRING)
|
||||
function sanitize_string($input) {
|
||||
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
// Helper function to validate required field
|
||||
function validate_required($value, $fieldName) {
|
||||
if (empty($value)) {
|
||||
throw new Exception("Le champ '$fieldName' est requis.");
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Define variables
|
||||
$yamlFolder = "data/yaml/";
|
||||
$yamlFolder = __DIR__ . "/data/yaml/";
|
||||
$date = date("Y-m-d");
|
||||
$errors = [];
|
||||
|
||||
// Sanitize input data
|
||||
$auteurice = filter_var($_POST["auteurice"], FILTER_SANITIZE_STRING);
|
||||
$annee = filter_var($_POST["année"], FILTER_SANITIZE_NUMBER_INT);
|
||||
$mail = filter_var($_POST["mail"], FILTER_SANITIZE_EMAIL);
|
||||
$titre = filter_var($_POST["titre"], FILTER_SANITIZE_STRING);
|
||||
$tag = filter_var($_POST["tag"], FILTER_SANITIZE_STRING);
|
||||
$promoteurice = filter_var($_POST["promoteurice"], FILTER_SANITIZE_STRING);
|
||||
$problematique = filter_var($_POST["problématique"], FILTER_SANITIZE_STRING);
|
||||
$description = filter_var($_POST["description"], FILTER_SANITIZE_STRING);
|
||||
$orientation = filter_var($_POST["orientation"], FILTER_SANITIZE_STRING);
|
||||
$ap = filter_var($_POST["ap"], FILTER_SANITIZE_STRING);
|
||||
$lien = filter_var($_POST["lien"], FILTER_SANITIZE_STRING);
|
||||
$couverture = $_FILES["couverture"];
|
||||
$files = $_FILES["files"];
|
||||
try {
|
||||
// Validate and sanitize input data with proper error handling
|
||||
$auteurice = validate_required(sanitize_string($_POST["auteurice"] ?? ''), "Nom/Prénom/Pseudo");
|
||||
|
||||
// Transformation du string de mot-clé en un array.
|
||||
$tagArray = explode(', ', $tag);
|
||||
|
||||
|
||||
$coverFolder = $memoireFolder . "data/cover/";
|
||||
if (!file_exists($coverFolder)) {
|
||||
mkdir($coverFolder, 0755, true);
|
||||
}
|
||||
|
||||
$couverturePath = "";
|
||||
if ($couverture["error"] === UPLOAD_ERR_OK) {
|
||||
$fileExtension = pathinfo($couverture["name"], PATHINFO_EXTENSION);
|
||||
$newCouvertureName = $auteurice . "_" . $annee . "_" . $uniqueId . "." . $fileExtension;
|
||||
$targetFile = $coverFolder . $newCouvertureName;
|
||||
if (move_uploaded_file($couverture["tmp_name"], $targetFile)) {
|
||||
chmod($targetFile, 0644);
|
||||
$couverturePath = $targetFile;
|
||||
} else {
|
||||
error_log("Failed to move uploaded couverture file: " . $couverture["name"]);
|
||||
$annee = filter_var($_POST["année"] ?? '', FILTER_VALIDATE_INT);
|
||||
if ($annee === false || $annee < 2000 || $annee > (int)date('Y') + 1) {
|
||||
throw new Exception("Année invalide. Veuillez entrer une année valide.");
|
||||
}
|
||||
}
|
||||
|
||||
$uploadedFiles = [];
|
||||
$mail = filter_var($_POST["mail"] ?? '', FILTER_VALIDATE_EMAIL);
|
||||
if ($mail === false && !empty($_POST["mail"])) {
|
||||
throw new Exception("Adresse email invalide.");
|
||||
}
|
||||
|
||||
// Create necessary directories
|
||||
$memoireFolder = "data/content/{$annee}/{$auteurice}/";
|
||||
if (!file_exists($yamlFolder)) {
|
||||
mkdir($yamlFolder, 0755, true);
|
||||
}
|
||||
if (!file_exists($memoireFolder)) {
|
||||
mkdir($memoireFolder, 0755, true);
|
||||
}
|
||||
$titre = validate_required(sanitize_string($_POST["titre"] ?? ''), "Titre du mémoire");
|
||||
$tag = sanitize_string($_POST["tag"] ?? '');
|
||||
$promoteurice = sanitize_string($_POST["promoteurice"] ?? '');
|
||||
$problematique = sanitize_string($_POST["problématique"] ?? '');
|
||||
$description = sanitize_string($_POST["description"] ?? '');
|
||||
|
||||
$targetDir = $memoireFolder;
|
||||
$orientation = validate_required(sanitize_string($_POST["orientation"] ?? ''), "Orientation");
|
||||
$ap = validate_required(sanitize_string($_POST["ap"] ?? ''), "Atelier Pratique");
|
||||
|
||||
// Generate unique file name
|
||||
$uniqueId = time() . "_" . rand(1000, 9999);
|
||||
$sanitizedAuteurice = Transliterator::transliterate($auteurice);
|
||||
$uniqueFileName = $sanitizedAuteurice . "_" . $date . "_" . $uniqueId;
|
||||
|
||||
// Define security constraints
|
||||
$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip'];
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'mp4', 'zip'];
|
||||
$maxFileSize = 50 * 1024 * 1024; // 50 MB
|
||||
|
||||
// Process uploaded files
|
||||
if (is_array($files["name"])) {
|
||||
for ($i = 0; $i < count($files["name"]); $i++) {
|
||||
// Log the file being processed
|
||||
error_log("Processing file: " . $files["name"][$i]);
|
||||
// Check for file upload errors
|
||||
if ($files["error"][$i] !== UPLOAD_ERR_OK) {
|
||||
error_log("File upload error: " . $files["name"][$i]);
|
||||
continue;
|
||||
// Validate URL if provided
|
||||
$lien = $_POST["lien"] ?? '';
|
||||
if (!empty($lien)) {
|
||||
$lien = filter_var($lien, FILTER_VALIDATE_URL);
|
||||
if ($lien === false) {
|
||||
throw new Exception("Lien URL invalide.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check MIME type and file extension
|
||||
$couverture = $_FILES["couverture"] ?? null;
|
||||
$files = $_FILES["files"] ?? null;
|
||||
|
||||
// Transformation du string de mot-clé en un array.
|
||||
$tagArray = !empty($tag) ? array_map('trim', explode(',', $tag)) : [];
|
||||
|
||||
// Generate unique identifiers FIRST (before using them)
|
||||
$uniqueId = time() . "_" . rand(1000, 9999);
|
||||
$sanitizedAuteurice = Transliterator::transliterate($auteurice);
|
||||
$uniqueFileName = $sanitizedAuteurice . "_" . $date . "_" . $uniqueId;
|
||||
|
||||
// Create necessary directories
|
||||
$memoireFolder = __DIR__ . "/data/content/{$annee}/{$auteurice}/";
|
||||
$coverFolder = __DIR__ . "/data/cover/";
|
||||
|
||||
if (!file_exists($yamlFolder)) {
|
||||
mkdir($yamlFolder, 0755, true);
|
||||
}
|
||||
if (!file_exists($memoireFolder)) {
|
||||
mkdir($memoireFolder, 0755, true);
|
||||
}
|
||||
if (!file_exists($coverFolder)) {
|
||||
mkdir($coverFolder, 0755, true);
|
||||
}
|
||||
|
||||
$targetDir = $memoireFolder;
|
||||
$uploadedFiles = [];
|
||||
$couverturePath = "";
|
||||
|
||||
// Define security constraints
|
||||
$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'application/zip'];
|
||||
$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'mp4', 'zip'];
|
||||
$maxFileSize = 50 * 1024 * 1024; // 50 MB
|
||||
|
||||
// Process cover image first
|
||||
if ($couverture && isset($couverture["error"]) && $couverture["error"] === UPLOAD_ERR_OK) {
|
||||
// Security: validate MIME type
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($files["tmp_name"][$i]);
|
||||
$fileExtension = pathinfo($files["name"][$i], PATHINFO_EXTENSION);
|
||||
$mimeType = $finfo->file($couverture["tmp_name"]);
|
||||
$fileExtension = strtolower(pathinfo($couverture["name"], PATHINFO_EXTENSION));
|
||||
|
||||
if (!in_array($mimeType, $allowedMimeTypes) || !in_array($fileExtension, $allowedExtensions)) {
|
||||
error_log("Invalid file type or extension: " . $files["name"][$i]);
|
||||
continue;
|
||||
}
|
||||
// Only allow image files for cover
|
||||
if (in_array($mimeType, ['image/jpeg', 'image/png']) &&
|
||||
in_array($fileExtension, ['jpg', 'jpeg', 'png'])) {
|
||||
|
||||
// Check file size
|
||||
if ($files["size"][$i] > $maxFileSize) {
|
||||
error_log("File is too large: " . $files["name"][$i]);
|
||||
continue;
|
||||
}
|
||||
// Move and set permissions for the uploaded file
|
||||
$targetFile = $targetDir . basename($files["name"][$i]);
|
||||
if (move_uploaded_file($files["tmp_name"][$i], $targetFile)) {
|
||||
// Log successful file move
|
||||
error_log("File successfully moved: " . $targetFile);
|
||||
chmod($targetFile, 0644);
|
||||
$uploadedFiles[] = $targetFile;
|
||||
// Security: Generate random filename to prevent overwrites and path traversal
|
||||
$randomName = bin2hex(random_bytes(16));
|
||||
$newCouvertureName = $randomName . "." . $fileExtension;
|
||||
$targetFile = $coverFolder . $newCouvertureName;
|
||||
|
||||
if (move_uploaded_file($couverture["tmp_name"], $targetFile)) {
|
||||
chmod($targetFile, 0644);
|
||||
$couverturePath = "data/cover/" . $newCouvertureName;
|
||||
error_log("Cover image uploaded: " . $newCouvertureName);
|
||||
} else {
|
||||
error_log("Failed to move uploaded couverture file: " . $couverture["name"]);
|
||||
}
|
||||
} else {
|
||||
error_log("Failed to move uploaded file: " . $files["name"][$i]);
|
||||
error_log("Invalid cover image type: " . $mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
// Process uploaded files
|
||||
if ($files && is_array($files["name"])) {
|
||||
for ($i = 0; $i < count($files["name"]); $i++) {
|
||||
// Skip if no file was uploaded for this slot
|
||||
if ($files["error"][$i] === UPLOAD_ERR_NO_FILE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Log the file being processed
|
||||
error_log("Processing file: " . $files["name"][$i]);
|
||||
|
||||
// Check for file upload errors
|
||||
if ($files["error"][$i] !== UPLOAD_ERR_OK) {
|
||||
error_log("File upload error code " . $files["error"][$i] . ": " . $files["name"][$i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check MIME type and file extension
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($files["tmp_name"][$i]);
|
||||
$fileExtension = strtolower(pathinfo($files["name"][$i], PATHINFO_EXTENSION));
|
||||
|
||||
if (!in_array($mimeType, $allowedMimeTypes) || !in_array($fileExtension, $allowedExtensions)) {
|
||||
error_log("Invalid file type or extension: " . $files["name"][$i] . " (MIME: $mimeType, Ext: $fileExtension)");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check file size
|
||||
if ($files["size"][$i] > $maxFileSize) {
|
||||
error_log("File is too large: " . $files["name"][$i] . " (" . $files["size"][$i] . " bytes)");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Security: Generate random filename to prevent overwrites and path traversal
|
||||
$randomName = bin2hex(random_bytes(16));
|
||||
$safeFileName = $randomName . "." . $fileExtension;
|
||||
$targetFile = $targetDir . $safeFileName;
|
||||
|
||||
if (move_uploaded_file($files["tmp_name"][$i], $targetFile)) {
|
||||
// Log successful file move
|
||||
error_log("File successfully moved: " . $safeFileName);
|
||||
chmod($targetFile, 0644);
|
||||
$uploadedFiles[] = [
|
||||
'path' => "data/content/{$annee}/{$auteurice}/" . $safeFileName,
|
||||
'original_name' => basename($files["name"][$i]),
|
||||
'size' => $files["size"][$i]
|
||||
];
|
||||
} else {
|
||||
error_log("Failed to move uploaded file: " . $files["name"][$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Prepare form data for YAML
|
||||
$formData = [
|
||||
'auteurice' => $auteurice,
|
||||
'année' => $annee,
|
||||
'email' => $mail ?: '',
|
||||
'titre' => $titre,
|
||||
'tag' => $tagArray,
|
||||
'promoteurice' => $promoteurice,
|
||||
'problématique' => $problematique,
|
||||
'description' => $description, // Fixed: was $resume
|
||||
'orientation' => $orientation,
|
||||
'ap' => $ap,
|
||||
'lien' => $lien,
|
||||
'couverture' => $couverturePath,
|
||||
'files' => $uploadedFiles
|
||||
];
|
||||
|
||||
// Convert form data to YAML
|
||||
$yamlData = Yaml::dump($formData);
|
||||
|
||||
// Save YAML file
|
||||
$yamlFilePath = $yamlFolder . $uniqueFileName . ".yaml";
|
||||
if (file_put_contents($yamlFilePath, $yamlData) === false) {
|
||||
throw new Exception("Erreur lors de l'écriture du fichier YAML.");
|
||||
}
|
||||
|
||||
error_log("Form submission saved: " . $yamlFilePath);
|
||||
|
||||
// Clear CSRF token after successful submission
|
||||
unset($_SESSION['csrf_token']);
|
||||
|
||||
// Redirect to the thank you page
|
||||
header('Location: thanks.php?file=' . urlencode($yamlFilePath));
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Form processing error: " . $e->getMessage());
|
||||
die("Erreur lors du traitement du formulaire : " . htmlspecialchars($e->getMessage()) .
|
||||
"<br><br><a href='index.php'>Retour au formulaire</a>");
|
||||
}
|
||||
|
||||
|
||||
// Prepare form data for YAML
|
||||
$formData = [
|
||||
'auteurice' => $auteurice,
|
||||
'année' => $annee,
|
||||
'email' => $mail,
|
||||
'titre' => $titre,
|
||||
'tag' => $tagArray,
|
||||
'promoteurice' => $promoteurice,
|
||||
'problématique' => $problematique,
|
||||
'description' => $resume,
|
||||
'orientation' => $orientation,
|
||||
'ap' => $ap,
|
||||
'lien' => $lien,
|
||||
'couverture' => $couverturePath,
|
||||
'files' => $uploadedFiles
|
||||
];
|
||||
|
||||
// Convert form data to YAML
|
||||
$yamlData = Yaml::dump($formData);
|
||||
|
||||
// Save YAML file
|
||||
$yamlFilePath = $yamlFolder . $uniqueFileName . ".yaml";
|
||||
file_put_contents($yamlFilePath, $yamlData);
|
||||
|
||||
// Redirect to the thank you page
|
||||
header('Location: thanks.php?file=' . urlencode($yamlFilePath));
|
||||
|
||||
?>
|
||||
@@ -1,4 +1,10 @@
|
||||
|
||||
<?php
|
||||
// Start session and generate CSRF token
|
||||
session_start();
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -19,6 +25,8 @@
|
||||
|
||||
<main>
|
||||
<form action="formulaire.php" method="post" enctype="multipart/form-data">
|
||||
<!-- CSRF Protection -->
|
||||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
|
||||
<label>Nom/Prénom/Pseudo</label>
|
||||
<input type="text" name="auteurice" placeholder="..." required>
|
||||
|
||||
|
||||
@@ -8,8 +8,39 @@ require 'vendor/autoload.php';
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
$yamlFile = isset($_GET['file']) ? urldecode($_GET['file']) : '';
|
||||
$data = Yaml::parseFile($yamlFile);
|
||||
// Security: Validate file parameter to prevent path traversal
|
||||
$yamlFile = '';
|
||||
$data = null;
|
||||
$error = null;
|
||||
|
||||
if (isset($_GET['file'])) {
|
||||
$requestedFile = urldecode($_GET['file']);
|
||||
|
||||
// Security: Only allow files from the yaml directory
|
||||
$yamlFolder = realpath(__DIR__ . '/data/yaml/');
|
||||
$requestedPath = realpath($requestedFile);
|
||||
|
||||
// Verify the file exists and is within the allowed directory
|
||||
if ($requestedPath &&
|
||||
$yamlFolder &&
|
||||
strpos($requestedPath, $yamlFolder) === 0 &&
|
||||
file_exists($requestedPath) &&
|
||||
pathinfo($requestedPath, PATHINFO_EXTENSION) === 'yaml') {
|
||||
|
||||
try {
|
||||
$data = Yaml::parseFile($requestedPath);
|
||||
$yamlFile = $requestedPath;
|
||||
} catch (Exception $e) {
|
||||
error_log("Error parsing YAML file: " . $e->getMessage());
|
||||
$error = "Erreur lors de la lecture du fichier.";
|
||||
}
|
||||
} else {
|
||||
error_log("Invalid file access attempt: " . $requestedFile);
|
||||
$error = "Fichier non valide ou accès refusé.";
|
||||
}
|
||||
} else {
|
||||
$error = "Aucun fichier spécifié.";
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
@@ -30,11 +61,19 @@ $data = Yaml::parseFile($yamlFile);
|
||||
<h1>Merci 💜</h1>
|
||||
</header>
|
||||
<main>
|
||||
<p>d'avoir rempli le formulaire. Le contenu soumis a été sauvegardé et est en attente de traitement.</p>
|
||||
<?php if ($error): ?>
|
||||
<p style="color: red;">⚠️ <?php echo htmlspecialchars($error); ?></p>
|
||||
<p>Pour revenir au <a href="index.php">formulaire</a>.</p>
|
||||
<?php elseif ($data): ?>
|
||||
<p>d'avoir rempli le formulaire. Le contenu soumis a été sauvegardé et est en attente de traitement.</p>
|
||||
|
||||
<h4>Voici les informations que vous avez encodées dans le formulaire, affiché tel que c'est stocké, en yaml:</h4>
|
||||
<pre><code><?php echo htmlspecialchars(Yaml::dump($data)); ?></code></pre>
|
||||
<p>Pour revenir au <a href="index.php">formulaire</a>.</p>
|
||||
<?php else: ?>
|
||||
<p>Aucune donnée à afficher.</p>
|
||||
<p>Pour revenir au <a href="index.php">formulaire</a>.</p>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
<footer>
|
||||
<p>Formulaire fait avec ❤ en PHP et <a href="https://github.com/kevquirk/simple.css">SimpleCSS</a>.</p>
|
||||
|
||||
Reference in New Issue
Block a user