![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/private_html/utils/ |
<?php
// Audio proxy endpoint - hides actual MP3 file paths
// Uses track_id instead of task_id for community pages
// Now includes signed token validation to prevent URL sharing
error_reporting(0);
ini_set('display_errors', 0);
ob_start();
session_start();
// Include token validation
require_once __DIR__ . '/audio_token.php';
/**
* Log access violation and display a styled error page (bilingual FR/EN)
* Logs attempt to database and invites user to review terms
*
* @param string $reason Reason for denial (for logging)
* @param int|string $trackId The track ID attempted
* @param string $token The token used (if any)
* @param int $expires Token expiration (if any)
*/
function showAccessDeniedPage($reason, $trackId = null, $token = null, $expires = null) {
// Collect violation data
$violationData = [
'reason' => $reason,
'track_id' => $trackId,
'token' => $token ? substr($token, 0, 8) . '...' : null, // Partial token for privacy
'expires' => $expires,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
'referrer' => $_SERVER['HTTP_REFERER'] ?? 'direct',
'request_uri' => $_SERVER['REQUEST_URI'] ?? '',
'user_id' => $_SESSION['user_id'] ?? null,
'session_id' => session_id(),
'timestamp' => date('Y-m-d H:i:s')
];
// Log to error log
error_log("AUDIO ACCESS VIOLATION: " . json_encode($violationData));
// Try to log to database
try {
require_once __DIR__ . '/../config/database.php';
$pdo = getDBConnection();
if ($pdo) {
// Create table if not exists (first time only)
$pdo->exec("
CREATE TABLE IF NOT EXISTS audio_access_violations (
id INT AUTO_INCREMENT PRIMARY KEY,
reason VARCHAR(255) NOT NULL,
track_id INT NULL,
token_partial VARCHAR(20) NULL,
ip_address VARCHAR(45) NOT NULL,
user_agent TEXT,
referrer TEXT,
request_uri TEXT,
user_id INT NULL,
session_id VARCHAR(128),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ip (ip_address),
INDEX idx_created (created_at),
INDEX idx_reason (reason)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
// Insert violation record
$stmt = $pdo->prepare("
INSERT INTO audio_access_violations
(reason, track_id, token_partial, ip_address, user_agent, referrer, request_uri, user_id, session_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$reason,
$trackId,
$violationData['token'],
$violationData['ip_address'],
substr($violationData['user_agent'], 0, 500),
substr($violationData['referrer'], 0, 500),
substr($violationData['request_uri'], 0, 500),
$violationData['user_id'],
$violationData['session_id']
]);
}
} catch (Exception $e) {
error_log("Failed to log audio violation to database: " . $e->getMessage());
}
// Clear any output buffers
while (ob_get_level()) {
ob_end_clean();
}
// Send 403 status
http_response_code(403);
// Include translation system first to get proper language
require_once __DIR__ . '/../includes/translations.php';
$current_lang = getCurrentLanguage();
$isFrench = ($current_lang === 'fr');
// Bilingual content
$texts = [
'title' => $isFrench ? 'Accès Refusé' : 'Access Denied',
'page_title' => $isFrench ? 'Accès Refusé - Sound Studio Pro' : 'Access Denied - Sound Studio Pro',
'message' => $isFrench
? 'Ce contenu audio est protégé. L\'accès direct aux fichiers audio n\'est pas autorisé afin de protéger les droits de nos créateurs et d\'assurer une licence appropriée.'
: 'This audio content is protected. Direct access to audio files is not permitted to protect the rights of our creators and ensure proper licensing.',
'why_title' => $isFrench ? 'Pourquoi je vois ceci ?' : 'Why am I seeing this?',
'why_message' => $isFrench
? 'Ce lien n\'est plus valide. Il a peut-être expiré ou été utilisé depuis un autre appareil.'
: 'This link is no longer valid. It may have expired or been used from another device.',
'terms' => $isFrench ? '📜 Conditions d\'utilisation' : '📜 Terms of Service',
'privacy' => $isFrench ? '🔐 Politique de confidentialité' : '🔐 Privacy Policy',
'home_btn' => $isFrench ? '🏠 Retour à Sound Studio Pro' : '🏠 Return to Sound Studio Pro',
'need_help' => $isFrench ? 'Besoin d\'aide ?' : 'Need help?',
'contact' => $isFrench ? 'Contacter le support' : 'Contact Support',
'copyright' => $isFrench ? 'Tous droits réservés.' : 'All rights reserved.'
];
// Set page variables for header
$page_title = $texts['page_title'];
$current_page = 'access_denied';
// Include the real site header
include __DIR__ . '/../includes/header.php';
?>
<style>
.access-denied-content {
display: flex;
align-items: center;
justify-content: center;
min-height: 60vh;
padding: 40px 20px;
}
.access-denied-container {
max-width: 520px;
text-align: center;
background: rgba(255,255,255,0.05);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 25px 50px rgba(0,0,0,0.5);
}
.access-denied-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #e74c3c, #c0392b);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 25px;
font-size: 40px;
}
.access-denied-container h1 {
font-size: 28px;
margin-bottom: 15px;
color: #fff;
}
.access-denied-message {
color: rgba(255,255,255,0.7);
line-height: 1.6;
margin-bottom: 30px;
}
.access-denied-info {
background: rgba(231, 76, 60, 0.1);
border: 1px solid rgba(231, 76, 60, 0.3);
border-radius: 10px;
padding: 15px;
margin-bottom: 25px;
font-size: 14px;
color: rgba(255,255,255,0.8);
text-align: left;
}
.access-denied-info strong {
color: #e74c3c;
}
.access-denied-links {
display: flex;
gap: 15px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 25px;
}
.access-denied-links a {
color: #667eea;
text-decoration: none;
padding: 10px 20px;
border: 1px solid #667eea;
border-radius: 8px;
transition: all 0.3s;
font-size: 14px;
}
.access-denied-links a:hover {
background: #667eea;
color: #fff;
}
.access-denied-home-btn {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 12px 30px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
transition: transform 0.3s, box-shadow 0.3s;
}
.access-denied-home-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
}
.access-denied-footer {
margin-top: 30px;
font-size: 12px;
color: rgba(255,255,255,0.4);
}
.access-denied-footer a {
color: rgba(255,255,255,0.6);
text-decoration: none;
}
.access-denied-footer a:hover {
color: #667eea;
}
</style>
<main class="access-denied-content">
<div class="access-denied-container">
<div class="access-denied-icon">🔒</div>
<h1><?= htmlspecialchars($texts['title']) ?></h1>
<p class="access-denied-message"><?= htmlspecialchars($texts['message']) ?></p>
<div class="access-denied-info">
<strong><?= htmlspecialchars($texts['why_title']) ?></strong><br><br>
<?= htmlspecialchars($texts['why_message']) ?>
</div>
<div class="access-denied-links">
<a href="/terms.php"><?= $texts['terms'] ?></a>
<a href="/privacy.php"><?= $texts['privacy'] ?></a>
</div>
<a href="/" class="access-denied-home-btn"><?= $texts['home_btn'] ?></a>
<div class="access-denied-footer">
<p><?= htmlspecialchars($texts['need_help']) ?> <a href="/contact.php"><?= htmlspecialchars($texts['contact']) ?></a></p>
<p style="margin-top: 10px;">© <?= date('Y') ?> Sound Studio Pro. <?= htmlspecialchars($texts['copyright']) ?></p>
</div>
</div>
</main>
<?php
// Include the real site footer
include __DIR__ . '/../includes/footer.php';
exit;
}
// Get track ID and optional variation from URL
$trackId = $_GET['id'] ?? '';
$variationIndex = isset($_GET['variation']) ? (int)$_GET['variation'] : null;
$token = $_GET['token'] ?? '';
$expires = isset($_GET['expires']) ? (int)$_GET['expires'] : 0;
// CRITICAL: Block direct URL access - only allow when coming from proper pages or Range requests (playback)
// This prevents people from pasting URLs directly in browser
$referrer = $_SERVER['HTTP_REFERER'] ?? '';
$isRangeRequest = isset($_SERVER['HTTP_RANGE']); // Range requests are from audio player (playback)
$isPageLoad = !$isRangeRequest && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET';
// Allowed pages that can generate valid audio URLs
$allowedPages = [
'track.php',
'community_fixed.php',
'create_music.php', // In case users preview tracks
'radio/', // Radio pages
'library', // Library pages (library.php, library_fixed.php, etc.)
'admin_batch_analyze_tracks.php', // Admin batch analysis
'admin.php', // Admin panel
];
$isFromValidPage = false;
// Check if request is coming from a valid page on our domain
if (!empty($referrer)) {
// Extract the path from the referrer URL
$referrerPath = parse_url($referrer, PHP_URL_PATH);
if ($referrerPath) {
$referrerPath = basename($referrerPath);
// Check if referrer is from our domain
$referrerHost = parse_url($referrer, PHP_URL_HOST);
$allowedDomains = [
$_SERVER['HTTP_HOST'] ?? 'soundstudiopro.com',
'soundstudiopro.com',
'www.soundstudiopro.com'
];
if (in_array($referrerHost, $allowedDomains)) {
// Check if it's from an allowed page
foreach ($allowedPages as $allowedPage) {
if (strpos($referrer, $allowedPage) !== false) {
$isFromValidPage = true;
break;
}
}
}
}
}
// Block direct access for page loads (not Range requests) if not from valid page
// Range requests are always allowed (they're from audio player, not direct browser access)
if ($isPageLoad && !$isFromValidPage) {
showAccessDeniedPage('Direct URL access - no valid referrer', $trackId, $token, $expires);
}
ob_clean();
if (empty($trackId) || !is_numeric($trackId)) {
http_response_code(400);
header('Content-Type: text/plain');
echo "Invalid track ID";
exit;
}
require_once __DIR__ . '/../config/database.php';
ob_clean();
$pdo = getDBConnection();
if (!$pdo) {
http_response_code(500);
header('Content-Type: text/plain');
echo "Database error";
exit;
}
// Get current user and session
$user_id = $_SESSION['user_id'] ?? null;
$session_id = session_id();
// Get track and verify access
$stmt = $pdo->prepare("SELECT id, task_id, audio_url, is_public, user_id, status, metadata, selected_variation FROM music_tracks WHERE id = ? AND status = 'complete'");
$stmt->execute([$trackId]);
$track = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$track) {
http_response_code(404);
header('Content-Type: text/plain');
echo "Track not found";
exit;
}
// SECURITY: Check access
$is_admin = isset($_SESSION['is_admin']) && $_SESSION['is_admin'];
$isOwner = ($user_id && $track['user_id'] == $user_id);
$isPublic = ($track['is_public'] == 1);
// Admins have access to all tracks (bypass ownership/public checks)
// For public tracks, token is optional (for backward compatibility)
// For private tracks or owners, token is required
if ($is_admin) {
// Admin access - still validate token if provided, but allow access regardless
if (!empty($token) && !empty($expires)) {
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, $user_id, $session_id);
if (!$tokenValid) {
// Try with null user_id (guest mode)
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, null, $session_id);
}
// If token is provided, it should be valid, but don't block admin if it's not
// (admin might be accessing for batch operations)
}
// Admin has access - continue to serve audio
} elseif ($isPublic) {
// Public track - if token is provided, it MUST be valid for this track and session
// This prevents URL sharing across browsers/sessions
if (!empty($token) && !empty($expires)) {
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, $user_id, $session_id);
if (!$tokenValid) {
// Try with null user_id (guest mode)
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, null, $session_id);
}
// CRITICAL: If token is provided, it MUST be valid - prevents cross-browser sharing
if (!$tokenValid) {
showAccessDeniedPage('Invalid token for session (possible URL sharing)', $trackId, $token, $expires);
}
}
// Public track - allow access (with valid token or without token)
} else {
// Private track - require valid token
if (empty($token) || empty($expires)) {
showAccessDeniedPage('Private track - missing authentication token', $trackId, $token, $expires);
}
// Validate token
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, $user_id, $session_id);
if (!$tokenValid && $isOwner) {
// Owner might have different session, try with null user_id
$tokenValid = validateAudioToken($trackId, $variationIndex, $token, $expires, null, $session_id);
}
if (!$tokenValid) {
showAccessDeniedPage('Invalid or expired token', $trackId, $token, $expires);
}
}
// CRITICAL: One-time use enforcement - check BEFORE serving, mark on page loads only
// First page load consumes the use, refresh is blocked
// Range requests (playback/seeking) don't consume uses - they're part of the same session
$isPageLoad = !isset($_SERVER['HTTP_RANGE']) && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'GET';
if (!empty($token) && !empty($expires)) {
// Check if token has already been used (blocks refresh)
$usage = checkTokenUsage($token, $trackId);
if ($usage['used'] && $usage['expired']) {
// Token already used - block immediately
showAccessDeniedPage('Token already used (possible replay attack)', $trackId, $token, $expires);
}
// Mark token as used ONLY on page loads (not Range requests)
// First page load consumes the use immediately, refresh is blocked
// Range requests (playback/seeking) don't consume uses - allows normal playback
if ($isPageLoad) {
markTokenUsed($token, $trackId);
}
}
// If no variation parameter provided, check for selected variation in metadata
if ($variationIndex === null) {
// Check metadata for selected_variation
if (!empty($track['metadata'])) {
$metadata = is_string($track['metadata']) ? json_decode($track['metadata'], true) : $track['metadata'];
if (isset($metadata['selected_variation'])) {
$variationIndex = (int)$metadata['selected_variation'];
}
}
// Also check selected_variation column if it exists
if ($variationIndex === null && isset($track['selected_variation']) && $track['selected_variation'] !== null) {
$variationIndex = (int)$track['selected_variation'];
}
}
// Get audio URL - PRIORITY: mastered version > specific variation > main track
$audioUrl = '';
// PRIORITY 1: Check for mastered version in metadata (if available)
if (!empty($track['metadata'])) {
$metadata = is_string($track['metadata']) ? json_decode($track['metadata'], true) : $track['metadata'];
if (!empty($metadata['mastered_audio_url'])) {
$audioUrl = $metadata['mastered_audio_url'];
}
}
// PRIORITY 2: Check for specific variation (if no mastered version)
if (empty($audioUrl) && $variationIndex !== null) {
// Get specific variation
$stmt = $pdo->prepare("SELECT audio_url FROM audio_variations WHERE track_id = ? AND variation_index = ?");
$stmt->execute([$trackId, $variationIndex]);
$variation = $stmt->fetch(PDO::FETCH_ASSOC);
if ($variation) {
$audioUrl = $variation['audio_url'] ?? '';
}
}
// PRIORITY 3: Use main track audio URL
if (empty($audioUrl)) {
$audioUrl = $track['audio_url'] ?? '';
// PRIORITY 4: If still empty, try first variation as fallback
if (empty($audioUrl)) {
$stmt = $pdo->prepare("SELECT audio_url FROM audio_variations WHERE track_id = ? ORDER BY variation_index ASC LIMIT 1");
$stmt->execute([$trackId]);
$variation = $stmt->fetch(PDO::FETCH_ASSOC);
if ($variation) {
$audioUrl = $variation['audio_url'] ?? '';
}
}
}
if (empty($audioUrl)) {
http_response_code(404);
header('Content-Type: text/plain');
echo "Audio not available";
exit;
}
// SECURITY: If it's a local file, validate path before serving
if (strpos($audioUrl, '/audio_files/') === 0 || strpos($audioUrl, '/uploads/') === 0) {
// Use file security utility to validate path (defense in depth)
require_once __DIR__ . '/../includes/file_security.php';
$audio_validation = validateAudioUrl($audioUrl);
if ($audio_validation['type'] !== 'local' || !$audio_validation['path']) {
// Path validation failed - security violation
error_log("SECURITY: Invalid audio path in play_audio.php: " . htmlspecialchars($audioUrl, ENT_QUOTES, 'UTF-8'));
showAccessDeniedPage('Security violation - invalid path', $trackId, $token, $expires);
}
$localPath = $audio_validation['path'];
if (file_exists($localPath)) {
$fileSize = filesize($localPath);
$fileType = mime_content_type($localPath) ?: 'audio/mpeg';
while (ob_get_level()) {
ob_end_clean();
}
header('Content-Type: ' . $fileType);
header('Accept-Ranges: bytes');
header('Cache-Control: public, max-age=3600');
// Handle Range requests for seeking support
if (isset($_SERVER['HTTP_RANGE'])) {
// Parse the Range header
$range = $_SERVER['HTTP_RANGE'];
if (preg_match('/bytes=(\d*)-(\d*)/', $range, $matches)) {
$start = $matches[1] === '' ? 0 : intval($matches[1]);
$end = $matches[2] === '' ? $fileSize - 1 : intval($matches[2]);
// Validate range
if ($start > $end || $start >= $fileSize || $end >= $fileSize) {
http_response_code(416); // Range Not Satisfiable
header("Content-Range: bytes */$fileSize");
exit;
}
$length = $end - $start + 1;
http_response_code(206); // Partial Content
header("Content-Range: bytes $start-$end/$fileSize");
header("Content-Length: $length");
// Serve the requested range
$fp = fopen($localPath, 'rb');
fseek($fp, $start);
$remaining = $length;
while ($remaining > 0 && !feof($fp)) {
$chunk = min(8192, $remaining);
echo fread($fp, $chunk);
$remaining -= $chunk;
flush();
}
fclose($fp);
exit;
}
}
// No Range header - serve full file
header('Content-Length: ' . $fileSize);
readfile($localPath);
exit;
}
}
// If it's an external URL, proxy it with Range support
if (strpos($audioUrl, 'http') === 0) {
while (ob_get_level()) {
ob_end_clean();
}
// Build headers to forward to external server
$requestHeaders = [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
];
// Forward Range header if present (for seeking support)
if (isset($_SERVER['HTTP_RANGE'])) {
$requestHeaders[] = 'Range: ' . $_SERVER['HTTP_RANGE'];
}
$ch = curl_init($audioUrl);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeaders);
// Capture response headers to forward them
$responseHeaders = [];
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = trim($header);
if (empty($header)) return $len;
// Parse header
$parts = explode(':', $header, 2);
if (count($parts) == 2) {
$name = strtolower(trim($parts[0]));
$value = trim($parts[1]);
$responseHeaders[$name] = $value;
} elseif (strpos($header, 'HTTP/') === 0) {
// Status line - extract status code
if (preg_match('/HTTP\/\d\.?\d?\s+(\d+)/', $header, $matches)) {
$responseHeaders['_status'] = intval($matches[1]);
}
}
return $len;
});
// Buffer the response to get headers first, then stream
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
http_response_code(500);
header('Content-Type: text/plain');
echo "Failed to stream audio";
exit;
}
// Set appropriate status code (206 for partial content, 200 for full)
if ($httpCode == 206) {
http_response_code(206);
}
// Forward relevant headers
header('Content-Type: ' . ($responseHeaders['content-type'] ?? 'audio/mpeg'));
header('Accept-Ranges: bytes');
header('Cache-Control: public, max-age=3600');
if (isset($responseHeaders['content-length'])) {
header('Content-Length: ' . $responseHeaders['content-length']);
}
if (isset($responseHeaders['content-range'])) {
header('Content-Range: ' . $responseHeaders['content-range']);
}
// Output the body
echo $body;
exit;
}
http_response_code(404);
header('Content-Type: text/plain');
echo "Audio not available";
exit;
?>